FELIX-661: temporarily add BND Builder source so we can apply a minor patch

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@683306 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/NOTICE b/bundleplugin/NOTICE
index b12ce4c..5ecb6de 100644
--- a/bundleplugin/NOTICE
+++ b/bundleplugin/NOTICE
@@ -13,6 +13,11 @@
 Copyright 2006-2008 The OSGi Alliance.
 Licensed under the Apache License 2.0.
 
+This product includes software developed by Peter Kriens
+(http://www.aqute.biz/Code/Bnd)
+Copyright 2006-2008 aQute, All rights reserved
+Licensed under the Apache License 2.0.
+
 
 II. Used Software
 
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
new file mode 100644
index 0000000..1b0f0ad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
@@ -0,0 +1,769 @@
+/* Copyright 2006 aQute SARL 
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ * 
+ * Private-Package: package-decl ( ',' package-decl )*
+ * 
+ * Export-Package: package-decl ( ',' package-decl )*
+ * 
+ * Import-Package: package-decl ( ',' package-decl )*
+ * 
+ * @version $Revision: 1.4 $
+ */
+public class Builder extends Analyzer {
+    private static final int SPLIT_MERGE_LAST  = 1;
+    private static final int SPLIT_MERGE_FIRST = 2;
+    private static final int SPLIT_ERROR       = 3;
+    private static final int SPLIT_FIRST       = 4;
+    private static final int SPLIT_DEFAULT     = 0;
+
+    boolean                  sources           = false;
+    File[]                   sourcePath;
+    Pattern                  NAME_URL          = Pattern
+                                                       .compile("(.*)(http://.*)");
+
+    public Jar build() throws Exception {
+        begin();
+
+        dot = new Jar("dot");
+        doPrebuild(dot, parseHeader(getProperty("-prebuild")));
+        doExpand(dot);
+        doIncludeResources(dot);
+
+        doConditional(dot);
+
+        dot.setManifest(calcManifest());
+        // This must happen after we analyzed so
+        // we know what it is on the classpath
+        addSources(dot);
+        if (getProperty(POM) != null)
+            doPom(dot);
+
+        doVerify(dot);
+        if (dot.getResources().isEmpty())
+            error("The JAR is empty");
+
+        dot.updateModified(lastModified());
+        return dot;
+    }
+
+    void begin() {
+        super.begin();
+        if (getProperty(IMPORT_PACKAGE) == null)
+            setProperty(IMPORT_PACKAGE, "*");
+
+        sources = getProperty(SOURCES) != null;
+    }
+
+    private void doConditional(Jar dot) throws IOException {
+        Map conditionals = getHeader(CONDITIONAL_PACKAGE);
+        if (conditionals != null && conditionals.size() > 0) {
+            int size;
+            do {
+                size = dot.getDirectories().size();
+                analyze();
+                analyzed = false;
+                Map imports = getImports();
+
+                Map filtered = merge(CONDITIONAL_PACKAGE, conditionals,
+                        imports, new HashSet());
+
+                // remove existing packages to prevent merge errors
+                filtered.keySet().removeAll(dot.getPackages());
+                filtered = replaceWithPattern(filtered);
+                doExpand(dot, CONDITIONAL_PACKAGE, filtered);
+            } while (dot.getDirectories().size() > size);
+        }
+    }
+
+    /**
+     * Intercept the call to analyze and cleanup versions after we have analyzed
+     * the setup. We do not want to cleanup if we are going to verify.
+     */
+
+    public void analyze() throws IOException {
+        super.analyze();
+        cleanupVersion(imports);
+        cleanupVersion(exports);
+        String version = getProperty(BUNDLE_VERSION);
+        if (version != null)
+            setProperty(BUNDLE_VERSION, cleanupVersion(version));
+    }
+
+    public void cleanupVersion(Map mapOfMap) {
+        for (Iterator e = mapOfMap.entrySet().iterator(); e.hasNext();) {
+            Map.Entry entry = (Map.Entry) e.next();
+            Map attributes = (Map) entry.getValue();
+            if (attributes.containsKey("version")) {
+                attributes.put("version", cleanupVersion((String) attributes
+                        .get("version")));
+            }
+        }
+    }
+
+    /**
+     * Clean up version parameters. Other builders use more fuzzy definitions of
+     * the version syntax. This method cleans up such a version to match an OSGi
+     * version.
+     * 
+     * @param version
+     * @return
+     */
+    static Pattern fuzzyVersion  = Pattern
+                                         .compile(
+                                                 "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
+                                                 Pattern.DOTALL);
+    static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)",
+                                         Pattern.DOTALL);
+
+    static Pattern nummeric      = Pattern.compile("\\d*");
+
+    static public String cleanupVersion(String version) {
+        Matcher m = fuzzyVersion.matcher(version);
+        if (m.matches()) {
+            StringBuffer result = new StringBuffer();
+            String d1 = m.group(1);
+            String d2 = m.group(3);
+            String d3 = m.group(5);
+            String qualifier = m.group(7);
+
+            if (d1 != null) {
+                result.append(d1);
+                if (d2 != null) {
+                    result.append(".");
+                    result.append(d2);
+                    if (d3 != null) {
+                        result.append(".");
+                        result.append(d3);
+                        if (qualifier != null) {
+                            result.append(".");
+                            cleanupModifier(result, qualifier);
+                        }
+                    } else if (qualifier != null) {
+                        result.append(".0.");
+                        cleanupModifier(result, qualifier);
+                    }
+                } else if (qualifier != null) {
+                    result.append(".0.0.");
+                    cleanupModifier(result, qualifier);
+                }
+                return result.toString();
+            }
+        }
+        return version;
+    }
+
+    static void cleanupModifier(StringBuffer result, String modifier) {
+        Matcher m = fuzzyModifier.matcher(modifier);
+        if (m.matches())
+            modifier = m.group(2);
+
+        for (int i = 0; i < modifier.length(); i++) {
+            char c = modifier.charAt(i);
+            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
+                    || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
+                result.append(c);
+        }
+    }
+
+    /**
+     * 
+     */
+    private void addSources(Jar dot) {
+        if (!sources)
+            return;
+
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            getProperties().store(out, "Generated by BND, at " + new Date());
+            dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out
+                    .toByteArray(), 0));
+            out.close();
+        } catch (Exception e) {
+            error("Can not embed bnd file in JAR: " + e);
+        }
+
+        for (Iterator cpe = classspace.keySet().iterator(); cpe.hasNext();) {
+            String path = (String) cpe.next();
+            path = path.substring(0, path.length() - ".class".length())
+                    + ".java";
+
+            for (int i = 0; i < sourcePath.length; i++) {
+                File root = sourcePath[i];
+                File f = getFile(root, path);
+                if (f.exists()) {
+                    dot
+                            .putResource("OSGI-OPT/src/" + path,
+                                    new FileResource(f));
+                }
+            }
+        }
+    }
+
+    private void doVerify(Jar dot) throws Exception {
+        Verifier verifier = new Verifier(dot, getProperties());
+        verifier.setPedantic(isPedantic());
+        verifier.verify();
+        errors.addAll(verifier.getErrors());
+        warnings.addAll(verifier.getWarnings());
+    }
+
+    private void doExpand(Jar jar) throws IOException {
+        if (classpath.size() == 0
+                && (getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+            warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+        Map prive = replaceWithPattern(getHeader(Analyzer.PRIVATE_PACKAGE));
+        Map export = replaceWithPattern(getHeader(Analyzer.EXPORT_PACKAGE));
+        if (prive.isEmpty() && export.isEmpty()) {
+            warnings
+                    .add("Neither Export-Package nor Private-Package is set, therefore no packages will be included");
+        }
+        doExpand(jar, EXPORT_PACKAGE, export);
+        doExpand(jar, PRIVATE_PACKAGE, prive);
+    }
+
+    private void doExpand(Jar jar, String name, Map instructions) {
+        Set superfluous = new HashSet(instructions.keySet());
+        for (Iterator c = classpath.iterator(); c.hasNext();) {
+            Jar now = (Jar) c.next();
+            doExpand(jar, instructions, now, superfluous);
+        }
+        if (superfluous.size() > 0) {
+            StringBuffer sb = new StringBuffer();
+            String del = "Instructions for " + name + " that are never used: ";
+            for (Iterator i = superfluous.iterator(); i.hasNext();) {
+                Instruction p = (Instruction) i.next();
+                sb.append(del);
+                sb.append(p.getPattern());
+                del = ", ";
+            }
+            warning(sb.toString());
+        }
+    }
+
+    /**
+     * Iterate over each directory in the class path entry and check if that
+     * directory is a desired package.
+     * 
+     * @param included
+     * @param classpathEntry
+     */
+    private void doExpand(Jar jar, Map included, Jar classpathEntry,
+            Set superfluous) {
+
+        loop: for (Iterator p = classpathEntry.getDirectories().entrySet()
+                .iterator(); p.hasNext();) {
+            Map.Entry directory = (Map.Entry) p.next();
+            String path = (String) directory.getKey();
+
+            if (doNotCopy.matcher(getName(path)).matches())
+                continue;
+
+            if (directory.getValue() == null)
+                continue;
+
+            String pack = path.replace('/', '.');
+            Instruction instr = matches(included, pack, superfluous);
+            if (instr != null) {
+                // System.out.println("Pattern match: " + pack + " " +
+                // instr.getPattern() + " " + instr.isNegated());
+                if (!instr.isNegated()) {
+                    Map contents = (Map) directory.getValue();
+
+                    // What to do with split packages? Well if this
+                    // directory already exists, we will check the strategy
+                    // and react accordingly.
+                    boolean overwriteResource = true;
+                    if (jar.hasDirectory(path)) {
+                        Map directives = (Map) included.get(instr);
+
+                        switch (getSplitStrategy((String) directives
+                                .get(SPLIT_PACKAGE_DIRECTIVE))) {
+                        case SPLIT_MERGE_LAST:
+                            overwriteResource = true;
+                            break;
+
+                        case SPLIT_MERGE_FIRST:
+                            overwriteResource = false;
+                            break;
+
+                        case SPLIT_ERROR:
+                            error("Split package generates error: " + pack);
+                            continue loop;
+
+                        case SPLIT_FIRST:
+                            continue loop;
+
+                        default:
+                            // Default is like merge-first, but with a warning
+                            warning("There are split packages, use directive -split-package:=(merge-first|merge-last) on instruction to get rid of this warning: "
+                                    + pack
+                                    + ", classpath: "
+                                    + classpath
+                                    + " from: " + classpathEntry.source);
+                            overwriteResource = false;
+                            break;
+                        }
+                    }
+                    jar.addDirectory(contents, overwriteResource);
+                }
+            }
+        }
+    }
+
+    private int getSplitStrategy(String type) {
+        if (type == null)
+            return SPLIT_DEFAULT;
+
+        if (type.equals("merge-last"))
+            return SPLIT_MERGE_LAST;
+
+        if (type.equals("merge-first"))
+            return SPLIT_MERGE_FIRST;
+
+        if (type.equals("error"))
+            return SPLIT_ERROR;
+
+        if (type.equals("first"))
+            return SPLIT_FIRST;
+
+        error("Invalid strategy for split-package: " + type);
+        return SPLIT_DEFAULT;
+    }
+
+    private Map replaceWithPattern(Map header) {
+        Map map = new LinkedHashMap();
+        for (Iterator e = header.entrySet().iterator(); e.hasNext();) {
+            Map.Entry entry = (Map.Entry) e.next();
+            String pattern = (String) entry.getKey();
+            Instruction instr = Instruction.getPattern(pattern);
+            map.put(instr, entry.getValue());
+        }
+        return map;
+    }
+
+    private Instruction matches(Map instructions, String pack,
+            Set superfluousPatterns) {
+        for (Iterator i = instructions.keySet().iterator(); i.hasNext();) {
+            Instruction pattern = (Instruction) i.next();
+            if (pattern.matches(pack)) {
+                superfluousPatterns.remove(pattern);
+                return pattern;
+            }
+        }
+        return null;
+    }
+
+    private Map getHeader(String string) {
+        if (string == null)
+            return new LinkedHashMap();
+        return parseHeader(getProperty(string));
+    }
+
+    /**
+     * Parse the Bundle-Includes header. Files in the bundles Include header are
+     * included in the jar. The source can be a directory or a file.
+     * 
+     * @throws IOException
+     * @throws FileNotFoundException
+     */
+    private void doIncludeResources(Jar jar) throws Exception {
+        Macro macro = new Macro(getProperties(), this);
+
+        String includes = getProperty("Bundle-Includes");
+        if (includes == null)
+            includes = getProperty("Include-Resource");
+        else
+            warnings
+                    .add("Please use Include-Resource instead of Bundle-Includes");
+
+        if (includes == null)
+            return;
+
+        for (Iterator i = getClauses(includes).iterator(); i.hasNext();) {
+            boolean preprocess = false;
+            String clause = (String) i.next();
+            if (clause.startsWith("{") && clause.endsWith("}")) {
+                preprocess = true;
+                clause = clause.substring(1, clause.length() - 1).trim();
+            }
+
+            Map extra = new HashMap();
+            int n = clause.indexOf(';');
+            if (n > 0) {
+                String attributes = clause.substring(n + 1);
+                String parts[] = attributes.split("\\s*;\\s*");
+                for (int j = 0; j < parts.length; j++) {
+                    String assignment[] = parts[j].split("\\s*=\\s*");
+                    if (assignment.length == 2)
+                        extra.put(assignment[0], assignment[1]);
+                    else
+                        error("Invalid attribute on Include-Resource: "
+                                + clause);
+                }
+                clause = clause.substring(0, n);
+            }
+
+            if (clause.startsWith("@")) {
+                extractFromJar(jar, clause.substring(1));
+            } else {
+                String parts[] = clause.split("\\s*=\\s*");
+
+                String source;
+                File sourceFile;
+                String destinationPath;
+
+                if (parts.length == 1) {
+                    // Just a copy, destination path defined by
+                    // source path.
+                    source = parts[0];
+                    sourceFile = getFile(base, source);
+                    // Directories should be copied to the root
+                    // but files to their file name ...
+                    if (sourceFile.isDirectory())
+                        destinationPath = "";
+                    else
+                        destinationPath = sourceFile.getName();
+                } else {
+                    source = parts[1];
+                    sourceFile = getFile(base, source);
+                    destinationPath = parts[0];
+                }
+
+                // Some people insist on ending a directory with
+                // a slash ... it now also works if you do /=dir
+                if (destinationPath.endsWith("/"))
+                    destinationPath = destinationPath.substring(0,
+                            destinationPath.length() - 1);
+
+                if (!sourceFile.exists()) {
+                    Jar src = getJarFromName(source, "Include-Resource "
+                            + source);
+                    if (src != null) {
+                        JarResource jarResource = new JarResource(src);
+                        jar.putResource(destinationPath, jarResource);
+                    } else {
+                        error("Input file does not exist: " + source);
+                    }
+                } else
+                    copy(jar, destinationPath, sourceFile, preprocess ? macro
+                            : null, extra);
+            }
+        }
+    }
+
+    /**
+     * Extra resources from a Jar and add them to the given jar. The clause is
+     * the
+     * 
+     * @param jar
+     * @param clauses
+     * @param i
+     * @throws ZipException
+     * @throws IOException
+     */
+    private void extractFromJar(Jar jar, String name) throws ZipException,
+            IOException {
+        // Inline all resources and classes from another jar
+        // optionally appended with a modified regular expression
+        // like @zip.jar!/META-INF/MANIFEST.MF
+        int n = name.lastIndexOf("!/");
+        Pattern filter = null;
+        if (n > 0) {
+            String fstring = name.substring(n + 2);
+            name = name.substring(0, n);
+            filter = wildcard(fstring);
+        }
+        Jar sub = getJarFromName(name, "extract from jar");
+        if (sub == null)
+            error("Can not find JAR file " + name);
+        else
+            jar.addAll(sub, filter);
+    }
+
+    private Pattern wildcard(String spec) {
+        StringBuffer sb = new StringBuffer();
+        for (int j = 0; j < spec.length(); j++) {
+            char c = spec.charAt(j);
+            switch (c) {
+            case '.':
+                sb.append("\\.");
+                break;
+
+            case '*':
+                // test for ** (all directories)
+                if (j < spec.length() - 1 && spec.charAt(j + 1) == '*') {
+                    sb.append(".*");
+                    j++;
+                } else
+                    sb.append("[^/]*");
+                break;
+            default:
+                sb.append(c);
+                break;
+            }
+        }
+        String s = sb.toString();
+        try {
+            return Pattern.compile(s);
+        } catch (Exception e) {
+            error("Invalid regular expression on wildcarding: " + spec
+                    + " used *");
+        }
+        return null;
+    }
+
+    private void copy(Jar jar, String path, File from, Macro macro, Map extra)
+            throws Exception {
+        if (doNotCopy.matcher(from.getName()).matches())
+            return;
+
+        if (from.isDirectory()) {
+            String next = path;
+            if (next.length() != 0)
+                next += '/';
+
+            File files[] = from.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                copy(jar, next + files[i].getName(), files[i], macro, extra);
+            }
+        } else {
+            if (from.exists()) {
+                if (macro != null) {
+                    String content = read(from);
+                    content = macro.process(content);
+                    Resource resource = new EmbeddedResource(content
+                            .getBytes("UTF-8"), from.lastModified());
+
+                    String x = (String) extra.get("extra");
+                    if (x != null)
+                        resource.setExtra(x);
+                    jar.putResource(path, resource);
+                } else
+                    jar.putResource(path, new FileResource(from));
+            } else {
+                error("Input file does not exist: " + from);
+            }
+        }
+    }
+
+    private String read(File from) throws Exception {
+        long size = from.length();
+        byte[] buffer = new byte[(int) size];
+        FileInputStream in = new FileInputStream(from);
+        in.read(buffer);
+        in.close();
+        return new String(buffer, "UTF-8");
+    }
+
+    private String getName(String where) {
+        int n = where.lastIndexOf('/');
+        if (n < 0)
+            return where;
+
+        return where.substring(n + 1);
+    }
+
+    public void setSourcepath(File[] files) {
+        sourcePath = files;
+    }
+
+    /**
+     * Create a POM reseource for Maven containing as much information as
+     * possible from the manifest.
+     * 
+     * @param output
+     * @param builder
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void doPom(Jar dot) throws FileNotFoundException, IOException {
+        {
+            Manifest manifest = dot.getManifest();
+            String name = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_NAME);
+            String description = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_DESCRIPTION);
+            String docUrl = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_DOCURL);
+            String version = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_VERSION);
+            String bundleVendor = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_VENDOR);
+            ByteArrayOutputStream s = new ByteArrayOutputStream();
+            PrintStream ps = new PrintStream(s);
+            String bsn = manifest.getMainAttributes().getValue(
+                    Analyzer.BUNDLE_SYMBOLICNAME);
+            String licenses = manifest.getMainAttributes().getValue(
+                    BUNDLE_LICENSE);
+
+            if (bsn == null) {
+                errors
+                        .add("Can not create POM unless Bundle-SymbolicName is set");
+                return;
+            }
+
+            bsn = bsn.trim();
+            int n = bsn.lastIndexOf('.');
+            if (n <= 0) {
+                errors
+                        .add("Can not create POM unless Bundle-SymbolicName contains a .");
+                ps.close();
+                s.close();
+                return;
+            }
+            String groupId = bsn.substring(0, n);
+            String artifactId = bsn.substring(n + 1);
+            ps
+                    .println("<project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd'>");
+            ps.println("  <modelVersion>4.0.0</modelVersion>");
+            ps.println("  <groupId>" + groupId + "</groupId>");
+
+            n = artifactId.indexOf(';');
+            if (n > 0)
+                artifactId = artifactId.substring(0, n).trim();
+
+            ps.println("  <artifactId>" + artifactId + "</artifactId>");
+            ps.println("  <version>" + version + "</version>");
+            if (description != null) {
+                ps.println("  <description>");
+                ps.print("    ");
+                ps.println(description);
+                ps.println("  </description>");
+            }
+            if (name != null) {
+                ps.print("  <name>");
+                ps.print(name);
+                ps.println("</name>");
+            }
+            if (docUrl != null) {
+                ps.print("  <url>");
+                ps.print(docUrl);
+                ps.println("</url>");
+            }
+
+            if (bundleVendor != null) {
+                Matcher m = NAME_URL.matcher(bundleVendor);
+                String namePart = bundleVendor;
+                String urlPart = null;
+                if (m.matches()) {
+                    namePart = m.group(1);
+                    urlPart = m.group(2);
+                }
+                ps.println("  <organization>");
+                ps.print("    <name>");
+                ps.print(namePart.trim());
+                ps.println("</name>");
+                if (urlPart != null) {
+                    ps.print("    <url>");
+                    ps.print(urlPart.trim());
+                    ps.println("</url>");
+                }
+                ps.println("  </organization>");
+            }
+            if (licenses != null) {
+                ps.println("  <licenses>");
+                Map map = parseHeader(licenses);
+                for (Iterator e = map.entrySet().iterator(); e.hasNext();) {
+                    Map.Entry entry = (Map.Entry) e.next();
+                    ps.println("    <license>");
+                    Map values = (Map) entry.getValue();
+                    print(ps, values, "name", "name", (String) values
+                            .get("url"));
+                    print(ps, values, "url", "url", null);
+                    print(ps, values, "distribution", "distribution", "repo");
+                    ps.println("    </license>");
+                }
+                ps.println("  </licenses>");
+            }
+            ps.println("</project>");
+            ps.close();
+            s.close();
+            dot
+                    .putResource("pom.xml", new EmbeddedResource(s
+                            .toByteArray(), 0));
+        }
+    }
+
+    /**
+     * NEW
+     * 
+     */
+    void doPrebuild(Jar dot, Map clauses) {
+        for (Iterator i = clauses.entrySet().iterator(); i.hasNext();) {
+            Map.Entry entry = (Map.Entry) i.next();
+            String name = (String) entry.getKey();
+            Map args = (Map) entry.getValue();
+            File file = getFile(base, name);
+            File dir = file.getParentFile();
+            if (!dir.exists())
+                error("No directory for prebuild: " + file.getAbsolutePath());
+            else {
+                Instruction instr = Instruction.getPattern(file.getName());
+                File[] children = dir.listFiles();
+                for (int c = 0; c < children.length; c++) {
+                    File child = children[c];
+                    if (instr.matches(child.getName()) && !instr.isNegated()) {
+                        try {
+                            progress("Prebuilding "+child);
+                            doPrebuild(dot, child, args);
+                        } catch (Exception e) {
+                            error("Can not build: "+child);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    void doPrebuild(Jar dot, File f, Map args) throws Exception {
+        Builder builder = new Builder();
+        builder.setBase(base);
+        builder.setProperties(f);
+
+        Properties p = new Properties();
+        p.putAll(getProperties());
+        p.keySet().removeAll(builder.getProperties().keySet());
+        builder.getProperties().putAll(p);
+
+        Jar jar = builder.build();
+        String path = (String) args.get("path");
+        if (path != null)
+            path = f.getName();
+
+        dot.putResource(path, new JarResource(jar));
+    }
+
+    /**
+     * Utility function to print a tag from a map
+     * 
+     * @param ps
+     * @param values
+     * @param string
+     * @param tag
+     * @param object
+     */
+    private void print(PrintStream ps, Map values, String string, String tag,
+            String object) {
+        String value = (String) values.get(string);
+        if (value == null)
+            value = object;
+        if (value == null)
+            return;
+        ps.println("    <" + tag + ">" + value.trim() + "</" + tag + ">");
+    }
+
+    public void close() {
+        super.close();
+    }
+}