Package aQute.bnd.osgi

Source Code of aQute.bnd.osgi.Builder

package aQute.bnd.osgi;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.jar.*;
import java.util.regex.*;
import java.util.zip.*;

import aQute.bnd.component.*;
import aQute.bnd.differ.*;
import aQute.bnd.header.*;
import aQute.bnd.make.*;
import aQute.bnd.make.component.*;
import aQute.bnd.make.metatype.*;
import aQute.bnd.maven.*;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.service.*;
import aQute.bnd.service.diff.*;
import aQute.lib.collections.*;
import aQute.lib.io.*;
import aQute.libg.generics.*;

/**
* Include-Resource: ( [name '=' ] file )+ Private-Package: package-decl ( ','
* package-decl )* Export-Package: package-decl ( ',' package-decl )*
* Import-Package: package-decl ( ',' package-decl )*
*
* @version $Revision: 1.27 $
*/
public class Builder extends Analyzer {
  static Pattern          IR_PATTERN      = Pattern.compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
  private final DiffPluginImpl  differ        = new DiffPluginImpl();
  private Pattern          xdoNotCopy      = null;
  private static final int    SPLIT_MERGE_LAST  = 1;
  private static final int    SPLIT_MERGE_FIRST  = 2;
  private static final int    SPLIT_ERROR      = 3;
  private static final int    SPLIT_FIRST      = 4;
  private static final int    SPLIT_DEFAULT    = 0;
  private final List<File>    sourcePath      = new ArrayList<File>();
  private final Make        make        = new Make(this);

  public Builder(Processor parent) {
    super(parent);
  }

  public Builder() {}

  public Jar build() throws Exception {
    trace("build");
    init();
    if (isTrue(getProperty(NOBUNDLES)))
      return null;

    if (getProperty(CONDUIT) != null)
      error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");

    Jar dot = new Jar("dot");
    try {
      long modified = Long.parseLong(getProperty("base.modified"));
      dot.updateModified(modified, "Base modified");
    }
    catch (Exception e) {
      // Ignore
    }
    setJar(dot);

    doExpand(dot);
    doIncludeResources(dot);
    doWab(dot);

    // Check if we override the calculation of the
    // manifest. We still need to calculated it because
    // we need to have analyzed the classpath.

    Manifest manifest = calcManifest();

    String mf = getProperty(MANIFEST);
    if (mf != null) {
      File mff = getFile(mf);
      if (mff.isFile()) {
        try {
          InputStream in = new FileInputStream(mff);
          manifest = new Manifest(in);
          in.close();
        }
        catch (Exception e) {
          error(MANIFEST + " while reading manifest file", e);
        }
      } else {
        error(MANIFEST + ", no such file " + mf);
      }
    }

    if (getProperty(NOMANIFEST) == null)
      dot.setManifest(manifest);
    else
      dot.setDoNotTouchManifest();

    // This must happen after we analyzed so
    // we know what it is on the classpath
    addSources(dot);

    if (getProperty(POM) != null)
      dot.putResource("pom.xml", new PomResource(dot.getManifest()));

    if (!isNoBundle())
      doVerify(dot);

    if (dot.getResources().isEmpty())
      warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
          getBsn());

    dot.updateModified(lastModified(), "Last Modified Processor");
    dot.setName(getBsn());

    doDigests(dot);

    sign(dot);
    doSaveManifest(dot);

    doDiff(dot); // check if need to diff this bundle
    doBaseline(dot); // check for a baseline
    return dot;
  }

  /**
   * Check if we need to calculate any checksums.
   *
   * @param dot
   * @throws Exception
   */
  private void doDigests(Jar dot) throws Exception {
    Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
    if (ps.isEmpty())
      return;
    trace("digests %s", ps);
    String[] digests = ps.keySet().toArray(new String[ps.size()]);
    dot.setDigestAlgorithms(digests);
  }

  /**
   * Allow any local initialization by subclasses before we build.
   */
  public void init() throws Exception {
    begin();
    doRequireBnd();

    // Check if we have sensible setup

    if (getClasspath().size() == 0
        && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
      warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");

  }

  /**
   * Turn this normal bundle in a web and add any resources.
   *
   * @throws Exception
   */
  private Jar doWab(Jar dot) throws Exception {
    String wab = getProperty(WAB);
    String wablib = getProperty(WABLIB);
    if (wab == null && wablib == null)
      return dot;

    trace("wab %s %s", wab, wablib);
    setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));

    Set<String> paths = new HashSet<String>(dot.getResources().keySet());

    for (String path : paths) {
      if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
        trace("wab: moving: %s", path);
        dot.rename(path, "WEB-INF/classes/" + path);
      }
    }

    Parameters clauses = parseHeader(getProperty(WABLIB));
    for (String key : clauses.keySet()) {
      File f = getFile(key);
      addWabLib(dot, f);
    }
    doIncludeResource(dot, wab);
    return dot;
  }

  /**
   * Add a wab lib to the jar.
   *
   * @param f
   */
  private void addWabLib(Jar dot, File f) throws Exception {
    if (f.exists()) {
      Jar jar = new Jar(f);
      jar.setDoNotTouchManifest();
      addClose(jar);
      String path = "WEB-INF/lib/" + f.getName();
      dot.putResource(path, new JarResource(jar));
      setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));

      Manifest m = jar.getManifest();
      String cp = m.getMainAttributes().getValue("Class-Path");
      if (cp != null) {
        Collection<String> parts = split(cp, ",");
        for (String part : parts) {
          File sub = getFile(f.getParentFile(), part);
          if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
            warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
                f);
          } else {
            addWabLib(dot, sub);
          }
        }
      }
    } else {
      error("WAB lib does not exist %s", f);
    }
  }

  /**
   * Get the manifest and write it out separately if -savemanifest is set
   *
   * @param dot
   */
  private void doSaveManifest(Jar dot) throws Exception {
    String output = getProperty(SAVEMANIFEST);
    if (output == null)
      return;

    File f = getFile(output);
    if (f.isDirectory()) {
      f = new File(f, "MANIFEST.MF");
    }
    f.delete();
    File fp = f.getParentFile();
    if (!fp.exists() && !fp.mkdirs()) {
      throw new IOException("Could not create directory " + fp);
    }
    OutputStream out = new FileOutputStream(f);
    try {
      Jar.writeManifest(dot.getManifest(), out);
    }
    finally {
      out.close();
    }
    changedFile(f);
  }

  protected void changedFile(@SuppressWarnings("unused")
  File f) {}

  /**
   * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
   * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
   *
   * @return
   */

  void sign(@SuppressWarnings("unused")
  Jar jar) throws Exception {
    String signing = getProperty(SIGN);
    if (signing == null)
      return;

    trace("Signing %s, with %s", getBsn(), signing);
    List<SignerPlugin> signers = getPlugins(SignerPlugin.class);

    Parameters infos = parseHeader(signing);
    for (Entry<String,Attrs> entry : infos.entrySet()) {
      for (SignerPlugin signer : signers) {
        signer.sign(this, entry.getKey());
      }
    }
  }

  public boolean hasSources() {
    return isTrue(getProperty(SOURCES));
  }

  /**
   * Answer extra packages. In this case we implement conditional package. Any
   */
  @Override
  protected Jar getExtra() throws Exception {
    Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
    if (conditionals.isEmpty())
      return null;
    trace("do Conditional Package %s", conditionals);
    Instructions instructions = new Instructions(conditionals);

    Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
    referred.removeAll(getContained().keySet());

    Jar jar = new Jar("conditional-import");
    addClose(jar);
    for (PackageRef pref : referred) {
      for (Jar cpe : getClasspath()) {
        Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
        if (map != null) {
          copy(jar, cpe, pref.getPath(), false);
          // Now use copy so that bnd.info is processed, next line
          // should be
          // removed in the future TODO
          // jar.addDirectory(map, false);
          break;
        }
      }
    }
    if (jar.getDirectories().size() == 0) {
      trace("extra dirs %s", jar.getDirectories());
      return null;
    }
    return jar;
  }

  /**
   * Intercept the call to analyze and cleanup versions after we have analyzed
   * the setup. We do not want to cleanup if we are going to verify.
   */

  @Override
  public void analyze() throws Exception {
    super.analyze();
    cleanupVersion(getImports(), null);
    cleanupVersion(getExports(), getVersion());
    String version = getProperty(BUNDLE_VERSION);
    if (version != null) {
      version = cleanupVersion(version);
      if (version.endsWith(".SNAPSHOT")) {
        version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
      }
      setProperty(BUNDLE_VERSION, version);
    }
  }

  public void cleanupVersion(Packages packages, String defaultVersion) {
    for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
      Attrs attributes = entry.getValue();
      String v = attributes.get(Constants.VERSION_ATTRIBUTE);
      if (v == null && defaultVersion != null) {
        if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
          v = defaultVersion;
          if (isPedantic())
            warning("Used bundle version %s for exported package %s", v, entry.getKey());
        } else {
          if (isPedantic())
            warning("No export version for exported package %s", entry.getKey());
        }
      }
      if (v != null)
        attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
    }
  }

  /**
     *
     */
  private void addSources(Jar dot) {
    if (!hasSources())
      return;

    Set<PackageRef> packages = Create.set();

    for (TypeRef typeRef : getClassspace().keySet()) {
      PackageRef packageRef = typeRef.getPackageRef();
      String sourcePath = typeRef.getSourcePath();
      String packagePath = packageRef.getPath();

      boolean found = false;
      String[] fixed = {
          "packageinfo", "package.html", "module-info.java", "package-info.java"
      };

      for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
        File root = i.next();

        // TODO should use bcp?

        File f = getFile(root, sourcePath);
        if (f.exists()) {
          found = true;
          if (!packages.contains(packageRef)) {
            packages.add(packageRef);
            File bdir = getFile(root, packagePath);
            for (int j = 0; j < fixed.length; j++) {
              File ff = getFile(bdir, fixed[j]);
              if (ff.isFile()) {
                String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
                dot.putResource(name, new FileResource(ff));
              }
            }
          }
          if (packageRef.isDefaultPackage())
            System.err.println("Duh?");
          dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
        }
      }
      if (!found) {
        for (Jar jar : getClasspath()) {
          Resource resource = jar.getResource(sourcePath);
          if (resource != null) {
            dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
          } else {
            resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
            if (resource != null) {
              dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
            }
          }
        }
      }
      if (getSourcePath().isEmpty())
        warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
      // TODO copy from the jars where they came from
    }
  }

  boolean      firstUse  = true;
  private Tree  tree;

  public Collection<File> getSourcePath() {
    if (firstUse) {
      firstUse = false;
      String sp = getProperty(SOURCEPATH);
      if (sp != null) {
        Parameters map = parseHeader(sp);
        for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
          String file = i.next();
          if (!isDuplicate(file)) {
            File f = getFile(file);
            if (!f.isDirectory()) {
              error("Adding a sourcepath that is not a directory: " + f);
            } else {
              sourcePath.add(f);
            }
          }
        }
      }
    }
    return sourcePath;
  }

  private void doVerify(@SuppressWarnings("unused")
  Jar dot) throws Exception {
    Verifier verifier = new Verifier(this);
    // Give the verifier the benefit of our analysis
    // prevents parsing the files twice
    verifier.verify();
    getInfo(verifier);
  }

  private void doExpand(Jar dot) {

    // Build an index of the class path that we can then
    // use destructively
    MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
    for (Jar srce : getClasspath()) {
      dot.updateModified(srce.lastModified, srce + " (" + srce.lastModifiedReason + ")");
      for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
        if (e.getValue() != null)
          packages.add(e.getKey(), srce);
      }
    }

    Parameters privatePackages = getPrivatePackage();
    if (isTrue(getProperty(Constants.UNDERTEST))) {
      String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
      privatePackages.putAll(parseHeader(h));
    }

    if (!privatePackages.isEmpty()) {
      Instructions privateFilter = new Instructions(privatePackages);
      Set<Instruction> unused = doExpand(dot, packages, privateFilter);

      if (!unused.isEmpty()) {
        warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
      }
    }

    Parameters exportedPackage = getExportPackage();
    if (!exportedPackage.isEmpty()) {
      Instructions exportedFilter = new Instructions(exportedPackage);

      // We ignore unused instructions for exports, they should show
      // up as errors during analysis. Otherwise any overlapping
      // packages with the private packages should show up as
      // unused

      doExpand(dot, packages, exportedFilter);
    }
  }

  /**
   * Destructively filter the packages from the build up index. This index is
   * used by the Export Package as well as the Private Package
   *
   * @param jar
   * @param name
   * @param instructions
   */
  private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
    Set<Instruction> unused = Create.set();

    for (Entry<Instruction,Attrs> e : filter.entrySet()) {
      Instruction instruction = e.getKey();
      if (instruction.isDuplicate())
        continue;

      Attrs directives = e.getValue();

      // We can optionally filter on the
      // source of the package. We assume
      // they all match but this can be overridden
      // on the instruction
      Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));

      boolean used = false;

      for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
        Entry<String,List<Jar>> p = entry.next();

        String directory = p.getKey();
        PackageRef packageRef = getPackageRef(directory);

        // Skip * and meta data, we're talking packages!
        if (packageRef.isMetaData() && instruction.isAny())
          continue;

        if (!instruction.matches(packageRef.getFQN()))
          continue;

        // Ensure it is never matched again
        entry.remove();

        // ! effectively removes it from consideration by others (this
        // includes exports)
        if (instruction.isNegated())
          continue;

        // Do the from: directive, filters on the JAR type
        List<Jar> providers = filterFrom(from, p.getValue());
        if (providers.isEmpty())
          continue;

        int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
        copyPackage(jar, providers, directory, splitStrategy);

        used = true;
      }

      if (!used && !isTrue(directives.get("optional:")))
        unused.add(instruction);
    }
    return unused;
  }

  /**
   * @param from
   * @return
   */
  private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
    if (from.isAny())
      return providers;

    List<Jar> np = new ArrayList<Jar>();
    for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
      Jar j = i.next();
      if (from.matches(j.getName())) {
        np.add(j);
      }
    }
    return np;
  }

  /**
   * Copy the package from the providers based on the split package strategy.
   *
   * @param dest
   * @param providers
   * @param directory
   * @param splitStrategy
   */
  private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
    switch (splitStrategy) {
      case SPLIT_MERGE_LAST :
        for (Jar srce : providers) {
          copy(dest, srce, path, true);
        }
        break;

      case SPLIT_MERGE_FIRST :
        for (Jar srce : providers) {
          copy(dest, srce, path, false);
        }
        break;

      case SPLIT_ERROR :
        error(diagnostic(path, providers));
        break;

      case SPLIT_FIRST :
        copy(dest, providers.get(0), path, false);
        break;

      default :
        if (providers.size() > 1)
          warning("%s", diagnostic(path, providers));
        for (Jar srce : providers) {
          copy(dest, srce, path, false);
        }
        break;
    }
  }

  /**
   * Cop
   *
   * @param dest
   * @param srce
   * @param path
   * @param overwriteResource
   */
  private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
    trace("copy d=" + dest + " s=" + srce + " p=" + path);
    dest.copy(srce, path, overwrite);

    // bnd.info sources must be preprocessed
    String bndInfoPath = path + "/bnd.info";
    Resource r = dest.getResource(bndInfoPath);
    if (r != null && !(r instanceof PreprocessResource)) {
      trace("preprocessing bnd.info");
      PreprocessResource pp = new PreprocessResource(this, r);
      dest.putResource(bndInfoPath, pp);
    }

    if (hasSources()) {
      String srcPath = "OSGI-OPT/src/" + path;
      Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
      if (srcContents != null) {
        dest.addDirectory(srcContents, overwrite);
      }
    }
  }

  /**
   * Analyze the classpath for a split package
   *
   * @param pack
   * @param classpath
   * @param source
   * @return
   */
  private String diagnostic(String pack, List<Jar> culprits) {
    // Default is like merge-first, but with a warning
    return "Split package, multiple jars provide the same package:"
        + pack
        + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
        + "Package found in   " + culprits + "\n" //
        + "Class path         " + getClasspath();
  }

  private int getSplitStrategy(String type) {
    if (type == null)
      return SPLIT_DEFAULT;

    if (type.equals("merge-last"))
      return SPLIT_MERGE_LAST;

    if (type.equals("merge-first"))
      return SPLIT_MERGE_FIRST;

    if (type.equals("error"))
      return SPLIT_ERROR;

    if (type.equals("first"))
      return SPLIT_FIRST;

    error("Invalid strategy for split-package: " + type);
    return SPLIT_DEFAULT;
  }

  /**
   * Matches the instructions against a package.
   *
   * @param instructions
   *            The list of instructions
   * @param pack
   *            The name of the package
   * @param unused
   *            The total list of patterns, matched patterns are removed
   * @param source
   *            The name of the source container, can be filtered upon with
   *            the from: directive.
   * @return
   */
  private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
    for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
      Instruction pattern = entry.getKey();

      // It is possible to filter on the source of the
      // package with the from: directive. This is an
      // instruction that must match the name of the
      // source class path entry.

      String from = entry.getValue().get(FROM_DIRECTIVE);
      if (from != null) {
        Instruction f = new Instruction(from);
        if (!f.matches(source) || f.isNegated())
          continue;
      }

      // Now do the normal
      // matching
      if (pattern.matches(pack)) {
        if (unused != null)
          unused.remove(pattern);
        return pattern;
      }
    }
    return null;
  }

  /**
   * Parse the Bundle-Includes header. Files in the bundles Include header are
   * included in the jar. The source can be a directory or a file.
   *
   * @throws IOException
   * @throws FileNotFoundException
   */
  private void doIncludeResources(Jar jar) throws Exception {
    String includes = getProperty("Bundle-Includes");
    if (includes == null) {
      includes = getProperty(INCLUDERESOURCE);
      if (includes == null || includes.length() == 0)
        includes = getProperty("Include-Resource");
    } else
      warning("Please use -includeresource instead of Bundle-Includes");

    doIncludeResource(jar, includes);

  }

  private void doIncludeResource(Jar jar, String includes) throws Exception {
    Parameters clauses = parseHeader(includes);
    doIncludeResource(jar, clauses);
  }

  private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
    for (Entry<String,Attrs> entry : clauses.entrySet()) {
      doIncludeResource(jar, entry.getKey(), entry.getValue());
    }
  }

  private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
      Exception {

    boolean preprocess = false;
    boolean absentIsOk = false;

    if (name.startsWith("{") && name.endsWith("}")) {
      preprocess = true;
      name = name.substring(1, name.length() - 1).trim();
    }

    String parts[] = name.split("\\s*=\\s*");
    String source = parts[0];
    String destination = parts[0];
    if (parts.length == 2)
      source = parts[1];

    if (source.startsWith("-")) {
      source = source.substring(1);
      absentIsOk = true;
    }

    if (source.startsWith("@")) {
      extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
    } else if (extra.containsKey("cmd")) {
      doCommand(jar, source, destination, extra, preprocess, absentIsOk);
    } else if (extra.containsKey(LITERAL_ATTRIBUTE)) {
      String literal = extra.get(LITERAL_ATTRIBUTE);
      Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
      String x = extra.get("extra");
      if (x != null)
        r.setExtra(x);
      jar.putResource(name, r);
    } else {
      File sourceFile;
      String destinationPath;

      sourceFile = getFile(source);
      if (parts.length == 1) {
        // Directories should be copied to the root
        // but files to their file name ...
        if (sourceFile.isDirectory())
          destinationPath = "";
        else
          destinationPath = sourceFile.getName();
      } else {
        destinationPath = parts[0];
      }
      // Handle directories
      if (sourceFile.isDirectory()) {
        destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
        return;
      }

      // destinationPath = checkDestinationPath(destinationPath);

      if (!sourceFile.exists()) {
        if (absentIsOk)
          return;

        noSuchFile(jar, name, extra, source, destinationPath);
      } else
        copy(jar, destinationPath, sourceFile, preprocess, extra);
    }
  }

  /**
   * It is possible in Include-Resource to use a system command that generates
   * the contents, this is indicated with {@code cmd} attribute. The command
   * can be repeated for a number of source files with the {@code for}
   * attribute which indicates a list of repetitions, often down with the
   * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
   * repetition will repeat the given command for each item. The @} macro can
   * be used to replace the current item. If no {@code for} is given, the
   * source is used as the only item. If the destination contains a macro,
   * each iteration will create a new file, otherwise the destination name is
   * used.
   *
   * @param jar
   * @param source
   * @param destination
   * @param extra
   * @param preprocess
   * @param absentIsOk
   * @throws Exception
   */
  private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
      boolean absentIsOk) throws Exception {
    String repeat = extra.get("for"); // TODO constant
    if (repeat == null)
      repeat = source;

    Collection<String> requires = split(extra.get("requires"));
    long lastModified = 0;
    for (String required : requires) {
      File file = getFile(required);
      if (!file.exists()) {
        error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
            file.getAbsoluteFile());
      } else
        lastModified = findLastModifiedWhileOlder(file, lastModified());
    }

    String cmd = extra.get("cmd");

    List<String> paths = new ArrayList<String>();

    for (String item : Processor.split(repeat)) {
      File f = IO.getFile(item);
      traverse(paths,f);
    }

    CombinedResource cr = null;

    if (!destination.contains("${@}")) {
      cr = new CombinedResource();
      cr.lastModified = lastModified;
    }

    setProperty("@requires", join(requires, " "));
    try {
      for (String item : paths) {
        setProperty("@", item);
        try {
          String path = getReplacer().process(destination);
          String command = getReplacer().process(cmd);
          File file = getFile(item);
          if (file.exists())
            lastModified = Math.max(lastModified, file.lastModified());

          CommandResource cmdresource = new CommandResource(command, this, lastModified, getBase());

          Resource r = cmdresource;

          // Turn this resource into a file resource
          // so we execute the command now and catch its
          // errors
          FileResource fr = new FileResource(r);

          addClose(fr);
          r = fr;

          if (preprocess)
            r = new PreprocessResource(this, r);

          if (cr == null)
            jar.putResource(path, r);
          else
            cr.addResource(r);
        }
        finally {
          unsetProperty("@");
        }
      }
    }
    finally {
      unsetProperty("@requires");
    }

    // Add last so the correct modification date is used
    // to update the modified time.
    if (cr != null)
      jar.putResource(destination, cr);

    updateModified(lastModified, "Include-Resource: cmd");
  }

  private void traverse(List<String> paths, File item) {
   
    if (item.isDirectory()) {
      for ( File sub : item.listFiles()) {
        traverse(paths, sub);
      }
    } else if ( item.isFile())
      paths.add(item.getAbsolutePath());
    else
      paths.add(item.getName());
  }

  /**
   * Check if a file or directory is older than the given time.
   *
   * @param file
   * @param lastModified
   * @return
   */
  private long findLastModifiedWhileOlder(File file, long lastModified) {
    if (file.isDirectory()) {
      File children[] = file.listFiles();
      for (File child : children) {
        if (child.lastModified() > lastModified)
          return child.lastModified();

        long lm = findLastModifiedWhileOlder(child, lastModified);
        if (lm > lastModified)
          return lm;
      }
    }
    return file.lastModified();
  }

  private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
      String destinationPath) throws Exception {
    String filter = extra.get("filter:");
    boolean flatten = isTrue(extra.get("flatten:"));
    boolean recursive = true;
    String directive = extra.get("recursive:");
    if (directive != null) {
      recursive = isTrue(directive);
    }

    Instruction.Filter iFilter = null;
    if (filter != null) {
      iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
    } else {
      iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
    }

    Map<String,File> files = newMap();
    resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);

    for (Map.Entry<String,File> entry : files.entrySet()) {
      copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
    }
    return destinationPath;
  }

  private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
      boolean flatten) {

    if (doNotCopy(dir.getName())) {
      return;
    }

    File[] fs = dir.listFiles(filter);
    for (File file : fs) {
      if (file.isDirectory()) {
        if (recursive) {
          String nextPath;
          if (flatten)
            nextPath = path;
          else
            nextPath = appendPath(path, file.getName());

          resolveFiles(file, filter, recursive, nextPath, files, flatten);
        }
        // Directories are ignored otherwise
      } else {
        String p = appendPath(path, file.getName());
        if (files.containsKey(p))
          warning("Include-Resource overwrites entry %s from file %s", p, file);
        files.put(p, file);
      }
    }
    if (fs.length == 0) {
      File empty = new File(dir, Constants.EMPTY_HEADER);
      files.put(appendPath(path, empty.getName()), empty);
    }
  }

  private void noSuchFile(Jar jar, @SuppressWarnings("unused")
  String clause, Map<String,String> extra, String source, String destinationPath) throws Exception {
    Jar src = getJarFromName(source, "Include-Resource " + source);
    if (src != null) {
      // Do not touch the manifest so this also
      // works for signed files.
      src.setDoNotTouchManifest();
      JarResource jarResource = new JarResource(src);
      jar.putResource(destinationPath, jarResource);
    } else {
      Resource lastChance = make.process(source);
      if (lastChance != null) {
        String x = extra.get("extra");
        if (x != null)
          lastChance.setExtra(x);
        jar.putResource(destinationPath, lastChance);
      } else
        error("Input file does not exist: " + source);
    }
  }

  /**
   * Extra resources from a Jar and add them to the given jar. The clause is
   * the
   *
   * @param jar
   * @param clauses
   * @param i
   * @throws ZipException
   * @throws IOException
   */
  private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
      IOException {
    // Inline all resources and classes from another jar
    // optionally appended with a modified regular expression
    // like @zip.jar!/META-INF/MANIFEST.MF
    int n = source.lastIndexOf("!/");
    Instruction instr = null;
    if (n > 0) {
      instr = new Instruction(source.substring(n + 2));
      source = source.substring(0, n);
    }

    // Pattern filter = null;
    // if (n > 0) {
    // String fstring = source.substring(n + 2);
    // source = source.substring(0, n);
    // filter = wildcard(fstring);
    // }
    Jar sub = getJarFromName(source, "extract from jar");
    if (sub == null) {
      if (absentIsOk)
        return;

      error("Can not find JAR file " + source);
    } else {
      addAll(jar, sub, instr, destination);
    }
  }

  /**
   * Add all the resources in the given jar that match the given filter.
   *
   * @param sub
   *            the jar
   * @param filter
   *            a pattern that should match the resoures in sub to be added
   */
  public boolean addAll(Jar to, Jar sub, Instruction filter) {
    return addAll(to, sub, filter, "");
  }

  /**
   * Add all the resources in the given jar that match the given filter.
   *
   * @param sub
   *            the jar
   * @param filter
   *            a pattern that should match the resoures in sub to be added
   */
  public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
    boolean dupl = false;
    for (String name : sub.getResources().keySet()) {
      if ("META-INF/MANIFEST.MF".equals(name))
        continue;

      if (filter == null || filter.matches(name) != filter.isNegated())
        dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
    }
    return dupl;
  }

  private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
    if (doNotCopy(from.getName()))
      return;

    if (from.isDirectory()) {

      File files[] = from.listFiles();
      for (int i = 0; i < files.length; i++) {
        copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
      }
    } else {
      if (from.exists()) {
        Resource resource = new FileResource(from);
        if (preprocess) {
          resource = new PreprocessResource(this, resource);
        }
        String x = extra.get("extra");
        if (x != null)
          resource.setExtra(x);
        if (path.endsWith("/"))
          path = path + from.getName();
        jar.putResource(path, resource);

        if (isTrue(extra.get(LIB_DIRECTIVE))) {
          setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
        }
      } else if (from.getName().equals(Constants.EMPTY_HEADER)) {
        jar.putResource(path, new EmbeddedResource(new byte[0], 0));
      } else {
        error("Input file does not exist: " + from);
      }
    }
  }

  public void setSourcepath(File[] files) {
    for (int i = 0; i < files.length; i++)
      addSourcepath(files[i]);
  }

  public void addSourcepath(File cp) {
    if (!cp.exists())
      warning("File on sourcepath that does not exist: " + cp);

    sourcePath.add(cp);
  }

  @Override
  public void close() {
    super.close();
  }

  /**
   * Build Multiple jars. If the -sub command is set, we filter the file with
   * the given patterns.
   *
   * @return
   * @throws Exception
   */
  public Jar[] builds() throws Exception {
    begin();

    // Are we acting as a conduit for another JAR?
    String conduit = getProperty(CONDUIT);
    if (conduit != null) {
      Parameters map = parseHeader(conduit);
      Jar[] result = new Jar[map.size()];
      int n = 0;
      for (String file : map.keySet()) {
        Jar c = new Jar(getFile(file));
        addClose(c);
        String name = map.get(file).get("name");
        if (name != null)
          c.setName(name);

        result[n++] = c;
      }
      return result;
    }

    List<Jar> result = new ArrayList<Jar>();
    List<Builder> builders;

    builders = getSubBuilders();

    for (Builder builder : builders) {
      try {
        Jar jar = builder.build();
        jar.setName(builder.getBsn());
        result.add(jar);
      }
      catch (Exception e) {
        e.printStackTrace();
        error("Sub Building " + builder.getBsn(), e);
      }
      if (builder != this)
        getInfo(builder, builder.getBsn() + ": ");
    }
    return result.toArray(new Jar[result.size()]);
  }

  /**
   * Answer a list of builders that represent this file or a list of files
   * specified in -sub. This list can be empty. These builders represents to
   * be created artifacts and are each scoped to such an artifacts. The
   * builders can be used to build the bundles or they can be used to find out
   * information about the to be generated bundles.
   *
   * @return List of 0..n builders representing artifacts.
   * @throws Exception
   */
  public List<Builder> getSubBuilders() throws Exception {
    String sub = getProperty(SUB);
    if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
      return Arrays.asList(this);

    List<Builder> builders = new ArrayList<Builder>();
    if (isTrue(getProperty(NOBUNDLES)))
      return builders;

    Parameters subsMap = parseHeader(sub);
    for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
      File file = getFile(i.next());
      if (file.isFile() && !file.getName().startsWith(".")) {
        builders.add(getSubBuilder(file));
        i.remove();
      }
    }

    Instructions instructions = new Instructions(subsMap);

    List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));

    nextFile: while (members.size() > 0) {

      File file = members.remove(0);

      // Check if the file is one of our parents
      Processor p = this;
      while (p != null) {
        if (file.equals(p.getPropertiesFile()))
          continue nextFile;
        p = p.getParent();
      }

      for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {

        Instruction instruction = i.next();
        if (instruction.matches(file.getName())) {

          if (!instruction.isNegated()) {
            builders.add(getSubBuilder(file));
          }

          // Because we matched (even though we could be negated)
          // we skip any remaining searches
          continue nextFile;
        }
      }
    }
    return builders;
  }

  public Builder getSubBuilder(File file) throws Exception {
    Builder builder = getSubBuilder();
    if (builder != null) {
      builder.setProperties(file);
      addClose(builder);
    }
    return builder;
  }

  public Builder getSubBuilder() throws Exception {
    Builder builder = new Builder(this);
    builder.setBase(getBase());

    for (Jar file : getClasspath()) {
      builder.addClasspath(file);
    }

    return builder;
  }

  /**
   * A macro to convert a maven version to an OSGi version
   */

  public String _maven_version(String args[]) {
    if (args.length > 2)
      error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
    else if (args.length < 2)
      error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
    else {
      return cleanupVersion(args[1]);
    }
    return null;
  }

  public String _permissions(String args[]) {
    StringBuilder sb = new StringBuilder();

    for (String arg : args) {
      if ("packages".equals(arg) || "all".equals(arg)) {
        for (PackageRef imp : getImports().keySet()) {
          if (!imp.isJava()) {
            sb.append("(org.osgi.framework.PackagePermission \"");
            sb.append(imp);
            sb.append("\" \"import\")\r\n");
          }
        }
        for (PackageRef exp : getExports().keySet()) {
          sb.append("(org.osgi.framework.PackagePermission \"");
          sb.append(exp);
          sb.append("\" \"export\")\r\n");
        }
      } else if ("admin".equals(arg) || "all".equals(arg)) {
        sb.append("(org.osgi.framework.AdminPermission)");
      } else if ("permissions".equals(arg))
        ;
      else
        error("Invalid option in ${permissions}: %s", arg);
    }
    return sb.toString();
  }

  /**
     *
     */
  public void removeBundleSpecificHeaders() {
    Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
    setForceLocal(set);
  }

  /**
   * Check if the given resource is in scope of this bundle. That is, it
   * checks if the Include-Resource includes this resource or if it is a class
   * file it is on the class path and the Export-Package or Private-Package
   * include this resource.
   *
   * @param f
   * @return
   */
  public boolean isInScope(Collection<File> resources) throws Exception {
    Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
    clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
    if (isTrue(getProperty(Constants.UNDERTEST))) {
      clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
    }

    Collection<String> ir = getIncludedResourcePrefixes();

    Instructions instructions = new Instructions(clauses);

    for (File r : resources) {
      String cpEntry = getClasspathEntrySuffix(r);

      if (cpEntry != null) {

        if (cpEntry.equals("")) // Meaning we actually have a CPE
          return true;

        String pack = Descriptors.getPackage(cpEntry);
        Instruction i = matches(instructions, pack, null, r.getName());
        if (i != null)
          return !i.isNegated();
      }

      // Check if this resource starts with one of the I-C header
      // paths.
      String path = r.getAbsolutePath();
      for (String p : ir) {
        if (path.startsWith(p))
          return true;
      }
    }
    return false;
  }

  /**
   * Extra the paths for the directories and files that are used in the
   * Include-Resource header.
   *
   * @return
   */
  private Collection<String> getIncludedResourcePrefixes() {
    List<String> prefixes = new ArrayList<String>();
    Parameters includeResource = getIncludeResource();
    for (Entry<String,Attrs> p : includeResource.entrySet()) {
      if (p.getValue().containsKey("literal"))
        continue;

      Matcher m = IR_PATTERN.matcher(p.getKey());
      if (m.matches()) {
        File f = getFile(m.group(1));
        prefixes.add(f.getAbsolutePath());
      }
    }
    return prefixes;
  }

  /**
   * Answer the string of the resource that it has in the container. It is
   * possible that the resource is a classpath entry. In that case an empty
   * string is returned.
   *
   * @param resource
   *            The resource to look for
   * @return A suffix on the classpath or "" if the resource is a class path
   *         entry
   * @throws Exception
   */
  public String getClasspathEntrySuffix(File resource) throws Exception {
    for (Jar jar : getClasspath()) {
      File source = jar.getSource();
      if (source != null) {

        source = source.getCanonicalFile();
        String sourcePath = source.getAbsolutePath();
        String resourcePath = resource.getAbsolutePath();
        if (sourcePath.equals(resourcePath))
          return ""; // Matches a classpath entry

        if (resourcePath.startsWith(sourcePath)) {
          // Make sure that the path name is translated correctly
          // i.e. on Windows the \ must be translated to /
          String filePath = resourcePath.substring(sourcePath.length() + 1);

          return filePath.replace(File.separatorChar, '/');
        }
      }
    }
    return null;
  }

  /**
   * doNotCopy The doNotCopy variable maintains a patter for files that should
   * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
   * ca be overridden with the {@link Constants#DONOTCOPY} property.
   */

  public boolean doNotCopy(String v) {
    return getDoNotCopy().matcher(v).matches();
  }

  public Pattern getDoNotCopy() {
    if (xdoNotCopy == null) {
      String string = null;
      try {
        string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
        xdoNotCopy = Pattern.compile(string);
      }
      catch (Exception e) {
        error("Invalid value for %s, value is %s", DONOTCOPY, string);
        xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
      }
    }
    return xdoNotCopy;
  }

  /**
   */

  static MakeBnd      makeBnd        = new MakeBnd();
  static MakeCopy      makeCopy      = new MakeCopy();
  static ServiceComponent  serviceComponent  = new ServiceComponent();
  static DSAnnotations  dsAnnotations    = new DSAnnotations();
  static MetatypePlugin  metatypePlugin    = new MetatypePlugin();

  @Override
  protected void setTypeSpecificPlugins(Set<Object> list) {
    list.add(makeBnd);
    list.add(makeCopy);
    list.add(serviceComponent);
    list.add(dsAnnotations);
    list.add(metatypePlugin);
    super.setTypeSpecificPlugins(list);
  }

  /**
   * Diff this bundle to another bundle for the given packages.
   *
   * @throws Exception
   */

  public void doDiff(@SuppressWarnings("unused")
  Jar dot) throws Exception {
    Parameters diffs = parseHeader(getProperty("-diff"));
    if (diffs.isEmpty())
      return;

    trace("diff %s", diffs);

    if (tree == null)
      tree = differ.tree(this);

    for (Entry<String,Attrs> entry : diffs.entrySet()) {
      String path = entry.getKey();
      File file = getFile(path);
      if (!file.isFile()) {
        error("Diffing against %s that is not a file", file);
        continue;
      }

      boolean full = entry.getValue().get("--full") != null;
      boolean warning = entry.getValue().get("--warning") != null;

      Tree other = differ.tree(file);
      Diff api = tree.diff(other).get("<api>");
      Instructions instructions = new Instructions(entry.getValue().get("--pack"));

      trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
      for (Diff p : api.getChildren()) {
        String pname = p.getName();
        if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
          if (p.getDelta() != Delta.UNCHANGED) {

            if (!full)
              if (warning)
                warning("Differ %s", p);
              else
                error("Differ %s", p);
            else {
              if (warning)
                warning("Diff found a difference in %s for packages %s", file, instructions);
              else
                error("Diff found a difference in %s for packages %s", file, instructions);
              show(p, "", warning);
            }
          }
        }
      }
    }
  }

  /**
   * Show the diff recursively
   *
   * @param p
   * @param i
   */
  private void show(Diff p, String indent, boolean warning) {
    Delta d = p.getDelta();
    if (d == Delta.UNCHANGED)
      return;

    if (warning)
      warning("%s%s", indent, p);
    else
      error("%s%s", indent, p);

    indent = indent + " ";
    switch (d) {
      case CHANGED :
      case MAJOR :
      case MINOR :
      case MICRO :
        break;

      default :
        return;
    }
    for (Diff c : p.getChildren())
      show(c, indent, warning);
  }

  public void addSourcepath(Collection<File> sourcepath) {
    for (File f : sourcepath) {
      addSourcepath(f);
    }
  }

  /**
   * Base line against a previous version. Should be overridden in the
   * ProjectBuilder where we have access to the repos
   *
   * @throws Exception
   */

  protected void doBaseline(Jar dot) throws Exception {}
}
TOP

Related Classes of aQute.bnd.osgi.Builder

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.