Package aQute.lib.osgi

Source Code of aQute.lib.osgi.Analyzer

/* 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;

/**
* This class can calculate the required headers for a (potential) JAR file. It
* analyzes a directory or JAR for the packages that are contained and that are
* referred to by the bytecodes. The user can the use regular expressions to
* define the attributes and directives. The matching is not fully regex for
* convenience. A * and ? get a . prefixed and dots are escaped.
*
* <pre>
*                                                                   *;auto=true        any   
*                                                                   org.acme.*;auto=true    org.acme.xyz
*                                                                   org.[abc]*;auto=true    org.acme.xyz
* </pre>
*
* Additional, the package instruction can start with a '=' or a '!'. The '!'
* indicates negation. Any matching package is removed. The '=' is literal, the
* expression will be copied verbatim and no matching will take place.
*
* Any headers in the given properties are used in the output properties.
*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.util.jar.Attributes.*;
import java.util.regex.*;

import aQute.lib.filter.*;

public class Analyzer extends Processor {

    static Pattern                         doNotCopy         = Pattern
                                                                     .compile("CVS|.svn");
    static String                          version;
    static Pattern                         versionPattern    = Pattern
                                                                     .compile("(\\d+\\.\\d+)\\.\\d+.*");
    private Properties                     properties        = new Properties();
    final Map<String, Map<String, String>> contained         = newMap();                              // package
    final Map<String, Map<String, String>> referred          = newMap();                              // package
    final Map<String, Set<String>>         uses              = newMap();                              // package
    Map<String, Clazz>                     classspace;
    Map<String, Map<String, String>>       exports;
    Map<String, Map<String, String>>       imports;
    Map<String, Map<String, String>>       bundleClasspath;                                           // Bundle
    final Map<String, Map<String, String>> ignored           = newMap();                              // Ignored
    // packages
    Jar                                    dot;
    Map<String, Map<String, String>>       cpExports;

    String                                 activator;

    final List<Jar>                        classpath         = newList();

    Macro                                  replacer =  new Macro(properties, this);
;
    long                                   lastModified;
    Manifest                               bndManifest;

    boolean                                noExtraHeaders;
    boolean                                fixedupProperties = false;
    boolean                                analyzed;
    String                                 bsn;
    File                                   propertiesFile;

    /**
     * Specifically for Maven
     *
     * @param properties
     *            the properties
     */

    public static Properties getManifest(File dirOrJar) throws IOException {
        Analyzer analyzer = new Analyzer();
        analyzer.setJar(dirOrJar);
        Properties properties = new Properties();
        properties.put(IMPORT_PACKAGE, "*");
        properties.put(EXPORT_PACKAGE, "*");
        analyzer.setProperties(properties);
        Manifest m = analyzer.calcManifest();
        Properties result = new Properties();
        for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i
                .hasNext();) {
            Attributes.Name name = (Attributes.Name) i.next();
            result.put(name.toString(), m.getMainAttributes().getValue(name));
        }
        return result;
    }

    /**
     * Calcualtes the data structures for generating a manifest.
     *
     * @throws IOException
     */
    public void analyze() throws IOException {
        if (!analyzed) {
            begin();
            analyzed = true;
            cpExports = newMap();
            activator = getProperty(BUNDLE_ACTIVATOR);
            bundleClasspath = parseHeader(getProperty(BUNDLE_CLASSPATH));

            analyzeClasspath();

            classspace = analyzeBundleClasspath(dot, bundleClasspath,
                    contained, referred, uses);

            if (activator != null) {
                // Add the package of the activator to the set
                // of referred classes. This must be done before we remove
                // contained set.
                int n = activator.lastIndexOf('.');
                if (n > 0) {
                    referred.put(activator.substring(0, n),
                            new LinkedHashMap<String, String>());
                }
            }

            referred.keySet().removeAll(contained.keySet());
            if (referred.containsKey(".")) {
                error("The default package '.' is not permitted by the Import-Package syntax. \n"
                        + "The following package(s) import from the default package "
                        + getUsedBy("."));
            }

            Map<String, Map<String, String>> exportInstructions = parseHeader(getProperty(EXPORT_PACKAGE));
            Map<String, Map<String, String>> additionalExportInstructions = parseHeader(getProperty(EXPORT_CONTENTS));
            exportInstructions.putAll(additionalExportInstructions);
            Map<String, Map<String, String>> importInstructions = parseHeader(getImportPackages());
            Map<String, Map<String, String>> dynamicImports = parseHeader(getProperty(DYNAMICIMPORT_PACKAGE));

            if (dynamicImports != null) {
                // Remove any dynamic imports from the referred set.
                referred.keySet().removeAll(dynamicImports.keySet());
            }

            Map<String, Map<String, String>> superfluous = newMap();
            // Tricky!
            for (Iterator<String> i = exportInstructions.keySet().iterator(); i
                    .hasNext();) {
                String instr = i.next();
                if (!instr.startsWith("!"))
                    superfluous.put(instr, exportInstructions.get(instr));
            }

            exports = merge("export-package", exportInstructions, contained,
                    superfluous.keySet());

            for (Iterator<Map.Entry<String, Map<String, String>>> i = superfluous
                    .entrySet().iterator(); i.hasNext();) {
                // It is possible to mention metadata directories in the export
                // explicitly, they are then exported and removed from the
                // warnings. Note that normally metadata directories are not
                // exported.
                Map.Entry<String, Map<String, String>> entry = i.next();
                String pack = entry.getKey();
                if (pack.endsWith(DUPLICATE_MARKER))
                    i.remove();
                else if (isMetaData(pack)) {
                    exports.put(pack, entry.getValue());
                    i.remove();
                }
            }

            if (!superfluous.isEmpty()) {
                warning("Superfluous export-package instructions: "
                        + superfluous.keySet());
            }

            // Add all exports that do not have an -noimport: directive
            // to the imports.
            Map<String, Map<String, String>> referredAndExported = newMap(referred);
            referredAndExported.putAll(addExportsToImports(exports));

            // match the imports to the referred and exported packages,
            // merge the info for matching packages
            Set<String> extra = new TreeSet<String>(importInstructions.keySet());
            imports = merge("import-package", importInstructions,
                    referredAndExported, extra);

            // Instructions that have not been used could be superfluous
            // or if they do not contain wildcards, should be added
            // as extra imports, the user knows best.
            for (Iterator<String> i = extra.iterator(); i.hasNext();) {
                String p = i.next();
                if (p.startsWith("!") || p.indexOf('*') >= 0
                        || p.indexOf('?') >= 0 || p.indexOf('[') >= 0) {
                    if (!isResourceOnly())
                        warning("Did not find matching referal for " + p);
                } else {
                    Map<String, String> map = importInstructions.get(p);
                    imports.put(p, map);
                }
            }

            // See what information we can find to augment the
            // imports. I.e. look on the classpath
            augmentImports();

            // Add the uses clause to the exports
            doUses(exports, uses, imports);
        }
    }

    /**
     * Copy the input collection into an output set but skip names that have
     * been marked as duplicates.
     *
     * @param superfluous
     * @return
     */
    Set<Instruction> removeMarkedDuplicates(Collection<Instruction> superfluous) {
        Set<Instruction> result = new HashSet<Instruction>();
        for (Iterator<Instruction> i = superfluous.iterator(); i.hasNext();) {
            Instruction instr = (Instruction) i.next();
            if (!instr.getPattern().endsWith(DUPLICATE_MARKER))
                result.add(instr);
        }
        return result;
    }

    /**
     * Analyzer has an empty default but the builder has a * as default.
     *
     * @return
     */
    protected String getImportPackages() {
        return getProperty(IMPORT_PACKAGE);
    }

    /**
     *
     * @return
     */
    boolean isResourceOnly() {
        return getProperty(RESOURCEONLY, "false").equalsIgnoreCase("true");
    }

    /**
     * Answer the list of packages that use the given package.
     */
    Set<String> getUsedBy(String pack) {
        Set<String> set = newSet();
        for (Iterator<Map.Entry<String, Set<String>>> i = uses.entrySet()
                .iterator(); i.hasNext();) {
            Map.Entry<String, Set<String>> entry = i.next();
            Set<String> used = entry.getValue();
            if (used.contains(pack))
                set.add(entry.getKey());
        }
        return set;
    }

    /**
     * One of the main workhorses of this class. This will analyze the current
     * setp and calculate a new manifest according to this setup. This method
     * will also set the manifest on the main jar dot
     *
     * @return
     * @throws IOException
     */
    public Manifest calcManifest() throws IOException {
        analyze();
        Manifest manifest = new Manifest();
        Attributes main = manifest.getMainAttributes();

        main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        main.putValue(BUNDLE_MANIFESTVERSION, "2");

        if (!noExtraHeaders) {
            main.putValue(CREATED_BY, System.getProperty("java.version") + " ("
                    + System.getProperty("java.vendor") + ")");
            main.putValue(TOOL, "Bnd-" + getVersion());
            main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
        }
        String exportHeader = printClauses(exports,
                "uses:|include:|exclude:|mandatory:", true);

        if (exportHeader.length() > 0)
            main.putValue(EXPORT_PACKAGE, exportHeader);
        else
            main.remove(EXPORT_PACKAGE);

        Map<String, Map<String, String>> temp = removeKeys(imports, "java.");
        if (!temp.isEmpty()) {
            main.putValue(IMPORT_PACKAGE, printClauses(temp, "resolution:"));
        } else {
            main.remove(IMPORT_PACKAGE);
        }

        temp = newMap(contained);
        temp.keySet().removeAll(exports.keySet());

        if (!temp.isEmpty())
            main.putValue(PRIVATE_PACKAGE, printClauses(temp, ""));
        else
            main.remove(PRIVATE_PACKAGE);

        if (!ignored.isEmpty()) {
            main.putValue(IGNORE_PACKAGE, printClauses(ignored, ""));
        } else {
            main.remove(IGNORE_PACKAGE);
        }

        if (bundleClasspath != null && !bundleClasspath.isEmpty())
            main.putValue(BUNDLE_CLASSPATH, printClauses(bundleClasspath, ""));
        else
            main.remove(BUNDLE_CLASSPATH);

        Map<String, Map<String, String>> l = doServiceComponent(getProperty(SERVICE_COMPONENT));
        if (!l.isEmpty())
            main.putValue(SERVICE_COMPONENT, printClauses(l, ""));
        else
            main.remove(SERVICE_COMPONENT);

        for (Enumeration<?> h = getProperties().propertyNames(); h
                .hasMoreElements();) {
            String header = (String) h.nextElement();
            if (header.trim().length() == 0) {
                warning("Empty property set with value: "
                        + getProperties().getProperty(header));
                continue;
            }
            if (!Character.isUpperCase(header.charAt(0)))
                continue;

            if (header.equals(BUNDLE_CLASSPATH)
                    || header.equals(EXPORT_PACKAGE)
                    || header.equals(IMPORT_PACKAGE))
                continue;

            if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
                String value = getProperty(header);
                if (value != null && main.getValue(header) == null) {
                    if (value.trim().length() == 0)
                        main.remove(header);
                    else
                        main.putValue(header, value);
                }
            } else {
                // TODO should we report?
            }
        }

        //
        // Calculate the bundle symbolic name if it is
        // not set.
        // 1. set
        // 2. name of properties file (must be != bnd.bnd)
        // 3. name of directory, which is usualy project name
        //
        String bsn = getBsn();
        if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
            main.putValue(BUNDLE_SYMBOLICNAME, bsn);
        }

        //
        // Use the same name for the bundle name as BSN when
        // the bundle name is not set
        //
        if (main.getValue(BUNDLE_NAME) == null) {
            main.putValue(BUNDLE_NAME, bsn);
        }

        if (main.getValue(BUNDLE_VERSION) == null)
            main.putValue(BUNDLE_VERSION, "0");

        // Copy old values into new manifest, when they
        // exist in the old one, but not in the new one
        merge(manifest, dot.getManifest());

        // Remove all the headers mentioned in -removeheaders
        Map<String, Map<String, String>> removes = parseHeader(getProperty(REMOVE_HEADERS));
        for (Iterator<String> i = removes.keySet().iterator(); i.hasNext();) {
            String header = i.next();
            for (Iterator<Object> j = main.keySet().iterator(); j.hasNext();) {
                Attributes.Name attr = (Attributes.Name) j.next();
                if (attr.toString().matches(header)) {
                    j.remove();
                    progress("Removing header: " + header);
                }
            }
        }

        dot.setManifest(manifest);
        return manifest;
    }

    /**
     * Clear the key part of a header. I.e. remove everything from the first ';'
     *
     * @param value
     * @return
     */
    public String getBsn() {
        String value = getProperty(BUNDLE_SYMBOLICNAME);
        if (value == null) {
            if (propertiesFile != null)
                value = propertiesFile.getName();

            if (value == null || value.equals("bnd.bnd"))
                value = getBase().getName();
            else if (value.endsWith(".bnd"))
                value = value.substring(0, value.length() - 4);
        }

        if (value == null)
            return "untitled";

        int n = value.indexOf(';');
        if (n > 0)
            value = value.substring(0, n);
        return value.trim();
    }

    /**
     * Calculate an export header solely based on the contents of a JAR file
     *
     * @param bundle
     *            The jar file to analyze
     * @return
     */
    public String calculateExportsFromContents(Jar bundle) {
        String ddel = "";
        StringBuffer sb = new StringBuffer();
        Map<String, Map<String, Resource>> map = bundle.getDirectories();
        for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
            String directory = (String) i.next();
            if (directory.equals("META-INF")
                    || directory.startsWith("META-INF/"))
                continue;
            if (directory.equals("OSGI-OPT")
                    || directory.startsWith("OSGI-OPT/"))
                continue;
            if (directory.equals("/"))
                continue;

            if (directory.endsWith("/"))
                directory = directory.substring(0, directory.length() - 1);

            directory = directory.replace('/', '.');
            sb.append(ddel);
            sb.append(directory);
            ddel = ",";
        }
        return sb.toString();
    }

    /**
     * Check if a service component header is actually referring to a class. If
     * so, replace the reference with an XML file reference. This makes it
     * easier to create and use components.
     *
     * @throws UnsupportedEncodingException
     *
     */
    public Map<String, Map<String, String>> doServiceComponent(
            String serviceComponent) throws IOException {
        Map<String, Map<String, String>> list = newMap();
        Map<String, Map<String, String>> sc = parseHeader(serviceComponent);
        if (!sc.isEmpty()) {
            for (Iterator<Map.Entry<String, Map<String, String>>> i = sc
                    .entrySet().iterator(); i.hasNext();) {
                Map.Entry<String, Map<String, String>> entry = i.next();
                String name = entry.getKey();
                Map<String, String> info = entry.getValue();
                if (name == null) {
                    error("No name in Service-Component header: " + info);
                    continue;
                }
                if (dot.exists(name)) {
                    // Normal service component
                    list.put(name, info);
                } else {
                    if (!checkClass(name))
                        error("Not found Service-Component header: " + name);
                    else {
                        // We have a definition, so make an XML resources
                        Resource resource = createComponentResource(name, info);
                        dot.putResource("OSGI-INF/" + name + ".xml", resource);
                        list.put("OSGI-INF/" + name + ".xml",
                                new HashMap<String, String>());
                    }
                }
            }
        }
        return list;
    }

    public Map<String, Map<String, String>> getBundleClasspath() {
        return bundleClasspath;
    }

    public Map<String, Map<String, String>> getContained() {
        return contained;
    }

    public Map<String, Map<String, String>> getExports() {
        return exports;
    }

    public Map<String, Map<String, String>> getImports() {
        return imports;
    }

    public Jar getJar() {
        return dot;
    }

    public Properties getProperties() {
        if (!fixedupProperties)
            begin();

        return properties;
    }

    public String getProperty(String headerName) {
        String value = getProperties().getProperty(headerName);
        if (value != null)
            return replacer.process(value);
        else
            return null;
    }

    public Map<String, Map<String, String>> getReferred() {
        return referred;
    }

    /**
     * Return the set of unreachable code depending on exports and the bundle
     * activator.
     *
     * @return
     */
    public Set<String> getUnreachable() {
        Set<String> unreachable = new HashSet<String>(uses.keySet()); // all
        for (Iterator<String> r = exports.keySet().iterator(); r.hasNext();) {
            String packageName = r.next();
            removeTransitive(packageName, unreachable);
        }
        if (activator != null) {
            String pack = activator.substring(0, activator.lastIndexOf('.'));
            removeTransitive(pack, unreachable);
        }
        return unreachable;
    }

    public Map<String, Set<String>> getUses() {
        return uses;
    }

    /**
     * Get the version from the manifest, a lot of work!
     *
     * @return version or unknown.
     */
    public String getVersion() {
        getBndManifest();
        String version = null;
        if (bndManifest != null)
            version = (String) bndManifest.getMainAttributes().getValue(
                    BUNDLE_VERSION);
        if (version != null)
            return version;
        return "unknown version";
    }

    public long getBndLastModified() {
        getBndManifest();
        if (bndManifest != null) {
            String v = bndManifest.getMainAttributes().getValue(
                    BND_LASTMODIFIED);
            if (v != null)
                try {
                    return Long.parseLong(v);
                } catch (Exception e) {
                    warning(BND_LASTMODIFIED
                            + " header of bnd jar is not a long " + v);
                }
        }
        if (isPedantic())
            warning("Can not find manifest for bnd program, assuming it is not modified");
        return 0;
    }

    public void getBndManifest() {
        if (bndManifest != null)
            return;

        try {
            Enumeration<?> e = getClass().getClassLoader().getResources(
                    "META-INF/MANIFEST.MF");
            while (e.hasMoreElements()) {
                URL url = (URL) e.nextElement();
                InputStream in = url.openStream();
                Manifest manifest = new Manifest(in);
                in.close();
                if (manifest != null) {
                    String bsn = manifest.getMainAttributes().getValue(
                            BUNDLE_SYMBOLICNAME);
                    if (bsn != null && bsn.indexOf("biz.aQute.bnd") >= 0) {
                        bndManifest = manifest;
                        return;
                    }
                }
            }
        } catch (IOException e) {
            // Well, too bad
            warning("bnd jar file is corrupted, can not find manifest " + e);
            return;
        }
        // Looking again has no purpose, so use an
        // empty manifest.
        bndManifest = new Manifest();
    }

    /**
     * Merge the existing manifest with the instructions.
     *
     * @param manifest
     *            The manifest to merge with
     * @throws IOException
     */
    public void mergeManifest(Manifest manifest) throws IOException {
        if (manifest != null) {
            Attributes attributes = manifest.getMainAttributes();
            for (Iterator<Object> i = attributes.keySet().iterator(); i
                    .hasNext();) {
                Name name = (Name) i.next();
                String key = name.toString();
                // Dont want instructions
                if (key.startsWith("-"))
                    continue;

                if (getProperty(key) == null)
                    setProperty(key, (String) attributes.get(name));
            }
        }
    }

    // public Signer getSigner() {
    // String sign = getProperty("-sign");
    // if (sign == null) return null;
    //
    // Map parsed = parseHeader(sign);
    // Signer signer = new Signer();
    // String password = (String) parsed.get("password");
    // if (password != null) {
    // signer.setPassword(password);
    // }
    //
    // String keystore = (String) parsed.get("keystore");
    // if (keystore != null) {
    // File f = new File(keystore);
    // if (!f.isAbsolute()) f = new File(base, keystore);
    // signer.setKeystore(f);
    // } else {
    // error("Signing requires a keystore");
    // return null;
    // }
    //
    // String alias = (String) parsed.get("alias");
    // if (alias != null) {
    // signer.setAlias(alias);
    // } else {
    // error("Signing requires an alias for the key");
    // return null;
    // }
    // return signer;
    // }

    public void setBase(File file) {
        super.setBase(file);
        getProperties().put("project.dir", getBase().getAbsolutePath());
    }

    /**
     * Set the classpath for this analyzer by file.
     *
     * @param classpath
     * @throws IOException
     */
    public void setClasspath(File[] classpath) throws IOException {
        List<Jar> list = new ArrayList<Jar>();
        for (int i = 0; i < classpath.length; i++) {
            if (classpath[i].exists()) {
                Jar current = new Jar(classpath[i]);
                list.add(current);
            } else {
                error("Missing file on classpath: " + classpath[i]);
            }
        }
        for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
            addClasspath(i.next());
        }
    }

    public void setClasspath(Jar[] classpath) {
        for (int i = 0; i < classpath.length; i++) {
            addClasspath(classpath[i]);
        }
    }

    public void setClasspath(String[] classpath) {
        for (int i = 0; i < classpath.length; i++) {
            Jar jar = getJarFromName(classpath[i], " setting classpath");
            if (jar != null)
                addClasspath(jar);
        }
    }

    /**
     * Set the JAR file we are going to work in. This will read the JAR in
     * memory.
     *
     * @param jar
     * @return
     * @throws IOException
     */
    public Jar setJar(File jar) throws IOException {
        Jar jarx = new Jar(jar);
        addClose(jarx);
        return setJar(jarx);
    }

    /**
     * Set the JAR directly we are going to work on.
     *
     * @param jar
     * @return
     */
    public Jar setJar(Jar jar) {
        this.dot = jar;
        return jar;
    }

    /**
     * Set the properties by file. Setting the properties this way will also set
     * the base for this analyzer. After reading the properties, this will call
     * setProperties(Properties) which will handle the includes.
     *
     * @param propertiesFile
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void setProperties(File propertiesFile)
            throws FileNotFoundException, IOException {
        setProperties(propertiesFile, propertiesFile.getParentFile());
    }

    public void setProperties(File propertiesFile, File base)
            throws FileNotFoundException, IOException {
        this.propertiesFile = propertiesFile.getAbsoluteFile();

        setBase(base);

        doBuildProperties();

        setProperties(loadProperties(propertiesFile));
        fixedupProperties = false;
        setProperty("project.file", propertiesFile.getAbsolutePath());
        String project = propertiesFile.getParentFile().getName();
        if (getProperty("p") == null) {
            setProperty("p", project);
        }
    }

    public void doBuildProperties() throws IOException {
        String home = System.getProperty("aQute.bnd.cnf",
                "../cnf/build.bld");

        File build = getFile(getBase(), home);
        if (build.isFile() && build.canRead()) {
            setProperties(loadProperties(build));
        }
    }

    public void mergeProperties(File file, boolean override) {
        if (file.isFile()) {
            try {
                Properties properties = loadProperties(file);
                mergeProperties(properties, override);
            } catch (Exception e) {
                error("Error loading properties file: " + file);
            }
        } else {
            if (!file.exists())
                error("Properties file does not exist: " + file);
            else
                error("Properties file must a file, not a directory: " + file);
        }
    }

    public void mergeProperties(Properties properties, boolean override) {
        if (override)
            this.properties = merge(this.properties, properties);
        else
            this.properties = merge(properties, this.properties);
        fixedupProperties = false;
    }

    Properties merge(Properties a, Properties b) {
        Properties result = new Properties();
        result.putAll(a);
        result.putAll(b);
        return result;
    }

    public void setProperties(Properties properties) {
        doPropertyIncludes(getBaseURL(), properties, new HashSet<String>());
        this.properties.putAll(properties);
        fixedupProperties = false;
    }

    void begin() {
        if (fixedupProperties)
            return;
        fixedupProperties = true;

        String doNotCopy = getProperty(DONOTCOPY);
        if (doNotCopy != null)
            Analyzer.doNotCopy = Pattern.compile(doNotCopy);

        verifyManifestHeadersCase(properties);

        if ("true".equalsIgnoreCase(getProperty(PEDANTIC)))
            setPedantic(true);
        noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
    }

    private URL getBaseURL() {
        try {
            return getBase().toURL();
        } catch (Exception e) {
            // who cares, can not happen
        }
        return null;
    }

    /**
     * Add or override a new property.
     *
     * @param key
     * @param value
     */
    public void setProperty(String key, String value) {
        checkheader: for (int i = 0; i < headers.length; i++) {
            if (headers[i].equalsIgnoreCase(value)) {
                value = headers[i];
                break checkheader;
            }
        }
        getProperties().put(key, value);
    }

    /**
     * Check if the given class or interface name is contained in the jar.
     *
     * @param interfaceName
     * @return
     */
    boolean checkClass(String interfaceName) {
        String path = interfaceName.replace('.', '/') + ".class";
        if (classspace.containsKey(path))
            return true;

        String pack = interfaceName;
        int n = pack.lastIndexOf('.');
        if (n > 0)
            pack = pack.substring(0, n);
        else
            pack = ".";

        return imports.containsKey(pack);
    }

    /**
     * Create the resource for a DS component.
     *
     * @param list
     * @param name
     * @param info
     * @throws UnsupportedEncodingException
     */
    Resource createComponentResource(String name, Map<String, String> info)
            throws IOException {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
        pw.println("<?xml version='1.0' encoding='utf-8'?>");
        pw.print("<component name='" + name + "'");

        String factory = info.get("factory:");
        if (factory != null)
            pw.print(" factory='" + factory + "'");

        String immediate = info.get("immediate:");
        if (immediate != null)
            pw.print(" immediate='" + immediate + "'");

        String enabled = info.get("enabled:");
        if (enabled != null)
            pw.print(" enabled='" + enabled + "'");

        pw.println(">");
        pw.println("  <implementation class='" + name + "'/>");
        String provides = info.get("provide:");
        boolean servicefactory = Boolean.getBoolean(info.get("servicefactory:")
                + "");
        provides(pw, provides, servicefactory);
        properties(pw, info);
        reference(info, pw);
        pw.println("</component>");
        pw.close();
        byte[] data = out.toByteArray();
        out.close();
        return new EmbeddedResource(data, 0);
    }

    /**
     * Try to get a Jar from a file name/path or a url, or in last resort from
     * the classpath name part of their files.
     *
     * @param name
     *            URL or filename relative to the base
     * @param from
     *            Message identifying the caller for errors
     * @return null or a Jar with the contents for the name
     */
    Jar getJarFromName(String name, String from) {
        File file = new File(name);
        if (!file.isAbsolute())
            file = new File(getBase(), name);

        if (file.exists())
            try {
                Jar jar = new Jar(file);
                addClose(jar);
                return jar;
            } catch (Exception e) {
                error("Exception in parsing jar file for " + from + ": " + name
                        + " " + e);
            }
        // It is not a file ...
        try {
            // Lets try a URL
            URL url = new URL(name);
            Jar jar = new Jar(fileName(url.getPath()));
            addClose(jar);
            URLConnection connection = url.openConnection();
            InputStream in = connection.getInputStream();
            long lastModified = connection.getLastModified();
            if (lastModified == 0)
                // We assume the worst :-(
                lastModified = System.currentTimeMillis();
            EmbeddedResource.build(jar, in, lastModified);
            in.close();
            return jar;
        } catch (IOException ee) {
            // Check if we have files on the classpath
            // that have the right name, allows us to specify those
            // names instead of the full path.
            for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
                Jar entry = cp.next();
                if (entry.source != null && entry.source.getName().equals(name)) {
                    return entry;
                }
            }
            // error("Can not find jar file for " + from + ": " + name);
        }
        return null;
    }

    private String fileName(String path) {
        int n = path.lastIndexOf('/');
        if (n > 0)
            return path.substring(n + 1);
        return path;
    }

    /**
     * Read a manifest but return a properties object.
     *
     * @param in
     * @return
     * @throws IOException
     */
    Properties getManifestAsProperties(InputStream in) throws IOException {
        Properties p = new Properties();
        Manifest manifest = new Manifest(in);
        for (Iterator<Object> it = manifest.getMainAttributes().keySet()
                .iterator(); it.hasNext();) {
            Attributes.Name key = (Attributes.Name) it.next();
            String value = manifest.getMainAttributes().getValue(key);
            p.put(key.toString(), value);
        }
        return p;
    }

    /**
     * Helper routine to create a set of a comma separated string.
     *
     * @param list
     * @return
     */
    List<String> getClauses(String list) {
        if (list == null)
            return new ArrayList<String>();
        list = list.trim();
        String[] parts = list.split("\\s*,\\s*");
        return Arrays.asList(parts);
    }

    /**
     *
     * @param manifest
     * @throws Exception
     */
    void merge(Manifest result, Manifest old) throws IOException {
        if (old != null) {
            for (Iterator<Map.Entry<Object, Object>> e = old
                    .getMainAttributes().entrySet().iterator(); e.hasNext();) {
                Map.Entry<Object, Object> entry = e.next();
                Attributes.Name name = (Attributes.Name) entry.getKey();
                String value = (String) entry.getValue();
                if (name.toString().equalsIgnoreCase("Created-By"))
                    name = new Attributes.Name("Originally-Created-By");
                if (!result.getMainAttributes().containsKey(name))
                    result.getMainAttributes().put(name, value);
            }

            // do not overwrite existing entries
            Map<String, Attributes> oldEntries = old.getEntries();
            Map<String, Attributes> newEntries = result.getEntries();
            for (Iterator<Map.Entry<String, Attributes>> e = oldEntries
                    .entrySet().iterator(); e.hasNext();) {
                Map.Entry<String, Attributes> entry = e.next();
                if (!newEntries.containsKey(entry.getKey())) {
                    newEntries.put(entry.getKey(), entry.getValue());
                }
            }
        }
    }

    /**
     * Print the Service-Component properties element
     *
     * @param pw
     * @param info
     */
    void properties(PrintWriter pw, Map<String, String> info) {
        List<String> properties = getClauses(info.get("properties:"));
        for (Iterator<String> p = properties.iterator(); p.hasNext();) {
            String clause = p.next();
            int n = clause.indexOf('=');
            if (n <= 0) {
                error("Not a valid property in service component: " + clause);
            } else {
                String type = null;
                String name = clause.substring(0, n);
                if (name.indexOf('@') >= 0) {
                    String parts[] = name.split("@");
                    name = parts[1];
                    type = parts[0];
                }
                String value = clause.substring(n + 1).trim();
                // TODO verify validity of name and value.
                pw.print("<property name='");
                pw.print(name);
                pw.print("'");

                if (type != null) {
                    if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
                        pw.print(" type='");
                        pw.print(type);
                        pw.print("'");
                    } else {
                        warning("Invalid property type '" + type
                                + "' for property " + name);
                    }
                }

                String parts[] = value.split("\\s*(\\||\\n)\\s*");
                if (parts.length> 1) {
                    pw.println(">");
                    for ( String part : parts ) {
                        pw.println(part);
                    }                   
                    pw.println("</property>");
                } else {
                    pw.print(" value='");
                    pw.print(parts[0]);
                    pw.print("'/>");
                }
            }
        }
    }

    /**
     * @param pw
     * @param provides
     */
    void provides(PrintWriter pw, String provides, boolean servicefactory) {
        if (provides != null) {
            if (!servicefactory)
                pw.println("  <service>");
            else
                pw.println("  <service servicefactory='true'>");

            StringTokenizer st = new StringTokenizer(provides, ",");
            while (st.hasMoreTokens()) {
                String interfaceName = st.nextToken();
                pw.println("    <provide interface='" + interfaceName + "'/>");
                if (!checkClass(interfaceName))
                    error("Component definition provides a class that is neither imported nor contained: "
                            + interfaceName);
            }
            pw.println("  </service>");
        }
    }

    final static Pattern REFERENCE = Pattern.compile("([^(]+)(\\(.+\\))?");

    /**
     * @param info
     * @param pw
     */

    void reference(Map<String, String> info, PrintWriter pw) {
        List<String> dynamic = getClauses((String) info.get("dynamic:"));
        List<String> optional = getClauses((String) info.get("optional:"));
        List<String> multiple = getClauses((String) info.get("multiple:"));

        for (Iterator<Map.Entry<String, String>> r = info.entrySet().iterator(); r
                .hasNext();) {
            Map.Entry<String, String> ref = r.next();
            String referenceName = (String) ref.getKey();
            String target = null;
            String interfaceName = (String) ref.getValue();
            if (interfaceName == null || interfaceName.length() == 0) {
                error("Invalid Interface Name for references in Service Component: "
                        + referenceName + "=" + interfaceName);
            }
            char c = interfaceName.charAt(interfaceName.length() - 1);
            if ("?+*~".indexOf(c) >= 0) {
                if (c == '?' || c == '*' || c == '~')
                    optional.add(referenceName);
                if (c == '+' || c == '*')
                    multiple.add(referenceName);
                if (c == '+' || c == '*' || c == '?')
                    dynamic.add(referenceName);
                interfaceName = interfaceName.substring(0, interfaceName
                        .length() - 1);
            }

            // TODO check if the interface is contained or imported

            if (referenceName.endsWith(":")) {
                if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
                    error("Unrecognized directive in Service-Component header: "
                            + referenceName);
                continue;
            }

            Matcher m = REFERENCE.matcher(interfaceName);
            if (m.matches()) {
                interfaceName = m.group(1);
                target = m.group(2);
            }

            if (!checkClass(interfaceName))
                error("Component definition refers to a class that is neither imported nor contained: "
                        + interfaceName);

            pw.print("  <reference name='" + referenceName + "' interface='"
                    + interfaceName + "'");

            String cardinality = optional.contains(referenceName) ? "0" : "1";
            cardinality += "..";
            cardinality += multiple.contains(referenceName) ? "n" : "1";
            if (!cardinality.equals("1..1"))
                pw.print(" cardinality='" + cardinality + "'");

            if (Character.isLowerCase(referenceName.charAt(0))) {
                String z = referenceName.substring(0, 1).toUpperCase()
                        + referenceName.substring(1);
                pw.print(" bind='set" + z + "'");
                // TODO Verify that the methods exist

                // TODO ProSyst requires both a bind and unbind :-(
                // if ( dynamic.contains(referenceName) )
                pw.print(" unbind='unset" + z + "'");
                // TODO Verify that the methods exist
            }
            if (dynamic.contains(referenceName)) {
                pw.print(" policy='dynamic'");
            }

            if (target != null) {
                Filter filter = new Filter(target);
                if (filter.verify() == null)
                    pw.print(" target='" + filter.toString() + "'");
                else
                    error("Target for " + referenceName
                            + " is not a correct filter: " + target + " "
                            + filter.verify());
            }
            pw.println("/>");
        }
    }

    String stem(String name) {
        int n = name.lastIndexOf('.');
        if (n > 0)
            return name.substring(0, n);
        else
            return name;
    }

    /**
     * Bnd is case sensitive for the instructions so we better check people are
     * not using an invalid case. We do allow this to set headers that should
     * not be processed by us but should be used by the framework.
     *
     * @param properties
     *            Properties to verify.
     */

    void verifyManifestHeadersCase(Properties properties) {
        for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
            String header = (String) i.next();
            for (int j = 0; j < headers.length; j++) {
                if (!headers[j].equals(header)
                        && headers[j].equalsIgnoreCase(header)) {
                    warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
                            + header + " and expecting: " + headers[j]);
                    break;
                }
            }
        }
    }

    /**
     * We will add all exports to the imports unless there is a -noimport
     * directive specified on an export. This directive is skipped for the
     * manifest.
     *
     */
    Map<String, Map<String, String>> addExportsToImports(
            Map<String, Map<String, String>> exports) {
        Map<String, Map<String, String>> importsFromExports = newMap();
        for (Iterator<Map.Entry<String, Map<String, String>>> export = exports
                .entrySet().iterator(); export.hasNext();) {
            Map.Entry<String, Map<String, String>> entry = export.next();
            String packageName = entry.getKey();
            Map<String, String> parameters = entry.getValue();
            String noimport = (String) parameters.get(NO_IMPORT_DIRECTIVE);
            if (noimport == null || !noimport.equalsIgnoreCase("true")) {
                Map<String, String> importParameters = importsFromExports
                        .get(packageName);
                if (importParameters == null)
                    importsFromExports.put(packageName, parameters);
            }
        }
        return importsFromExports;
    }

    /**
     * Create the imports/exports by parsing
     *
     * @throws IOException
     */
    void analyzeClasspath() throws IOException {
        cpExports = newMap();
        for (Iterator<Jar> c = getClasspath().iterator(); c.hasNext();) {
            Jar current = c.next();
            checkManifest(current);
            for (Iterator<String> j = current.getDirectories().keySet()
                    .iterator(); j.hasNext();) {
                String dir = j.next();
                Resource resource = current.getResource(dir + "/packageinfo");
                if (resource != null) {
                    InputStream in = resource.openInputStream();
                    String version = parsePackageInfo(in);
                    in.close();
                    setPackageInfo(dir, "version", version);
                }
            }
        }
    }

    /**
     *
     * @param jar
     */
    void checkManifest(Jar jar) {
        try {
            Manifest m = jar.getManifest();
            if (m != null) {
                String exportHeader = m.getMainAttributes().getValue(
                        EXPORT_PACKAGE);
                if (exportHeader != null) {
                    Map<String, Map<String, String>> exported = parseHeader(exportHeader);
                    if (exported != null)
                        cpExports.putAll(exported);
                }
            }
        } catch (Exception e) {
            warning("Erroneous Manifest for " + jar + " " + e);
        }
    }

    /**
     * Find some more information about imports in manifest and other places.
     */
    void augmentImports() {
        for (Iterator<String> imp = imports.keySet().iterator(); imp.hasNext();) {
            String packageName = imp.next();
            Map<String, String> currentAttributes = imports.get(packageName);
            Map<String, String> exporter = cpExports.get(packageName);
            if (exporter != null) {
                augmentVersion(currentAttributes, exporter);
                augmentMandatory(currentAttributes, exporter);
            }

            String remove = currentAttributes.get("remove-attribute:");
            Instruction removeInstr = null;

            if (remove != null)
                removeInstr = Instruction.getPattern(remove);

            for (Iterator<Map.Entry<String, String>> i = currentAttributes
                    .entrySet().iterator(); i.hasNext();) {
                Map.Entry<String, String> entry = i.next();
                if (entry.getValue().equals("!"))
                    i.remove();

                if (removeInstr != null
                        && removeInstr.matches((String) entry.getKey()))
                    i.remove();
            }
        }
    }

    /**
     * If we use an import with mandatory attributes we better all use them
     *
     * @param currentAttributes
     * @param exporter
     */
    private void augmentMandatory(Map<String, String> currentAttributes,
            Map<String, String> exporter) {
        String mandatory = (String) exporter.get("mandatory:");
        if (mandatory != null) {
            String[] attrs = mandatory.split("\\s*,\\s*");
            for (int i = 0; i < attrs.length; i++) {
                if (!currentAttributes.containsKey(attrs[i]))
                    currentAttributes.put(attrs[i], exporter.get(attrs[i]));
            }
        }
    }

    /**
     * Check if we can augment the version from the exporter.
     *
     * We allow the version in the import to specify a @ which is replaced with
     * the exporter's version.
     *
     * @param currentAttributes
     * @param exporter
     */
    private void augmentVersion(Map<String, String> currentAttributes,
            Map<String, String> exporter) {
        String currentVersion = currentAttributes.get("version");
        if ( currentVersion != null ) {
            currentVersion = cleanupVersion(currentVersion);
            currentAttributes.put("version", currentVersion);
        }
       
        if (currentVersion == null || currentVersion.indexOf("${") >= 0) {
            // See if we can borrow the version
            String exportVersion = (String) exporter.get("version");
            if (exportVersion == null)
                exportVersion = (String) exporter.get("specification-version");
            if (exportVersion != null) {
                exportVersion = cleanupVersion(exportVersion);
                if (currentVersion != null) {
                    // we mist replace the ${@} with the version we
                    // found this can be useful if you want a range to start
                    // with the found version.
                    setProperty("@", exportVersion);
                    exportVersion = replacer.process(currentVersion);
                    unsetProperty("@");
                } else {
                    // We remove the micro part of the version
                    // to a bit more lenient
                    Matcher m = versionPattern.matcher(exportVersion);
                    if (m.matches())
                        exportVersion = m.group(1);
                }
                currentAttributes.put("version", exportVersion);
            }
        }
    }

    public void unsetProperty(String string) {
        getProperties().remove(string);

    }

    /**
     * Inspect the properties and if you find -includes parse the line included
     * manifest files or propertie files. The files are relative from the given
     * base, this is normally the base for the analyzer.
     *
     * @param ubase
     * @param p
     * @param done
     * @throws IOException
     */
    private void doPropertyIncludes(URL ubase, Properties p, Set<String> done) {
        String includes = p.getProperty(INCLUDE);
        if (includes != null) {
            includes = replacer.process(includes);
           
            List<String> clauses = getClauses(includes);

            outer: for (Iterator<String> i = clauses.iterator(); i.hasNext();) {
                String value = i.next();
                boolean fileMustExist = true;
                boolean overwrite = true;
                while (true) {
                    if (value.startsWith("-")) {
                        fileMustExist = false;
                        value = value.substring(1).trim();
                    } else if (value.startsWith("~")) {
                        // Overwrite properties!
                        overwrite = false;
                        value = value.substring(1).trim();
                    } else
                        break;
                }
                try {
                    URL next = null;
                    try {
                        next = new URL(ubase, value);
                    } catch (MalformedURLException e) {

                        File f = new File(value);
                        if (!f.isAbsolute())
                            f = getFile(value);
                        if (f.exists()) {
                            next = f.getAbsoluteFile().toURL();
                            updateModified(f.lastModified());
                        } else {
                            if (fileMustExist)
                                error("Can not find include file: " + value);
                            continue outer;
                        }
                    }
                    String urlString = next.toExternalForm();
                    if (done.contains(urlString))
                        return;
                    done.add(urlString);

                    URLConnection connection = next.openConnection();
                    long time = connection.getLastModified();
                    if (time > 0)
                        updateModified(time);
                    InputStream in = connection.getInputStream();
                    Properties sub;
                    if (next.getFile().toLowerCase().endsWith(".mf")) {
                        sub = getManifestAsProperties(in);
                    } else
                        sub = loadProperties(in, next.toExternalForm());
                    doPropertyIncludes(next, sub, done);
                    in.close();

                    // make sure we do not override properties
                    if (!overwrite)
                        sub.keySet().removeAll(p.keySet());
                    p.putAll(sub);
                } catch (FileNotFoundException e) {
                    if (fileMustExist)
                        error("Can not find included file: " + value);
                } catch (IOException e) {
                    if (fileMustExist)
                        error("Error in processing included file: " + value
                                + "(" + e + ")");
                }
            }
        }
    }


    /**
     * Add the uses clauses
     *
     * @param exports
     * @param uses
     * @throws MojoExecutionException
     */
    void doUses(Map<String, Map<String, String>> exports,
            Map<String, Set<String>> uses,
            Map<String, Map<String, String>> imports) {
        if ("true".equalsIgnoreCase(getProperty(NOUSES)))
            return;

        for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
            String packageName = i.next();
            Map<String, String> clause = exports.get(packageName);
            String override = clause.get("uses:");
            if (override == null)
                override = "<<USES>>";

            Set<String> usedPackages = uses.get(packageName);
            if (usedPackages != null) {
                // Only do a uses on exported or imported packages
                // and uses should also not contain our own package
                // name
                Set<String> sharedPackages = new HashSet<String>();
                sharedPackages.addAll(imports.keySet());
                sharedPackages.addAll(exports.keySet());
                usedPackages.retainAll(sharedPackages);
                usedPackages.remove(packageName);

                StringBuffer sb = new StringBuffer();
                String del = "";
                for (Iterator<String> u = usedPackages.iterator(); u.hasNext();) {
                    String usedPackage = u.next();
                    if (!usedPackage.startsWith("java.")) {
                        sb.append(del);
                        sb.append(usedPackage);
                        del = ",";
                    }
                }
                override = override.replaceAll("<<USES>>", sb.toString())
                        .trim();
                if (override.endsWith(","))
                    override = override.substring(0, override.length() - 1);
                if (override.startsWith(","))
                    override = override.substring(1);
                if (override.length() > 0) {
                    clause.put("uses:", override);
                }
            }
        }
    }

    /**
     * Get a property with a proper default
     *
     * @param headerName
     * @param deflt
     * @return
     */
    public String getProperty(String headerName, String deflt) {
        String v = getProperty(headerName);
        return v == null ? deflt : v;
    }

    /**
     * Helper to load a properties file from disk.
     *
     * @param file
     * @return
     * @throws IOException
     */
    Properties loadProperties(File file) throws IOException {
        updateModified(file.lastModified());
        InputStream in = new FileInputStream(file);
        Properties p = loadProperties(in, file.getAbsolutePath());
        in.close();
        return p;
    }

    Properties loadProperties(InputStream in, String name) throws IOException {
        int n = name.lastIndexOf('/');
        if (n > 0)
            name = name.substring(0, n);
        if (name.length() == 0)
            name = ".";

        try {
            Properties p = new Properties();
            p.load(in);
            return replaceAll(p, "\\$\\{\\.\\}", name);
        } catch (Exception e) {
            error("Error during loading properties file: " + name + ", error:"
                    + e);
            return new Properties();
        }
    }

    /**
     * Replace a string in all the values of the map. This can be used to
     * preassign variables that change. I.e. the base directory ${.} for a
     * loaded properties
     */

    Properties replaceAll(Properties p, String pattern, String replacement) {
        Properties result = new Properties();
        for (Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator(); i
                .hasNext();) {
            Map.Entry<Object, Object> entry = i.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            value = value.replaceAll(pattern, replacement);
            result.put(key, value);
        }
        return result;
    }

    /**
     * Merge the attributes of two maps, where the first map can contain
     * wildcarded names. The idea is that the first map contains patterns (for
     * example *) with a set of attributes. These patterns are matched against
     * the found packages in actual. If they match, the result is set with the
     * merged set of attributes. It is expected that the instructions are
     * ordered so that the instructor can define which pattern matches first.
     * Attributes in the instructions override any attributes from the
     * actual.<br/>
     *
     * A pattern is a modified regexp so it looks like globbing. The * becomes a
     * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
     * if the pattern starts with an exclamation mark, it will remove that
     * matches for that pattern (- the !) from the working set. So the following
     * patterns should work:
     * <ul>
     * <li>com.foo.bar</li>
     * <li>com.foo.*</li>
     * <li>com.foo.???</li>
     * <li>com.*.[^b][^a][^r]</li>
     * <li>!com.foo.* (throws away any match for com.foo.*)</li>
     * </ul>
     * Enough rope to hang the average developer I would say.
     *
     *
     * @param instructions
     *            the instructions with patterns. A
     * @param actual
     *            the actual found packages
     */

    Map<String, Map<String, String>> merge(String type,
            Map<String, Map<String, String>> instructions,
            Map<String, Map<String, String>> actual, Set<String> superfluous) {
        Map<String, Map<String, String>> toVisit = new HashMap<String, Map<String, String>>(
                actual); // we do not want to ruin our
        // original
        Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
        for (Iterator<String> i = instructions.keySet().iterator(); i.hasNext();) {
            String instruction = i.next();
            String originalInstruction = instruction;

            Map<String, String> instructedAttributes = instructions
                    .get(instruction);

            // Check if we have a fixed (starts with '=') or a
            // duplicate name. A fixed name is added to the output without
            // checking against the contents. Duplicates are marked
            // at the end. In that case we do not pick up any contained
            // information but just add them to the output including the
            // marker.
            if (instruction.startsWith("=")) {
                result.put(instruction.substring(1), instructedAttributes);
                superfluous.remove(originalInstruction);
                continue;
            }
            if (instruction.endsWith(DUPLICATE_MARKER)) {
                result.put(instruction, instructedAttributes);
                superfluous.remove(originalInstruction);
                continue;
            }

            Instruction instr = Instruction.getPattern(instruction);

            for (Iterator<String> p = toVisit.keySet().iterator(); p.hasNext();) {
                String packageName = p.next();

                if (instr.matches(packageName)) {
                    superfluous.remove(originalInstruction);
                    if (!instr.isNegated()) {
                        Map<String, String> newAttributes = new HashMap<String, String>();
                        newAttributes.putAll(actual.get(packageName));
                        newAttributes.putAll(instructedAttributes);
                        result.put(packageName, newAttributes);
                    } else {
                        ignored.put(packageName, new HashMap<String, String>());
                    }
                    p.remove(); // Can never match again for another pattern
                }
            }

        }
        return result;
    }

    /**
     * Print a standard Map based OSGi header.
     *
     * @param exports
     *            map { name => Map { attribute|directive => value } }
     * @return the clauses
     */
    public String printClauses(Map<String, Map<String, String>> exports,
            String allowedDirectives) {
        return printClauses(exports, allowedDirectives, false);
    }

    public String printClauses(Map<String, Map<String, String>> exports,
            String allowedDirectives, boolean checkMultipleVersions) {
        StringBuffer sb = new StringBuffer();
        String del = "";
        for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
            String name = i.next();
            Map<String, String> clause = exports.get(name);

            // We allow names to be duplicated in the input
            // by ending them with '~'. This is necessary to use
            // the package names as keys. However, we remove these
            // suffixes in the output so that you can set multiple
            // exports with different attributes.
            String outname = name;
            while (outname.endsWith(DUPLICATE_MARKER))
                outname = outname.substring(0, outname.length() - 1);

            printClause(outname, clause, allowedDirectives, sb, del);
            del = ",";
        }
        return sb.toString();
    }

    private void printClause(String name, Map<String, String> map,
            String allowedDirectives, StringBuffer sb, String del) {
        sb.append(del);
        sb.append(name);

        for (Iterator<String> j = map.keySet().iterator(); j.hasNext();) {
            String key = j.next();

            // Skip directives we do not recognize
            if (!key.startsWith("x-") && key.endsWith(":")
                    && allowedDirectives.indexOf(key) < 0)
                continue;

            String value = ((String) map.get(key)).trim();
            sb.append(";");
            sb.append(key);
            sb.append("=");

            boolean clean = (value.length() >= 2 && value.charAt(0) == '"' && value
                    .charAt(value.length() - 1) == '"')
                    || Verifier.TOKEN.matcher(value).matches();
            if (!clean)
                sb.append("\"");
            sb.append(value);
            if (!clean)
                sb.append("\"");
        }
    }

    /**
     * Transitively remove all elemens from unreachable through the uses link.
     *
     * @param name
     * @param unreachable
     */
    void removeTransitive(String name, Set<String> unreachable) {
        if (!unreachable.contains(name))
            return;

        unreachable.remove(name);

        Set<String> ref = uses.get(name);
        if (ref != null) {
            for (Iterator<String> r = ref.iterator(); r.hasNext();) {
                String element = (String) r.next();
                removeTransitive(element, unreachable);
            }
        }
    }

    /**
     * Helper method to set the package info
     *
     * @param dir
     * @param key
     * @param value
     */
    void setPackageInfo(String dir, String key, String value) {
        if (value != null) {
            String pack = dir.replace('/', '.');
            Map<String, String> map = cpExports.get(pack);
            if (map == null) {
                map = new HashMap<String, String>();
                cpExports.put(pack, map);
            }
            map.put(key, value);
        }
    }

    public void close() {
        super.close();
        if (dot != null)
            dot.close();
       
        if (classpath != null)
            for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
                Jar jar = j.next();
                jar.close();
            }
    }

    /**
     * Findpath looks through the contents of the JAR and finds paths that end
     * with the given regular expression
     *
     * ${findpath (; reg-expr (; replacement)? )? }
     *
     * @param args
     * @return
     */
    public String _findpath(String args[]) {
        return findPath("findpath", args, true);
    }

    public String _findname(String args[]) {
        return findPath("findname", args, false);
    }

    String findPath(String name, String[] args, boolean fullPathName) {
        if (args.length > 3) {
            warning("Invalid nr of arguments to " + name + " "
                    + Arrays.asList(args) + ", syntax: ${" + name
                    + " (; reg-expr (; replacement)? )? }");
            return null;
        }

        String regexp = ".*";
        String replace = null;

        switch (args.length) {
        case 3:
            replace = args[2];
        case 2:
            regexp = args[1];
        }
        StringBuffer sb = new StringBuffer();
        String del = "";

        Pattern expr = Pattern.compile(regexp);
        for (Iterator<String> e = dot.getResources().keySet().iterator(); e
                .hasNext();) {
            String path = e.next();
            if (!fullPathName) {
                int n = path.lastIndexOf('/');
                if (n >= 0) {
                    path = path.substring(n + 1);
                }
            }

            Matcher m = expr.matcher(path);
            if (m.matches()) {
                if (replace != null)
                    path = m.replaceAll(replace);

                sb.append(del);
                sb.append(path);
                del = ", ";
            }
        }
        return sb.toString();
    }

    public void updateModified(long time) {
        if (time > lastModified)
            lastModified = time;
    }

    public long lastModified() {
        updateModified(getBndLastModified());
        return lastModified;
    }

    public void putAll(Map<String, String> additional, boolean force) {
        for (Iterator<Map.Entry<String, String>> i = additional.entrySet()
                .iterator(); i.hasNext();) {
            Map.Entry<String, String> entry = i.next();
            if (force || getProperties().get(entry.getKey()) == null)
                setProperty((String) entry.getKey(), (String) entry.getValue());
        }
    }

    boolean firstUse = true;

    public List<Jar> getClasspath() {
        if (firstUse) {
            firstUse = false;
            String cp = getProperty(CLASSPATH);
            if (cp != null)
                for (Iterator<String> i = getClauses(cp).iterator(); i
                        .hasNext();) {
                    Jar jar = getJarFromName(i.next(), "getting classpath");
                    if (jar != null)
                        addClasspath(jar);
                }
        }
        return classpath;
    }

    public void addClasspath(Jar jar) {
        if (isPedantic() && jar.getResources().isEmpty())
            warning("There is an empty jar or directory on the classpath: "
                    + jar.getName());

        classpath.add(jar);
    }

    public void clear() {
        classpath.clear();
    }

    public Jar getTarget() {
        return dot;
    }

    public Macro getReplacer() {
        begin();
        return replacer;
    }
   

}
TOP

Related Classes of aQute.lib.osgi.Analyzer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.