Temporarily add BND library code to the build, to try out some patches
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@723235 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
new file mode 100644
index 0000000..bdc2c7a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
@@ -0,0 +1,1128 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.*;
+import java.security.spec.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.bnd.make.*;
+import aQute.lib.signing.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ *
+ * Private-Package: package-decl ( ',' package-decl )*
+ *
+ * Export-Package: package-decl ( ',' package-decl )*
+ *
+ * Import-Package: package-decl ( ',' package-decl )*
+ *
+ * @version $Revision: 1.4 $
+ */
+public class Builder extends Analyzer {
+ private static final int SPLIT_MERGE_LAST = 1;
+ private static final int SPLIT_MERGE_FIRST = 2;
+ private static final int SPLIT_ERROR = 3;
+ private static final int SPLIT_FIRST = 4;
+ private static final int SPLIT_DEFAULT = 0;
+
+ List<File> sourcePath = new ArrayList<File>();
+ Pattern NAME_URL = Pattern
+ .compile("(.*)(http://.*)");
+
+ Make make = new Make(this);
+ private KeyStore keystore;
+
+ public Builder(Processor parent) {
+ super(parent);
+ }
+
+ public Builder() {
+ }
+
+ public Jar build() throws Exception {
+ if (getProperty(NOPE) != null)
+ return null;
+
+ String sub = getProperty(SUB);
+ if (sub != null && sub.trim().length() > 0)
+ error("Specified "
+ + SUB
+ + " but calls build() instead of builds() (might be a programmer error)");
+
+ if (getProperty(CONDUIT) != null)
+ error("Specified "
+ + CONDUIT
+ + " but calls build() instead of builds() (might be a programmer error");
+
+ dot = new Jar("dot");
+ addClose(dot);
+ try {
+ long modified = Long.parseLong(getProperty("base.modified"));
+ dot.updateModified(modified, "Base modified");
+ } catch (Exception e) {
+ }
+
+ doExpand(dot);
+ doIncludeResources(dot);
+
+ doConditional(dot);
+
+ // NEW!
+ // 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);
+ }
+ }
+
+ dot.setManifest(manifest);
+
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+ if (getProperty(POM) != null)
+ doPom(dot);
+
+ doVerify(dot);
+
+ if (dot.getResources().isEmpty())
+ error("The JAR is empty");
+
+ dot.updateModified(lastModified(), "Last Modified Processor");
+ dot.setName(getBsn());
+
+ sign(dot);
+ return dot;
+ }
+
+ /**
+ * Sign the jar file.
+ *
+ * -sign : <alias> [ ';' 'password:=' <password> ] [ ';' 'keystore:='
+ * <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
+ *
+ * @return
+ */
+
+ void sign(Jar jar) throws Exception {
+ String signing = getProperty("-sign");
+ if (signing == null)
+ return;
+
+ trace("Signing %s, with %s", getBsn(), signing);
+
+ Map<String, Map<String, String>> infos = parseHeader(signing);
+ for (Map.Entry<String, Map<String, String>> entry : infos.entrySet()) {
+ String alias = entry.getKey();
+ String keystoreLocation = entry.getValue().get(
+ KEYSTORE_LOCATION_DIRECTIVE);
+ String keystoreProvider = entry.getValue().get(
+ KEYSTORE_PROVIDER_DIRECTIVE);
+ String password = entry.getValue().get(KEYSTORE_PASSWORD_DIRECTIVE);
+ String signpassword = entry.getValue().get(SIGN_PASSWORD_DIRECTIVE);
+ KeyStore keystore = getKeystore(keystoreLocation, keystoreProvider,
+ password);
+ if (keystore == null) {
+ error(
+ "Cannot find keystore to sign bundle: location=%s, provider=%s",
+ keystoreLocation, keystoreProvider);
+ } else {
+ if (signpassword == null && !"-none".equals(signpassword))
+ signpassword = password;
+
+ X509Certificate chain[] = getChain(keystore, alias);
+ if (chain == null) {
+ error(
+ "Trying to sign bundle but no signing certificate found: %s",
+ alias);
+ continue;
+ }
+
+ try {
+ Key key = keystore.getKey(alias,
+ (signpassword == null ? null : signpassword
+ .toCharArray()));
+ KeyFactory keyFactory = KeyFactory.getInstance(key
+ .getAlgorithm());
+ KeySpec keySpec = keyFactory.getKeySpec(key,
+ RSAPrivateKeySpec.class);
+ PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+
+ JarSigner signer = new JarSigner(alias, privateKey, chain);
+ signer.signJar(jar);
+
+ } catch (UnrecoverableKeyException uke) {
+ error(
+ "Cannot get key to sign, likely invalid password: %s, for %s : %s",
+ signpassword, alias, uke);
+ }
+ }
+ }
+ }
+
+ X509Certificate[] getChain(KeyStore keystore, String alias)
+ throws Exception {
+ java.security.cert.Certificate[] chain = keystore
+ .getCertificateChain(alias);
+ X509Certificate certChain[] = new X509Certificate[chain.length];
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ for (int count = 0; count < chain.length; count++) {
+ ByteArrayInputStream certIn = new ByteArrayInputStream(chain[count]
+ .getEncoded());
+ X509Certificate cert = (X509Certificate) cf
+ .generateCertificate(certIn);
+ certChain[count] = cert;
+ }
+ return certChain;
+ }
+
+ KeyStore getKeystore(String keystoreLocation, String keystoreProvider,
+ String password) throws Exception {
+ if (keystoreLocation == null) {
+ return getKeystore();
+ }
+ if (keystoreProvider == null)
+ keystoreProvider = "JKS";
+
+ KeyStore keystore = KeyStore.getInstance(keystoreProvider);
+ FileInputStream in = new FileInputStream(keystoreLocation);
+ try {
+ keystore.load(in, password == null ? null : password.toCharArray());
+ } finally {
+ in.close();
+ }
+ return keystore;
+ }
+
+ KeyStore getKeystore() throws Exception {
+ if (keystore != null) {
+ return keystore;
+ }
+
+ Map<String, Map<String, String>> header = parseHeader(getProperty(
+ "-keystore", "../cnf/keystore"));
+ if (header.size() == 0) {
+ error("Keystore needed but no -keystore specified");
+ return null;
+ }
+
+ if (header.size() > 1) {
+ warning("Multiple keystores specified, can only specify one: %s",
+ header);
+ }
+ for (Map.Entry<String, Map<String, String>> entry : header.entrySet()) {
+ return keystore = getKeystore(entry.getKey(), entry.getValue().get(
+ "provider:"), entry.getValue().get("password:"));
+ }
+ return null;
+ }
+
+ public boolean hasSources() {
+ return isTrue(getProperty(SOURCES));
+ }
+
+ protected String getImportPackages() {
+ String ip = super.getImportPackages();
+ if (ip != null)
+ return ip;
+
+ return "*";
+ }
+
+ private void doConditional(Jar dot) throws IOException {
+ Map<String, Map<String, String>> conditionals = getHeader(CONDITIONAL_PACKAGE);
+ int size;
+ do {
+ size = dot.getDirectories().size();
+ analyze();
+ analyzed = false;
+ Map<String, Map<String, String>> imports = getImports();
+
+ // Match the packages specified in conditionals
+ // against the imports. Any match must become a
+ // Private-Package
+ Map<String, Map<String, String>> filtered = merge(
+ CONDITIONAL_PACKAGE, conditionals, imports,
+ new HashSet<String>());
+
+ // Imports can also specify a private import. These
+ // packages must also be copied to the bundle
+ for (Map.Entry<String, Map<String, String>> entry : getImports()
+ .entrySet()) {
+ String type = entry.getValue().get("import:");
+ if (type != null && type.equals("private"))
+ filtered.put(entry.getKey(), entry.getValue());
+ }
+
+ // remove existing packages to prevent merge errors
+ filtered.keySet().removeAll(dot.getPackages());
+ doExpand(dot, CONDITIONAL_PACKAGE + " Private imports",
+ replaceWitInstruction(filtered, CONDITIONAL_PACKAGE), false);
+ } while (dot.getDirectories().size() > size);
+ analyzed = true;
+ }
+
+ /**
+ * Intercept the call to analyze and cleanup versions after we have analyzed
+ * the setup. We do not want to cleanup if we are going to verify.
+ */
+
+ public void analyze() throws IOException {
+ super.analyze();
+ cleanupVersion(imports);
+ cleanupVersion(exports);
+ String version = getProperty(BUNDLE_VERSION);
+ if (version != null)
+ setProperty(BUNDLE_VERSION, cleanupVersion(version));
+ }
+
+ public void cleanupVersion(Map<String, Map<String, String>> mapOfMap) {
+ for (Iterator<Map.Entry<String, Map<String, String>>> e = mapOfMap
+ .entrySet().iterator(); e.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = e.next();
+ Map<String, String> attributes = entry.getValue();
+ if (attributes.containsKey("version")) {
+ attributes.put("version", cleanupVersion(attributes
+ .get("version")));
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private void addSources(Jar dot) {
+ if (!hasSources())
+ return;
+
+ Set<String> packages = new HashSet<String>();
+
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ getProperties().store(out, "Generated by BND, at " + new Date());
+ dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out
+ .toByteArray(), 0));
+ out.close();
+ } catch (Exception e) {
+ error("Can not embed bnd file in JAR: " + e);
+ }
+
+ for (Iterator<String> cpe = classspace.keySet().iterator(); cpe
+ .hasNext();) {
+ String path = cpe.next();
+ path = path.substring(0, path.length() - ".class".length())
+ + ".java";
+ String pack = getPackage(path).replace('.', '/');
+ if (pack.length() > 1)
+ pack = pack + "/";
+ 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();
+ File f = getFile(root, path);
+ if (f.exists()) {
+ found = true;
+ if (!packages.contains(pack)) {
+ packages.add(pack);
+ File bdir = getFile(root, pack);
+ for (int j = 0; j < fixed.length; j++) {
+ File ff = getFile(bdir, fixed[j]);
+ if (ff.isFile()) {
+ dot.putResource("OSGI-OPT/src/" + pack
+ + fixed[j], new FileResource(ff));
+ }
+ }
+ }
+ dot
+ .putResource("OSGI-OPT/src/" + path,
+ new FileResource(f));
+ }
+ }
+ if (!found) {
+ for (Jar jar : classpath) {
+ Resource resource = jar.getResource(path);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src", resource);
+ } else {
+ resource = jar.getResource("OSGI-OPT/src/" + path);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src", 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;
+
+ public Collection<File> getSourcePath() {
+ if (firstUse) {
+ firstUse = false;
+ String sp = getProperty(SOURCEPATH);
+ if (sp != null) {
+ Map<String, Map<String, String>> 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(Jar dot) throws Exception {
+ Verifier verifier = new Verifier(dot, getProperties());
+ verifier.setPedantic(isPedantic());
+
+ // Give the verifier the benefit of our analysis
+ // prevents parsing the files twice
+ verifier.setClassSpace(classspace, contained, referred, uses);
+ verifier.verify();
+ getInfo(verifier);
+ }
+
+ private void doExpand(Jar jar) throws IOException {
+ if (getClasspath().size() == 0
+ && (getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+ warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+ Map<Instruction, Map<String, String>> all = newMap();
+
+ all.putAll(replaceWitInstruction(getHeader(EXPORT_PACKAGE),
+ EXPORT_PACKAGE));
+
+ all.putAll(replaceWitInstruction(getHeader(PRIVATE_PACKAGE),
+ PRIVATE_PACKAGE));
+
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ all.putAll(replaceWitInstruction(parseHeader(getProperty(
+ Constants.TESTPACKAGES, "test;presence:=optional")),
+ TESTPACKAGES));
+ }
+
+ if (all.isEmpty() && !isResourceOnly()) {
+ warning("Neither Export-Package, Private-Package, -testpackages is set, therefore no packages will be included");
+ }
+
+ doExpand(jar, "Export-Package, Private-Package, or -testpackages", all,
+ true);
+ }
+
+ /**
+ *
+ * @param jar
+ * @param name
+ * @param instructions
+ */
+ private void doExpand(Jar jar, String name,
+ Map<Instruction, Map<String, String>> instructions,
+ boolean mandatory) {
+ Set<Instruction> superfluous = removeMarkedDuplicates(instructions
+ .keySet());
+
+ for (Iterator<Jar> c = getClasspath().iterator(); c.hasNext();) {
+ Jar now = c.next();
+ doExpand(jar, instructions, now, superfluous);
+ }
+
+ if (mandatory && superfluous.size() > 0) {
+ StringBuffer sb = new StringBuffer();
+ String del = "Instructions in " + name + " that are never used: ";
+ for (Iterator<Instruction> i = superfluous.iterator(); i.hasNext();) {
+ Instruction p = i.next();
+ sb.append(del);
+ sb.append(p.getPattern());
+ del = ", ";
+ }
+ warning(sb.toString());
+ }
+ }
+
+ /**
+ * Iterate over each directory in the class path entry and check if that
+ * directory is a desired package.
+ *
+ * @param included
+ * @param classpathEntry
+ */
+ private void doExpand(Jar jar,
+ Map<Instruction, Map<String, String>> included, Jar classpathEntry,
+ Set<Instruction> superfluous) {
+
+ loop: for (Map.Entry<String, Map<String, Resource>> directory : classpathEntry
+ .getDirectories().entrySet()) {
+ String path = directory.getKey();
+
+ if (doNotCopy.matcher(getName(path)).matches())
+ continue;
+
+ if (directory.getValue() == null)
+ continue;
+
+ String pack = path.replace('/', '.');
+ Instruction instr = matches(included, pack, superfluous);
+ if (instr != null) {
+ // System.out.println("Pattern match: " + pack + " " +
+ // instr.getPattern() + " " + instr.isNegated());
+ if (!instr.isNegated()) {
+ Map<String, Resource> contents = directory.getValue();
+
+ // What to do with split packages? Well if this
+ // directory already exists, we will check the strategy
+ // and react accordingly.
+ boolean overwriteResource = true;
+ if (jar.hasDirectory(path)) {
+ Map<String, String> directives = included.get(instr);
+
+ switch (getSplitStrategy((String) directives
+ .get(SPLIT_PACKAGE_DIRECTIVE))) {
+ case SPLIT_MERGE_LAST:
+ overwriteResource = true;
+ break;
+
+ case SPLIT_MERGE_FIRST:
+ overwriteResource = false;
+ break;
+
+ case SPLIT_ERROR:
+ error(diagnostic(pack, getClasspath(),
+ classpathEntry.source));
+ continue loop;
+
+ case SPLIT_FIRST:
+ continue loop;
+
+ default:
+ warning(diagnostic(pack, getClasspath(),
+ classpathEntry.source));
+ overwriteResource = false;
+ break;
+ }
+ }
+
+ jar.addDirectory(contents, overwriteResource);
+
+ String key = path + "/bnd.info";
+ Resource r = jar.getResource(key);
+ if (r != null)
+ jar.putResource(key, new PreprocessResource(this, r));
+
+ if (hasSources()) {
+ String srcPath = "OSGI-INF/src/" + path;
+ Map<String, Resource> srcContents = classpathEntry
+ .getDirectories().get(srcPath);
+ if (srcContents != null) {
+ jar.addDirectory(srcContents, overwriteResource);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Analyze the classpath for a split package
+ *
+ * @param pack
+ * @param classpath
+ * @param source
+ * @return
+ */
+ private String diagnostic(String pack, List<Jar> classpath, File source) {
+ // Default is like merge-first, but with a warning
+ // Find the culprits
+ pack = pack.replace('.', '/');
+ List<Jar> culprits = new ArrayList<Jar>();
+ for (Iterator<Jar> i = classpath.iterator(); i.hasNext();) {
+ Jar culprit = (Jar) i.next();
+ if (culprit.getDirectories().containsKey(pack)) {
+ culprits.add(culprit);
+ }
+ }
+ return "Split package "
+ + pack
+ + "\nUse directive -split-package:=(merge-first|merge-last|error|first) on Export/Private Package instruction to get rid of this warning\n"
+ + "Package found in " + culprits + "\n"
+ + "Reference from " + source + "\n" + "Classpath "
+ + classpath;
+ }
+
+ private int getSplitStrategy(String type) {
+ if (type == null)
+ return SPLIT_DEFAULT;
+
+ if (type.equals("merge-last"))
+ return SPLIT_MERGE_LAST;
+
+ if (type.equals("merge-first"))
+ return SPLIT_MERGE_FIRST;
+
+ if (type.equals("error"))
+ return SPLIT_ERROR;
+
+ if (type.equals("first"))
+ return SPLIT_FIRST;
+
+ error("Invalid strategy for split-package: " + type);
+ return SPLIT_DEFAULT;
+ }
+
+ private Map<Instruction, Map<String, String>> replaceWitInstruction(
+ Map<String, Map<String, String>> header, String type) {
+ Map<Instruction, Map<String, String>> map = newMap();
+ for (Iterator<Map.Entry<String, Map<String, String>>> e = header
+ .entrySet().iterator(); e.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = e.next();
+ String pattern = entry.getKey();
+ Instruction instr = Instruction.getPattern(pattern);
+ String presence = entry.getValue().get(PRESENCE_DIRECTIVE);
+ if ("optional".equals(presence))
+ instr.setOptional();
+ map.put(instr, entry.getValue());
+ }
+ return map;
+ }
+
+ private Instruction matches(
+ Map<Instruction, Map<String, String>> instructions, String pack,
+ Set<Instruction> superfluousPatterns) {
+ for (Instruction pattern : instructions.keySet()) {
+ if (pattern.matches(pack)) {
+ superfluousPatterns.remove(pattern);
+ return pattern;
+ }
+ }
+ return null;
+ }
+
+ private Map<String, Map<String, String>> getHeader(String string) {
+ if (string == null)
+ return Collections.emptyMap();
+ return parseHeader(getProperty(string));
+ }
+
+ /**
+ * Parse the Bundle-Includes header. Files in the bundles Include header are
+ * included in the jar. The source can be a directory or a file.
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ private void doIncludeResources(Jar jar) throws Exception {
+ String includes = getProperty("Bundle-Includes");
+ if (includes == null)
+ includes = getProperty("Include-Resource");
+ else
+ warning("Please use Include-Resource instead of Bundle-Includes");
+
+ if (includes == null)
+ return;
+
+ Map<String, Map<String, String>> clauses = parseHeader(includes);
+
+ for (Iterator<Map.Entry<String, Map<String, String>>> i = clauses
+ .entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = i.next();
+ doIncludeResource(jar, entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void doIncludeResource(Jar jar, String name,
+ Map<String, String> extra) throws ZipException, IOException,
+ Exception {
+ boolean preprocess = false;
+ if (name.startsWith("{") && name.endsWith("}")) {
+ preprocess = true;
+ name = name.substring(1, name.length() - 1).trim();
+ }
+
+ if (name.startsWith("@")) {
+ extractFromJar(jar, name.substring(1));
+ } else
+ /*
+ * NEW
+ */
+ if (extra.containsKey("literal")) {
+ String literal = (String) extra.get("literal");
+ Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
+ String x = (String) extra.get("extra");
+ if (x != null)
+ r.setExtra(x);
+ jar.putResource(name, r);
+ } else {
+ String source;
+ File sourceFile;
+ String destinationPath;
+
+ String parts[] = name.split("\\s*=\\s*");
+ if (parts.length == 1) {
+ // Just a copy, destination path defined by
+ // source path.
+ source = parts[0];
+ sourceFile = getFile(source);
+ // Directories should be copied to the root
+ // but files to their file name ...
+ if (sourceFile.isDirectory())
+ destinationPath = "";
+ else
+ destinationPath = sourceFile.getName();
+ } else {
+ source = parts[1];
+ sourceFile = getFile(source);
+ destinationPath = parts[0];
+ }
+
+ // Some people insist on ending a directory with
+ // a slash ... it now also works if you do /=dir
+ if (destinationPath.endsWith("/"))
+ destinationPath = destinationPath.substring(0, destinationPath
+ .length() - 1);
+
+ if (!sourceFile.exists()) {
+ noSuchFile(jar, name, extra, source, destinationPath);
+ } else
+ copy(jar, destinationPath, sourceFile, preprocess, extra);
+ }
+ }
+
+ private void noSuchFile(Jar jar, String clause, Map<String, String> extra,
+ String source, String destinationPath) throws Exception {
+ Jar src = getJarFromName(source, "Include-Resource " + source);
+ if (src != null) {
+ JarResource jarResource = new JarResource(src);
+ jar.putResource(destinationPath, jarResource);
+ } else {
+ Resource lastChance = make.process(source);
+ if (lastChance != null) {
+ 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 name) throws ZipException,
+ IOException {
+ // Inline all resources and classes from another jar
+ // optionally appended with a modified regular expression
+ // like @zip.jar!/META-INF/MANIFEST.MF
+ int n = name.lastIndexOf("!/");
+ Pattern filter = null;
+ if (n > 0) {
+ String fstring = name.substring(n + 2);
+ name = name.substring(0, n);
+ filter = wildcard(fstring);
+ }
+ Jar sub = getJarFromName(name, "extract from jar");
+ if (sub == null)
+ error("Can not find JAR file " + name);
+ else
+ jar.addAll(sub, filter);
+ }
+
+ private Pattern wildcard(String spec) {
+ StringBuffer sb = new StringBuffer();
+ for (int j = 0; j < spec.length(); j++) {
+ char c = spec.charAt(j);
+ switch (c) {
+ case '.':
+ sb.append("\\.");
+ break;
+
+ case '*':
+ // test for ** (all directories)
+ if (j < spec.length() - 1 && spec.charAt(j + 1) == '*') {
+ sb.append(".*");
+ j++;
+ } else
+ sb.append("[^/]*");
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ String s = sb.toString();
+ try {
+ return Pattern.compile(s);
+ } catch (Exception e) {
+ error("Invalid regular expression on wildcarding: " + spec
+ + " used *");
+ }
+ return null;
+ }
+
+ private void copy(Jar jar, String path, File from, boolean preprocess,
+ Map<String, String> extra) throws Exception {
+ if (doNotCopy.matcher(from.getName()).matches())
+ return;
+
+ if (from.isDirectory()) {
+ String next = path;
+ if (next.length() != 0)
+ next += '/';
+
+ File files[] = from.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ copy(jar, next + files[i].getName(), files[i], preprocess,
+ extra);
+ }
+ } else {
+ if (from.exists()) {
+ Resource resource = new FileResource(from);
+ if (preprocess) {
+ resource = new PreprocessResource(this, resource);
+ }
+ jar.putResource(path, resource);
+ } else {
+ error("Input file does not exist: " + from);
+ }
+ }
+ }
+
+ private String getName(String where) {
+ int n = where.lastIndexOf('/');
+ if (n < 0)
+ return where;
+
+ return where.substring(n + 1);
+ }
+
+ 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);
+ }
+
+ /**
+ * Create a POM reseource for Maven containing as much information as
+ * possible from the manifest.
+ *
+ * @param output
+ * @param builder
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void doPom(Jar dot) throws FileNotFoundException, IOException {
+ {
+ Manifest manifest = dot.getManifest();
+ String name = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_NAME);
+ String description = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_DESCRIPTION);
+ String docUrl = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_DOCURL);
+ String version = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_VERSION);
+ String bundleVendor = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_VENDOR);
+ ByteArrayOutputStream s = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(s);
+ String bsn = manifest.getMainAttributes().getValue(
+ Analyzer.BUNDLE_SYMBOLICNAME);
+ String licenses = manifest.getMainAttributes().getValue(
+ BUNDLE_LICENSE);
+
+ if (bsn == null) {
+ error("Can not create POM unless Bundle-SymbolicName is set");
+ return;
+ }
+
+ bsn = bsn.trim();
+ int n = bsn.lastIndexOf('.');
+ if (n <= 0) {
+ error("Can not create POM unless Bundle-SymbolicName contains a .");
+ ps.close();
+ s.close();
+ return;
+ }
+ String groupId = bsn.substring(0, n);
+ String artifactId = bsn.substring(n + 1);
+ ps
+ .println("<project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd'>");
+ ps.println(" <modelVersion>4.0.0</modelVersion>");
+ ps.println(" <groupId>" + groupId + "</groupId>");
+
+ n = artifactId.indexOf(';');
+ if (n > 0)
+ artifactId = artifactId.substring(0, n).trim();
+
+ ps.println(" <artifactId>" + artifactId + "</artifactId>");
+ ps.println(" <version>" + version + "</version>");
+ if (description != null) {
+ ps.println(" <description>");
+ ps.print(" ");
+ ps.println(description);
+ ps.println(" </description>");
+ }
+ if (name != null) {
+ ps.print(" <name>");
+ ps.print(name);
+ ps.println("</name>");
+ }
+ if (docUrl != null) {
+ ps.print(" <url>");
+ ps.print(docUrl);
+ ps.println("</url>");
+ }
+
+ if (bundleVendor != null) {
+ Matcher m = NAME_URL.matcher(bundleVendor);
+ String namePart = bundleVendor;
+ String urlPart = null;
+ if (m.matches()) {
+ namePart = m.group(1);
+ urlPart = m.group(2);
+ }
+ ps.println(" <organization>");
+ ps.print(" <name>");
+ ps.print(namePart.trim());
+ ps.println("</name>");
+ if (urlPart != null) {
+ ps.print(" <url>");
+ ps.print(urlPart.trim());
+ ps.println("</url>");
+ }
+ ps.println(" </organization>");
+ }
+ if (licenses != null) {
+ ps.println(" <licenses>");
+ Map<String, Map<String, String>> map = parseHeader(licenses);
+ for (Iterator<Map.Entry<String, Map<String, String>>> e = map
+ .entrySet().iterator(); e.hasNext();) {
+ Map.Entry<String, Map<String, String>> entry = e.next();
+ ps.println(" <license>");
+ Map<String, String> values = entry.getValue();
+ print(ps, values, "name", "name", (String) values
+ .get("url"));
+ print(ps, values, "url", "url", null);
+ print(ps, values, "distribution", "distribution", "repo");
+ ps.println(" </license>");
+ }
+ ps.println(" </licenses>");
+ }
+ ps.println("</project>");
+ ps.close();
+ s.close();
+ dot
+ .putResource("pom.xml", new EmbeddedResource(s
+ .toByteArray(), 0));
+ }
+ }
+
+ /**
+ * Utility function to print a tag from a map
+ *
+ * @param ps
+ * @param values
+ * @param string
+ * @param tag
+ * @param object
+ */
+ private void print(PrintStream ps, Map<String, String> values,
+ String string, String tag, String object) {
+ String value = (String) values.get(string);
+ if (value == null)
+ value = object;
+ if (value == null)
+ return;
+ ps.println(" <" + tag + ">" + value.trim() + "</" + tag + ">");
+ }
+
+ public void close() {
+ super.close();
+ }
+
+ /**
+ * 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) {
+ Map<String, Map<String, String>> 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;
+ }
+
+ // If no -sub property, then reuse this builder object
+ // other wise, build all the sub parts.
+ String sub = getProperty(SUB);
+ if (sub == null) {
+ Jar jar = build();
+ if (jar == null)
+ return new Jar[0];
+
+ return new Jar[] { jar };
+ }
+
+ List<Jar> result = new ArrayList<Jar>();
+
+ // Get the Instruction objects that match the sub header
+ Set<Instruction> subs = replaceWitInstruction(parseHeader(sub), SUB)
+ .keySet();
+
+ // Get the member files of this directory
+ List<File> members = new ArrayList<File>(Arrays.asList(getBase()
+ .listFiles()));
+
+ getProperties().remove(SUB);
+ // For each member file
+ nextFile: while (members.size() > 0) {
+
+ File file = members.remove(0);
+ if (file.equals(getPropertiesFile()))
+ continue nextFile;
+
+ for (Iterator<Instruction> i = subs.iterator(); i.hasNext();) {
+
+ Instruction instruction = i.next();
+ if (instruction.matches(file.getName())) {
+
+ if (!instruction.isNegated()) {
+
+ Builder builder = null;
+ try {
+ builder = getSubBuilder();
+ addClose(builder);
+ builder.setProperties(file);
+ builder.setProperty(SUB, "");
+ // Recursively build
+ // TODO
+ Jar jar = builder.build();
+ jar.setName(builder.getBsn());
+ result.add(jar);
+ } catch (Exception e) {
+ e.printStackTrace();
+ error("Sub Building " + file, e);
+ }
+ if (builder != null)
+ getInfo(builder, file.getName() + ": ");
+ }
+
+ // Because we matched (even though we could be negated)
+ // we skip any remaining searches
+ continue nextFile;
+ }
+ }
+ }
+ setProperty(SUB, sub);
+ return result.toArray(new Jar[result.size()]);
+ }
+
+ protected Builder getSubBuilder() throws Exception {
+ return new Builder(this);
+ }
+
+ /**
+ * 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[]) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ for (String arg : args) {
+ if ("packages".equals(arg) || "all".equals(arg)) {
+ for (String imp : getImports().keySet()) {
+ if (!imp.startsWith("java.")) {
+ sb.append("(org.osgi.framework.PackagePermission \"");
+ sb.append(imp);
+ sb.append("\" \"import\")\r\n");
+ }
+ }
+ for (String 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();
+ }
+
+}