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 {
 
  public final static String    BUNDLE_CLASSPATH          = "Bundle-ClassPath";
  public final static String    BUNDLE_COPYRIGHT          = "Bundle-Copyright";
  public final static String    BUNDLE_DESCRIPTION          = "Bundle-Description";
  public final static String    BUNDLE_NAME              = "Bundle-Name";
  public final static String    BUNDLE_NATIVECODE          = "Bundle-NativeCode";
  public final static String    EXPORT_PACKAGE            = "Export-Package";
  public final static String    EXPORT_SERVICE            = "Export-Service";
  public final static String    IMPORT_PACKAGE            = "Import-Package";
  public final static String    DYNAMICIMPORT_PACKAGE        = "DynamicImport-Package";
  public final static String    IMPORT_SERVICE            = "Import-Service";
  public final static String    BUNDLE_VENDOR            = "Bundle-Vendor";
  public final static String    BUNDLE_VERSION            = "Bundle-Version";
  public final static String    BUNDLE_DOCURL            = "Bundle-DocURL";
  public final static String    BUNDLE_CONTACTADDRESS        = "Bundle-ContactAddress";
  public final static String    BUNDLE_ACTIVATOR          = "Bundle-Activator";
  public final static String    BUNDLE_REQUIREDEXECUTIONENVIRONMENT  = "Bundle-RequiredExecutionEnvironment";
  public final static String    BUNDLE_SYMBOLICNAME          = "Bundle-SymbolicName";
  public final static String    BUNDLE_LOCALIZATION          = "Bundle-Localization";
  public final static String    REQUIRE_BUNDLE            = "Require-Bundle";
  public final static String    FRAGMENT_HOST            = "Fragment-Host";
  public final static String    BUNDLE_MANIFESTVERSION        = "Bundle-ManifestVersion";
  public final static String    SERVICE_COMPONENT          = "Service-Component";
  public final static String    BUNDLE_LICENSE            = "Bundle-License";
  public static final String    PRIVATE_PACKAGE            = "Private-Package";
  public static final String    IGNORE_PACKAGE            = "Ignore-Package";
  public static final String    INCLUDE_RESOURCE          = "Include-Resource";
  public static final String    CONDITIONAL_PACKAGE          = "Conditional-Package";
  public static final String    BND_LASTMODIFIED          = "Bnd-LastModified";
  public static final String    CREATED_BY              = "Created-By";
  public static final String    TOOL                = "Tool";

  public final static String    headers[]              = {
      BUNDLE_ACTIVATOR, BUNDLE_CONTACTADDRESS, BUNDLE_COPYRIGHT,
      BUNDLE_DESCRIPTION, BUNDLE_DOCURL, BUNDLE_LOCALIZATION,
      BUNDLE_NATIVECODE, BUNDLE_VENDOR, BUNDLE_VERSION, BUNDLE_LICENSE,
      BUNDLE_CLASSPATH, SERVICE_COMPONENT, EXPORT_PACKAGE,
      IMPORT_PACKAGE, BUNDLE_LOCALIZATION, BUNDLE_MANIFESTVERSION,
      BUNDLE_NAME, BUNDLE_NATIVECODE,
      BUNDLE_REQUIREDEXECUTIONENVIRONMENT, BUNDLE_SYMBOLICNAME,
      BUNDLE_VERSION, FRAGMENT_HOST, PRIVATE_PACKAGE, IGNORE_PACKAGE,
      INCLUDE_RESOURCE, REQUIRE_BUNDLE, IMPORT_SERVICE, EXPORT_SERVICE,
      CONDITIONAL_PACKAGE, BND_LASTMODIFIED            };

  public static final String    REMOVE_HEADERS            = "-removeheaders";
  public static final String    SOURCES                = "-sources";
  public static final String    FAIL_OK                = "-failok";
  public static final String    DONOTCOPY              = "-donotcopy";
  public static final String    EXPORT_CONTENTS            = "-exportcontents";
  public static final String    CLASSPATH              = "-classpath";
  public static final String    PEDANTIC              = "-pedantic";
  public static final String    NOEXTRAHEADERS            = "-noextraheaders";
  public static final String    POM                  = "-pom";
  public static final String    INCLUDE                = "-include";

  public static final String    options[]              = {
      REMOVE_HEADERS, SOURCES, FAIL_OK, DONOTCOPY, EXPORT_CONTENTS,
      CLASSPATH, PEDANTIC, NOEXTRAHEADERS, POM, INCLUDE      };

  public static final String    SPLIT_PACKAGE_DIRECTIVE        = "-split-package:";
  public static final String    NO_IMPORT_DIRECTIVE          = "-noimport:";

  public static final String    directives[]            = {
      SPLIT_PACKAGE_DIRECTIVE, NO_IMPORT_DIRECTIVE, "resolution:",
      "include:", "uses:", "exclude:",
                                    // TODO
                                    };
  public static final String[]  componentDirectives          = new String[] {
      "factory:", "immediate:", "enabled:", "dynamic:", "multiple:",
      "provide:", "optional:", "properties:"            };

  static Map            EES                  = new HashMap();
  static Set            SET_COMPONENT_DIRECTIVES      = new HashSet(
                                        Arrays
                                            .asList(componentDirectives));

  static final Pattern      VALID_PROPERTY_TYPES        = Pattern
                                        .compile("(String|Long|Double|Float|Integer|Byte|Character|Boolean|Short)");

  static Pattern          doNotCopy              = Pattern
                                        .compile("CVS|.svn");
  static String          version;
  /**
   * For each import, find the exporter and see what you can learn from it.
   */
  static Pattern          versionPattern            = Pattern
                                        .compile("(\\d+\\.\\d+)\\.\\d+");
  private Properties        properties              /* String->String */= new Properties();
  File              base                = new File(
                                        "")
                                        .getAbsoluteFile();
  Map                contained              /* String->Map */= new HashMap();                            // package
  Map                referred              /* String->Map */= new HashMap();                            // package
  Map                uses                /* String->Map */= new HashMap();                            // package
  Map                classspace;
  boolean              analyzed;
  Map                exports;
  Map                imports;
  Map                bundleClasspath;                                                      // Bundle
  Map                ignored              /*
                                   * String ->
                                   * Map
                                   */= new HashMap();                                    // Ignored
  // packages
  Jar                dot;                                                            // The
  Map                cpExports              = new HashMap();

  String              activator;

  List              classpath              = new ArrayList();

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

  boolean              noExtraHeaders;
  boolean              fixedupProperties          = false;

 
  /**
   * 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 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 = new HashMap();
      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());
        }
      }

      referred.keySet().removeAll(contained.keySet());

      Map exportInstructions = parseHeader(getProperty(EXPORT_PACKAGE));
      Map additionalExportInstructions = parseHeader(getProperty(EXPORT_CONTENTS));
      exportInstructions.putAll(additionalExportInstructions);
      Map importInstructions = parseHeader(getProperty(IMPORT_PACKAGE));
      Map dynamicImports = parseHeader(getProperty(DYNAMICIMPORT_PACKAGE));

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

      Set superfluous = new TreeSet();
      // Tricky!
      for (Iterator i = exportInstructions.keySet().iterator(); i
          .hasNext();) {
        String instr = (String) i.next();
        if (!instr.startsWith("!"))
          superfluous.add(instr);
      }

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

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

      // Add all exports that do not have an -noimport: directive
      // to the imports.
      Map referredAndExported = new HashMap(referred);
      referredAndExported.putAll(addExportsToImports(exports));

      // match the imports to the referred and exported packages,
      // merge the info for matching packages
      Set extra = new TreeSet(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 i = extra.iterator(); i.hasNext();) {
        String p = (String) i.next();
        if (p.startsWith("!") || p.indexOf('*') >= 0 ||
            p.indexOf('?') >= 0 || p.indexOf('[') >= 0) {
          warning("Did not find matching referal for " + p);
        } else {
          Map map = (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);
    }
  }

  /**
   * 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.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:");

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

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

    temp = new TreeMap(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 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?
      }
    }

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

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

    // Check for some defaults
    String p = getProperty("p");
    if (p != null) {
      if (main.getValue(BUNDLE_NAME) == null) {
        if (main.getValue(BUNDLE_SYMBOLICNAME) != null) {
          main.putValue(BUNDLE_NAME, main
              .getValue(BUNDLE_SYMBOLICNAME));
        } else
          main.putValue(BUNDLE_NAME, p);
      }
      if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
        main.putValue(BUNDLE_SYMBOLICNAME, p);
      }
    }
    if (main.getValue(BUNDLE_VERSION) == null)
      main.putValue(BUNDLE_VERSION, "0");

    // Remove all the headers mentioned in -removeheaders
    Map removes = parseHeader(getProperty(REMOVE_HEADERS));
    for (Iterator i = removes.keySet().iterator(); i.hasNext();) {
      String header = (String) i.next();
      for (Iterator 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;
  }

  /**
   * 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 map = bundle.getDirectories();
    for (Iterator 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 doServiceComponent(String serviceComponent) throws IOException {
    Map list = new LinkedHashMap();
    Map sc = parseHeader(serviceComponent);
    if (!sc.isEmpty()) {
      for (Iterator i = sc.entrySet().iterator(); i.hasNext();) {
        Map.Entry entry = (Map.Entry) i.next();
        String name = (String) entry.getKey();
        Map info = (Map) 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());
          }
        }
      }
    }
    return list;
  }

  public Map getBundleClasspath() {
    return bundleClasspath;
  }

  public Map getContained() {
    return contained;
  }

  public Map getExports() {
    return exports;
  }

  public Map 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 getReferred() {
    return referred;
  }

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

  public Map 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 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) {
    base = file;
    getProperties().put("project.dir", base.getAbsolutePath());
  }

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

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

  public void setClasspath(String[] classpath) {
    List list = new ArrayList();
    for (int i = 0; i < classpath.length; i++) {
      Jar jar = getJarFromName(classpath[i], " setting classpath");
      if (jar != null)
        list.add(jar);
    }
    this.classpath.addAll(list);
  }

  /**
   * 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 {
    return setJar(new Jar(jar));
  }

  /**
   * 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 {
    propertiesFile = propertiesFile.getAbsoluteFile();
    updateModified(propertiesFile.lastModified());
    setBase(propertiesFile.getParentFile());

    Properties local = loadProperties(propertiesFile);

    setProperties(local);
    setProperty("project.file", propertiesFile.getAbsolutePath());
    if (getProperty("p") == null)
      setProperty("p", propertiesFile.getParentFile().getName());

    if (getProperty(BUNDLE_SYMBOLICNAME) == null) {
      // Calculate a default symbolic name
      // from the file name.
      String name = propertiesFile.getName();
      int n = name.lastIndexOf('.');
      if (n > 0)
        name = name.substring(0, n);
      local.setProperty(BUNDLE_SYMBOLICNAME, name);
    }
  }

  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) {
    this.properties = properties;
    doPropertyIncludes(getBaseURL(), properties, new HashSet());
    fixedupProperties = false;
  }

  void begin() {
    if (fixedupProperties)
      return;

    fixedupProperties = true;
    replacer = new Macro(properties, this);

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

    String cp = properties.getProperty(CLASSPATH);
    if (cp != null)
      doClasspath(cp);

    verifyManifestHeadersCase(properties);

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

  private URL getBaseURL() {
    try {
      return base.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 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 = (String) info.get("factory:");
    if (factory != null)
      pw.print(" factory='" + factory + "'");

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

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

    pw.println(">");
    pw.println("  <implementation class='" + name + "'/>");
    String provides = (String) 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);
  }

  /**
   * Parse the -classpath header. This is a comma separated list of urls or
   * file names.
   *
   * @param cp
   */
  void doClasspath(String cp) {
    for (Iterator i = getClauses(cp).iterator(); i.hasNext();) {
      Jar jar = getJarFromName((String) i.next(), "getting classpath");
      if (jar != null)
        addClasspath(jar);
    }
  }

  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);
  }

  /**
   * 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(base, name);

    if (file.exists())
      try {
        Jar jar = new Jar(file.getName(), file);
        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()));
      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 cp = classpath.iterator(); cp.hasNext();) {
        Jar entry = (Jar) 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 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
   */
  Set getClauses(String list) {
    if (list == null)
      return new HashSet();
    list = list.trim();
    String[] parts = list.split("\\s*,\\s*");
    return new LinkedHashSet(Arrays.asList(parts));
  }

  /**
   *
   * @param manifest
   * @throws Exception
   */
  void merge(Manifest result, Manifest old) throws IOException {
    if (old != null) {
      for (Iterator e = old.getMainAttributes().entrySet().iterator(); e
          .hasNext();) {
        Map.Entry entry = (Map.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 oldEntries = old.getEntries();
      Map newEntries = result.getEntries();
      for (Iterator e = oldEntries.entrySet().iterator(); e.hasNext();) {
        Map.Entry entry = (Map.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 info) {
    Set properties = getClauses((String) info.get("properties:"));
    for (Iterator p = properties.iterator(); p.hasNext();) {
      String clause = (String) 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);
        // 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 {
            warnings.add("Invalid property type '" + type +
                "' for property " + name);
          }
        }

        if (value.indexOf("\\n")>=0) {
          pw.print("'>");
          pw.print(value.replaceAll("\\n", "\n"));
          pw.println("</property>");
        } else {
          pw.print(" value='");
          pw.print(value);
          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 info, PrintWriter pw) {
    Set dynamic = getClauses((String) info.get("dynamic:"));
    Set optional = getClauses((String) info.get("optional:"));
    Set multiple = getClauses((String) info.get("multiple:"));

    for (Iterator r = info.entrySet().iterator(); r.hasNext();) {
      Map.Entry ref = (Map.Entry) r.next();
      String referenceName = (String) ref.getKey();
      String target = null;
      String interfaceName = (String) ref.getValue();
      // 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 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)) {
          warnings
              .add("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 addExportsToImports(Map exports) {
    Map importsFromExports = new HashMap();
    for (Iterator export = exports.entrySet().iterator(); export.hasNext();) {
      Map.Entry entry = (Map.Entry) export.next();
      String packageName = (String) entry.getKey();
      Map parameters = (Map) entry.getValue();
      String noimport = (String) parameters.get(NO_IMPORT_DIRECTIVE);
      if (noimport == null || !noimport.equalsIgnoreCase("true")) {
        Map importParameters = (Map) 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 = new HashMap();
    for (Iterator c = classpath.iterator(); c.hasNext();) {
      Jar current = (Jar) c.next();
      checkManifest(current);
      for (Iterator j = current.getDirectories().keySet().iterator(); j
          .hasNext();) {
        String dir = (String) 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 exported = (Map) 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 imp = imports.keySet().iterator(); imp.hasNext();) {
      String packageName = (String) imp.next();
      Map currentAttributes = (Map) imports.get(packageName);
      Map exporter = (Map) cpExports.get(packageName);
      if (exporter != null) {
        augmentVersion(currentAttributes, exporter);
        augmentMandatory(currentAttributes, exporter);
      }
    }
  }

  /**
   * If we use an import with mandatory attributes we better all use them
   *
   * @param currentAttributes
   * @param exporter
   */
  private void augmentMandatory(Map currentAttributes, Map exporter) {
    String mandatory = (String) exporter.get("mandatory:");
    if (mandatory != null) {
      String[] attrs = mandatory.split("\\s*,\\s*");
      for (int i = 0; i < attrs.length; 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 currentAttributes, Map exporter) {
    String currentVersion = (String) currentAttributes.get("version");
    if (currentVersion == null || currentVersion.indexOf("${") >= 0) {
      // See if we can borrow the version
      String version = (String) exporter.get("version");
      if (version == null)
        version = (String) exporter.get("specification-version");
      if (version != null) {
        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("@", version);
          version = replacer.process(currentVersion);
          unsetProperty("@");
        } else {
          // We remove the micro part of the version
          // to a bit more lenient
          Matcher m = versionPattern.matcher(version);
          if (m.matches())
            version = m.group(1);
        }
        currentAttributes.put("version", version);
      }
    }
  }

  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
   */
  void doPropertyIncludes(URL ubase, Properties p, Set done) {
    String includes = p.getProperty(INCLUDE);
    if (includes != null) {
      includes = replacer.process(includes);
      Set clauses = getClauses(includes);
      outer: for (Iterator i = clauses.iterator(); i.hasNext();) {
        String value = (String) i.next();
        boolean fileMustExist = true;
        if (value.startsWith("-")) {
          fileMustExist = false;
          value = value.substring(1).trim();
        }
        try {
          URL next = null;
          try {
            next = new URL(ubase, value);
          } catch (MalformedURLException e) {

            File f = new File(value);
            if (!f.isAbsolute())
              f = new File(base, 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);
          p.putAll(sub);
          in.close();
        } 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 exports, Map uses) {
    for (Iterator i = exports.keySet().iterator(); i.hasNext();) {
      String packageName = (String) i.next();
      Map clause = (Map) exports.get(packageName);

      Set t = (Set) uses.get(packageName);
      if (t != null && !t.isEmpty()) {
        StringBuffer sb = new StringBuffer();
        String del = "";
        for (Iterator u = t.iterator(); u.hasNext();) {
          String usedPackage = (String) u.next();
          if (!usedPackage.equals(packageName) &&
              !usedPackage.startsWith("java.")) {
            sb.append(del);
            sb.append(usedPackage);
            del = ",";
          }
        }
        String s = sb.toString();
        if (s.length() > 0)
          clause.put("uses:", sb.toString());
      }
    }
  }

  /**
   * 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 {
    InputStream in = new FileInputStream(file);
    Properties p = loadProperties(in, file.getAbsolutePath());
    in.close();
    return p;
  }

  Properties loadProperties(InputStream in, String name) throws IOException {
    try {
      Properties p = new Properties();
      p.load(in);
      return p;
    } catch (Exception e) {
      error("Error during loading properties file: " + name + ", error:" +
          e);
      return new Properties();
    }
  }

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

      Map instructedAttributes = (Map) instructions.get(instruction);

      if (instruction.startsWith("=")) {
        result.put(instruction.substring(1), instructedAttributes);
        superfluous.remove(originalInstruction);
        continue;
      }

      Instruction instr = Instruction.getPattern(instruction);

      for (Iterator p = actual.keySet().iterator(); p.hasNext();) {
        String packageName = (String) p.next();

        if (instr.matches(packageName)) {
          superfluous.remove(originalInstruction);
          if (!instr.isNegated()) {
            Map newAttributes = new HashMap();
            newAttributes.putAll((Map) actual.get(packageName));
            newAttributes.putAll(instructedAttributes);
            result.put(packageName, newAttributes);
          } else {
            ignored.put(packageName, new HashMap());
          }
          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 exports, String allowedDirectives) {
    StringBuffer sb = new StringBuffer();
    String del = "";
    for (Iterator i = exports.keySet().iterator(); i.hasNext();) {
      String name = (String) i.next();
      Map map = (Map) exports.get(name);
      sb.append(del);
      sb.append(name);

      for (Iterator j = map.keySet().iterator(); j.hasNext();) {
        String key = (String) 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("\"");
      }
      del = ",";
    }
    return sb.toString();
  }

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

    unreachable.remove(name);

    Set ref = (Set) uses.get(name);
    if (ref != null) {
      for (Iterator 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 map = (Map) cpExports.get(pack);
      if (map == null) {
        map = new HashMap();
        cpExports.put(pack, map);
      }
      map.put(key, value);
    }
  }

  public void close() {
    dot.close();
    if (classpath != null)
      for (Iterator j = classpath.iterator(); j.hasNext();) {
        Jar 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 e = dot.getResources().keySet().iterator(); e.hasNext();) {
      String path = (String) 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 additional, boolean force) {
    for (Iterator i = additional.entrySet().iterator(); i.hasNext();) {
      Map.Entry entry = (Map.Entry) i.next();
      if (force || getProperties().get(entry.getKey()) == null)
        setProperty((String) entry.getKey(), (String) entry.getValue());
    }
  }

  public List getClasspath() {
    return classpath;
  }

}
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.