FELIX-1262: add local Bnd source to apply temporary patches
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793527 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/lib/filter/Filter.java b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
new file mode 100644
index 0000000..e505a81
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/filter/Filter.java
@@ -0,0 +1,353 @@
+/**
+ * Copyright (c) 2000 Gatespace AB. All Rights Reserved.
+ *
+ * Gatespace grants Open Services Gateway Initiative (OSGi) an irrevocable,
+ * perpetual, non-exclusive, worldwide, paid-up right and license to
+ * reproduce, display, perform, prepare and have prepared derivative works
+ * based upon and distribute and sublicense this material and derivative
+ * works thereof as set out in the OSGi MEMBER AGREEMENT as of January 24
+ * 2000, for use in accordance with Section 2.2 of the BY-LAWS of the
+ * OSGi MEMBER AGREEMENT.
+ */
+
+package aQute.lib.filter;
+
+import java.lang.reflect.*;
+import java.math.*;
+import java.util.*;
+
+public class Filter {
+ final char WILDCARD = 65535;
+
+ final int EQ = 0;
+ final int LE = 1;
+ final int GE = 2;
+ final int APPROX = 3;
+
+ private String filter;
+
+ abstract class Query {
+ static final String GARBAGE = "Trailing garbage";
+ static final String MALFORMED = "Malformed query";
+ static final String EMPTY = "Empty list";
+ static final String SUBEXPR = "No subexpression";
+ static final String OPERATOR = "Undefined operator";
+ static final String TRUNCATED = "Truncated expression";
+ static final String EQUALITY = "Only equality supported";
+
+ private String tail;
+
+ boolean match() throws IllegalArgumentException {
+ tail = filter;
+ boolean val = doQuery();
+ if (tail.length() > 0)
+ error(GARBAGE);
+ return val;
+ }
+
+ private boolean doQuery() throws IllegalArgumentException {
+ if (tail.length() < 3 || !prefix("("))
+ error(MALFORMED);
+ boolean val;
+
+ switch (tail.charAt(0)) {
+ case '&':
+ val = doAnd();
+ break;
+ case '|':
+ val = doOr();
+ break;
+ case '!':
+ val = doNot();
+ break;
+ default:
+ val = doSimple();
+ break;
+ }
+
+ if (!prefix(")"))
+ error(MALFORMED);
+ return val;
+ }
+
+ private boolean doAnd() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ boolean val = true;
+ if (!tail.startsWith("("))
+ error(EMPTY);
+ do {
+ if (!doQuery())
+ val = false;
+ } while (tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doOr() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ boolean val = false;
+ if (!tail.startsWith("("))
+ error(EMPTY);
+ do {
+ if (doQuery())
+ val = true;
+ } while (tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doNot() throws IllegalArgumentException {
+ tail = tail.substring(1);
+ if (!tail.startsWith("("))
+ error(SUBEXPR);
+ return !doQuery();
+ }
+
+ private boolean doSimple() throws IllegalArgumentException {
+ int op = 0;
+ Object attr = getAttr();
+
+ if (prefix("="))
+ op = EQ;
+ else if (prefix("<="))
+ op = LE;
+ else if (prefix(">="))
+ op = GE;
+ else if (prefix("~="))
+ op = APPROX;
+ else
+ error(OPERATOR);
+
+ return compare(attr, op, getValue());
+ }
+
+ private boolean prefix(String pre) {
+ if (!tail.startsWith(pre))
+ return false;
+ tail = tail.substring(pre.length());
+ return true;
+ }
+
+ private Object getAttr() {
+ int len = tail.length();
+ int ix = 0;
+ label: for (; ix < len; ix++) {
+ switch (tail.charAt(ix)) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '=':
+ case '~':
+ case '*':
+ case '\\':
+ break label;
+ }
+ }
+ String attr = tail.substring(0, ix).toLowerCase();
+ tail = tail.substring(ix);
+ return getProp(attr);
+ }
+
+ abstract Object getProp(String key);
+
+ private String getValue() {
+ StringBuffer sb = new StringBuffer();
+ int len = tail.length();
+ int ix = 0;
+ label: for (; ix < len; ix++) {
+ char c = tail.charAt(ix);
+ switch (c) {
+ case '(':
+ case ')':
+ break label;
+ case '*':
+ sb.append(WILDCARD);
+ break;
+ case '\\':
+ if (ix == len - 1)
+ break label;
+ sb.append(tail.charAt(++ix));
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ tail = tail.substring(ix);
+ return sb.toString();
+ }
+
+ private void error(String m) throws IllegalArgumentException {
+ throw new IllegalArgumentException(m + " " + tail);
+ }
+
+ private boolean compare(Object obj, int op, String s) {
+ if (obj == null)
+ return false;
+ try {
+ Class<?> numClass = obj.getClass();
+ if (numClass == String.class) {
+ return compareString((String) obj, op, s);
+ } else if (numClass == Character.class) {
+ return compareString(obj.toString(), op, s);
+ } else if (numClass == Long.class) {
+ return compareSign(op, Long.valueOf(s)
+ .compareTo((Long) obj));
+ } else if (numClass == Integer.class) {
+ return compareSign(op, Integer.valueOf(s).compareTo(
+ (Integer) obj));
+ } else if (numClass == Short.class) {
+ return compareSign(op, Short.valueOf(s).compareTo(
+ (Short) obj));
+ } else if (numClass == Byte.class) {
+ return compareSign(op, Byte.valueOf(s)
+ .compareTo((Byte) obj));
+ } else if (numClass == Double.class) {
+ return compareSign(op, Double.valueOf(s).compareTo(
+ (Double) obj));
+ } else if (numClass == Float.class) {
+ return compareSign(op, Float.valueOf(s).compareTo(
+ (Float) obj));
+ } else if (numClass == Boolean.class) {
+ if (op != EQ)
+ return false;
+ int a = Boolean.valueOf(s).booleanValue() ? 1 : 0;
+ int b = ((Boolean) obj).booleanValue() ? 1 : 0;
+ return compareSign(op, a - b);
+ } else if (numClass == BigInteger.class) {
+ return compareSign(op, new BigInteger(s)
+ .compareTo((BigInteger) obj));
+ } else if (numClass == BigDecimal.class) {
+ return compareSign(op, new BigDecimal(s)
+ .compareTo((BigDecimal) obj));
+ } else if (obj instanceof Collection) {
+ for (Object x : (Collection<?>) obj)
+ if (compare(x, op, s))
+ return true;
+ } else if (numClass.isArray()) {
+ int len = Array.getLength(obj);
+ for (int i = 0; i < len; i++)
+ if (compare(Array.get(obj, i), op, s))
+ return true;
+ }
+ } catch (Exception e) {
+ }
+ return false;
+ }
+ }
+
+ class DictQuery extends Query {
+ private Dictionary<?,?> dict;
+
+ DictQuery(Dictionary<?,?> dict) {
+ this.dict = dict;
+ }
+
+ Object getProp(String key) {
+ return dict.get(key);
+ }
+ }
+
+ public Filter(String filter) throws IllegalArgumentException {
+ // NYI: Normalize the filter string?
+ this.filter = filter;
+ if (filter == null || filter.length() == 0)
+ throw new IllegalArgumentException("Null query");
+ }
+
+ public boolean match(Dictionary<?,?> dict) {
+ try {
+ return new DictQuery(dict).match();
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ public String verify() {
+ try {
+ new DictQuery(new Hashtable<Object,Object>()).match();
+ } catch (IllegalArgumentException e) {
+ return e.getMessage();
+ }
+ return null;
+ }
+
+ public String toString() {
+ return filter;
+ }
+
+ public boolean equals(Object obj) {
+ return obj != null && obj instanceof Filter
+ && filter.equals(((Filter) obj).filter);
+ }
+
+ public int hashCode() {
+ return filter.hashCode();
+ }
+
+ boolean compareString(String s1, int op, String s2) {
+ switch (op) {
+ case EQ:
+ return patSubstr(s1, s2);
+ case APPROX:
+ return fixupString(s2).equals(fixupString(s1));
+ default:
+ return compareSign(op, s2.compareTo(s1));
+ }
+ }
+
+ boolean compareSign(int op, int cmp) {
+ switch (op) {
+ case LE:
+ return cmp >= 0;
+ case GE:
+ return cmp <= 0;
+ case EQ:
+ return cmp == 0;
+ default: /* APPROX */
+ return cmp == 0;
+ }
+ }
+
+ String fixupString(String s) {
+ StringBuffer sb = new StringBuffer();
+ int len = s.length();
+ boolean isStart = true;
+ boolean isWhite = false;
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt(i);
+ if (Character.isWhitespace(c)) {
+ isWhite = true;
+ } else {
+ if (!isStart && isWhite)
+ sb.append(' ');
+ if (Character.isUpperCase(c))
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ isStart = false;
+ isWhite = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ boolean patSubstr(String s, String pat) {
+ if (s == null)
+ return false;
+ if (pat.length() == 0)
+ return s.length() == 0;
+ if (pat.charAt(0) == WILDCARD) {
+ pat = pat.substring(1);
+ for (;;) {
+ if (patSubstr(s, pat))
+ return true;
+ if (s.length() == 0)
+ return false;
+ s = s.substring(1);
+ }
+ } else {
+ if (s.length() == 0 || s.charAt(0) != pat.charAt(0))
+ return false;
+ return patSubstr(s.substring(1), pat.substring(1));
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/About.java b/bundleplugin/src/main/java/aQute/lib/osgi/About.java
new file mode 100644
index 0000000..52a2529
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/About.java
@@ -0,0 +1,48 @@
+/* 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 package contains a number of classes that assists by analyzing JARs and
+ * constructing bundles.
+ *
+ * The Analyzer class can be used to analyze an existing bundle and can create a
+ * manifest specification from proposed (wildcard) Export-Package,
+ * Bundle-Includes, and Import-Package headers.
+ *
+ * The Builder class can use the headers to construct a JAR from the classpath.
+ *
+ * The Verifier class can take an existing JAR and verify that all headers are
+ * correctly set. It will verify the syntax of the headers, match it against the
+ * proper contents, and verify imports and exports.
+ *
+ * A number of utility classes are available.
+ *
+ * Jar, provides an abstraction of a Jar file. It has constructors for creating
+ * a Jar from a stream, a directory, or a jar file. A Jar, keeps a collection
+ * Resource's. There are Resource implementations for File, from ZipFile, or from
+ * a stream (which copies the data). The Jar tries to minimize the work during
+ * build up so that it is cheap to use. The Resource's can be used to iterate
+ * over the names and later read the resources when needed.
+ *
+ * Clazz, provides a parser for the class files. This will be used to define the
+ * imports and exports.
+ *
+ * A key component in this library is the Map. Headers are translated to Maps of Maps. OSGi
+ * header syntax is like:
+ * <pre>
+ * header = clause ( ',' clause ) *
+ * clause = file ( ';' file ) * ( parameter ) *
+ * param = attr '=' value | directive ':=' value
+ * </pre>
+ * These headers are translated to a Map that contains all headers (the order is
+ * maintained). Each additional file in a header definition will have its own
+ * entry (only native code does not work this way). The clause is represented
+ * as another map. The ':' of directives is considered part of the name. This
+ * allows attributes and directives to be maintained in the clause map.
+ *
+ * @version $Revision: 1.1 $
+ */
+public class About {
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java
new file mode 100644
index 0000000..532b33e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/AbstractResource.java
@@ -0,0 +1,50 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public abstract class AbstractResource implements Resource {
+ String extra;
+ byte[] calculated;
+ long lastModified;
+
+ protected AbstractResource(long modified) {
+ lastModified = modified;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(getLocalBytes());
+ }
+
+ private byte[] getLocalBytes() throws IOException {
+ try {
+ if (calculated != null)
+ return calculated;
+
+ return calculated = getBytes();
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ IOException ee = new IOException("Opening resource");
+ ee.initCause(e);
+ throw ee;
+ }
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public void write(OutputStream out) throws IOException {
+ out.write(getLocalBytes());
+ }
+
+ abstract protected byte[] getBytes() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java b/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java
new file mode 100644
index 0000000..a529fbc
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Analyzer.java
@@ -0,0 +1,1596 @@
+/* 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.bnd.service.*;
+import aQute.lib.filter.*;
+
+public class Analyzer extends Processor {
+
+ static Pattern doNotCopy = Pattern
+ .compile("CVS|.svn");
+ static String version;
+ static Pattern versionPattern = Pattern
+ .compile("(\\d+\\.\\d+)\\.\\d+.*");
+ final Map<String, Map<String, String>> contained = newHashMap(); // package
+ final Map<String, Map<String, String>> referred = newHashMap(); // package
+ final Map<String, Set<String>> uses = newHashMap(); // package
+ Map<String, Clazz> classspace;
+ Map<String, Map<String, String>> exports;
+ Map<String, Map<String, String>> imports;
+ Map<String, Map<String, String>> bundleClasspath; // Bundle
+ final Map<String, Map<String, String>> ignored = newHashMap(); // Ignored
+ // packages
+ Jar dot;
+ Map<String, Map<String, String>> classpathExports;
+
+ String activator;
+
+ final List<Jar> classpath = newList();
+
+ static Properties bndInfo;
+
+ boolean analyzed;
+ String bsn;
+
+ public Analyzer(Processor parent) {
+ super(parent);
+ }
+
+ public Analyzer() {
+ }
+
+ /**
+ * Specifically for Maven
+ *
+ * @param properties
+ * the properties
+ */
+
+ public static Properties getManifest(File dirOrJar) throws IOException {
+ Analyzer analyzer = new Analyzer();
+ analyzer.setJar(dirOrJar);
+ Properties properties = new Properties();
+ properties.put(IMPORT_PACKAGE, "*");
+ properties.put(EXPORT_PACKAGE, "*");
+ analyzer.setProperties(properties);
+ Manifest m = analyzer.calcManifest();
+ Properties result = new Properties();
+ for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i
+ .hasNext();) {
+ Attributes.Name name = (Attributes.Name) i.next();
+ result.put(name.toString(), m.getMainAttributes().getValue(name));
+ }
+ return result;
+ }
+
+ /**
+ * Calcualtes the data structures for generating a manifest.
+ *
+ * @throws IOException
+ */
+ public void analyze() throws IOException {
+ if (!analyzed) {
+ analyzed = true;
+ classpathExports = newHashMap();
+ activator = getProperty(BUNDLE_ACTIVATOR);
+ bundleClasspath = parseHeader(getProperty(BUNDLE_CLASSPATH));
+
+ analyzeClasspath();
+
+ classspace = analyzeBundleClasspath(dot, bundleClasspath,
+ contained, referred, uses);
+
+ for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
+ if (plugin instanceof AnalyzerPlugin) {
+ AnalyzerPlugin analyzer = (AnalyzerPlugin) plugin;
+ try {
+ boolean reanalyze = analyzer.analyzeJar(this);
+ if (reanalyze)
+ classspace = analyzeBundleClasspath(dot,
+ bundleClasspath, contained, referred, uses);
+ } catch (Exception e) {
+ error("Plugin Analyzer " + analyzer
+ + " throws exception " + e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (activator != null) {
+ // Add the package of the activator to the set
+ // of referred classes. This must be done before we remove
+ // contained set.
+ int n = activator.lastIndexOf('.');
+ if (n > 0) {
+ referred.put(activator.substring(0, n),
+ new LinkedHashMap<String, String>());
+ }
+ }
+
+ referred.keySet().removeAll(contained.keySet());
+ if (referred.containsKey(".")) {
+ error("The default package '.' is not permitted by the Import-Package syntax. \n"
+ + " This can be caused by compile errors in Eclipse because Eclipse creates \n"
+ + "valid class files regardless of compile errors.\n"
+ + "The following package(s) import from the default package "
+ + getUsedBy("."));
+ }
+
+ Map<String, Map<String, String>> exportInstructions = parseHeader(getProperty(EXPORT_PACKAGE));
+ Map<String, Map<String, String>> additionalExportInstructions = parseHeader(getProperty(EXPORT_CONTENTS));
+ exportInstructions.putAll(additionalExportInstructions);
+ Map<String, Map<String, String>> importInstructions = parseHeader(getImportPackages());
+ Map<String, Map<String, String>> dynamicImports = parseHeader(getProperty(DYNAMICIMPORT_PACKAGE));
+
+ if (dynamicImports != null) {
+ // Remove any dynamic imports from the referred set.
+ referred.keySet().removeAll(dynamicImports.keySet());
+ }
+
+ Map<String, Map<String, String>> superfluous = newHashMap();
+ // Tricky!
+ for (Iterator<String> i = exportInstructions.keySet().iterator(); i
+ .hasNext();) {
+ String instr = i.next();
+ if (!instr.startsWith("!"))
+ superfluous.put(instr, exportInstructions.get(instr));
+ }
+
+ exports = merge("export-package", exportInstructions, contained,
+ superfluous.keySet(), null);
+
+ // disallow export of default package
+ exports.remove(".");
+
+ for (Iterator<Map.Entry<String, Map<String, String>>> i = superfluous
+ .entrySet().iterator(); i.hasNext();) {
+ // It is possible to mention metadata directories in the export
+ // explicitly, they are then exported and removed from the
+ // warnings. Note that normally metadata directories are not
+ // exported.
+ Map.Entry<String, Map<String, String>> entry = i.next();
+ String pack = entry.getKey();
+ if (isDuplicate(pack))
+ i.remove();
+ else if (isMetaData(pack)) {
+ exports.put(pack, entry.getValue());
+ i.remove();
+ }
+ }
+
+ if (!superfluous.isEmpty()) {
+ warning("Superfluous export-package instructions: "
+ + superfluous.keySet());
+ }
+
+ // Add all exports that do not have an -noimport: directive
+ // to the imports.
+ Map<String, Map<String, String>> referredAndExported = newMap(referred);
+ referredAndExported.putAll(addExportsToImports(exports));
+
+ // match the imports to the referred and exported packages,
+ // merge the info for matching packages
+ Set<String> extra = new TreeSet<String>(importInstructions.keySet());
+ imports = merge("import-package", importInstructions,
+ referredAndExported, extra, ignored);
+
+ // Instructions that have not been used could be superfluous
+ // or if they do not contain wildcards, should be added
+ // as extra imports, the user knows best.
+ for (Iterator<String> i = extra.iterator(); i.hasNext();) {
+ String p = i.next();
+ if (p.startsWith("!") || p.indexOf('*') >= 0
+ || p.indexOf('?') >= 0 || p.indexOf('[') >= 0) {
+ if (!isResourceOnly())
+ warning("Did not find matching referal for " + p);
+ } else {
+ Map<String, String> map = importInstructions.get(p);
+ imports.put(p, map);
+ }
+ }
+
+ // See what information we can find to augment the
+ // imports. I.e. look on the classpath
+ augmentImports();
+
+ // Add the uses clause to the exports
+ doUses(exports, uses, imports);
+ }
+ }
+
+ /**
+ * Copy the input collection into an output set but skip names that have
+ * been marked as duplicates or are optional.
+ *
+ * @param superfluous
+ * @return
+ */
+ Set<Instruction> removeMarkedDuplicates(Collection<Instruction> superfluous) {
+ Set<Instruction> result = new HashSet<Instruction>();
+ for (Iterator<Instruction> i = superfluous.iterator(); i.hasNext();) {
+ Instruction instr = (Instruction) i.next();
+ if (!isDuplicate(instr.getPattern()) && !instr.isOptional())
+ result.add(instr);
+ }
+ return result;
+ }
+
+ /**
+ * Analyzer has an empty default but the builder has a * as default.
+ *
+ * @return
+ */
+ protected String getImportPackages() {
+ return getProperty(IMPORT_PACKAGE);
+ }
+
+ /**
+ *
+ * @return
+ */
+ boolean isResourceOnly() {
+ return getProperty(RESOURCEONLY, "false").equalsIgnoreCase("true");
+ }
+
+ /**
+ * Answer the list of packages that use the given package.
+ */
+ Set<String> getUsedBy(String pack) {
+ Set<String> set = newSet();
+ for (Iterator<Map.Entry<String, Set<String>>> i = uses.entrySet()
+ .iterator(); i.hasNext();) {
+ Map.Entry<String, Set<String>> entry = i.next();
+ Set<String> used = entry.getValue();
+ if (used.contains(pack))
+ set.add(entry.getKey());
+ }
+ return set;
+ }
+
+ /**
+ * One of the main workhorses of this class. This will analyze the current
+ * setp and calculate a new manifest according to this setup. This method
+ * will also set the manifest on the main jar dot
+ *
+ * @return
+ * @throws IOException
+ */
+ public Manifest calcManifest() throws IOException {
+ analyze();
+ Manifest manifest = new Manifest();
+ Attributes main = manifest.getMainAttributes();
+
+ main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ main.putValue(BUNDLE_MANIFESTVERSION, "2");
+
+ boolean noExtraHeaders = "true"
+ .equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
+
+ 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:|" + IMPORT_DIRECTIVE, true);
+
+ if (exportHeader.length() > 0)
+ main.putValue(EXPORT_PACKAGE, exportHeader);
+ else
+ main.remove(EXPORT_PACKAGE);
+
+ Map<String, Map<String, String>> temp = removeKeys(imports, "java.");
+ if (!temp.isEmpty()) {
+ main.putValue(IMPORT_PACKAGE, printClauses(temp, "resolution:"));
+ } else {
+ main.remove(IMPORT_PACKAGE);
+ }
+
+ temp = newMap(contained);
+ temp.keySet().removeAll(exports.keySet());
+
+ if (!temp.isEmpty())
+ main.putValue(PRIVATE_PACKAGE, printClauses(temp, ""));
+ else
+ main.remove(PRIVATE_PACKAGE);
+
+ if (!ignored.isEmpty()) {
+ main.putValue(IGNORE_PACKAGE, printClauses(ignored, ""));
+ } else {
+ main.remove(IGNORE_PACKAGE);
+ }
+
+ if (bundleClasspath != null && !bundleClasspath.isEmpty())
+ main.putValue(BUNDLE_CLASSPATH, printClauses(bundleClasspath, ""));
+ else
+ main.remove(BUNDLE_CLASSPATH);
+
+
+ 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))) {
+ if (header.charAt(0) == '@')
+ doNameSection(manifest, header);
+ continue;
+ }
+
+ if (header.equals(BUNDLE_CLASSPATH)
+ || header.equals(EXPORT_PACKAGE)
+ || header.equals(IMPORT_PACKAGE))
+ continue;
+
+ if (header.equalsIgnoreCase("Name")) {
+ error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
+ 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 if (value.trim().equals("<<EMPTY>>"))
+ main.putValue(header, "");
+ else
+ main.putValue(header, value);
+ }
+ } else {
+ // TODO should we report?
+ }
+ }
+
+ //
+ // Calculate the bundle symbolic name if it is
+ // not set.
+ // 1. set
+ // 2. name of properties file (must be != bnd.bnd)
+ // 3. name of directory, which is usualy project name
+ //
+ String bsn = getBsn();
+ if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
+ main.putValue(BUNDLE_SYMBOLICNAME, bsn);
+ }
+
+ //
+ // Use the same name for the bundle name as BSN when
+ // the bundle name is not set
+ //
+ if (main.getValue(BUNDLE_NAME) == null) {
+ main.putValue(BUNDLE_NAME, bsn);
+ }
+
+ if (main.getValue(BUNDLE_VERSION) == null)
+ main.putValue(BUNDLE_VERSION, "0");
+
+ // Copy old values into new manifest, when they
+ // exist in the old one, but not in the new one
+ merge(manifest, dot.getManifest());
+
+ // Remove all the headers mentioned in -removeheaders
+ Map<String, Map<String, String>> removes = parseHeader(getProperty(REMOVE_HEADERS));
+ for (Iterator<String> i = removes.keySet().iterator(); i.hasNext();) {
+ String header = i.next();
+ for (Iterator<Object> j = main.keySet().iterator(); j.hasNext();) {
+ Attributes.Name attr = (Attributes.Name) j.next();
+ if (attr.toString().matches(header)) {
+ j.remove();
+ progress("Removing header: " + header);
+ }
+ }
+ }
+
+ dot.setManifest(manifest);
+ return manifest;
+ }
+
+ /**
+ * This method is called when the header starts with a @, signifying a name
+ * section header. The name part is defined by replacing all the @ signs to
+ * a /, removing the first and the last, and using the last part as header
+ * name:
+ *
+ * <pre>
+ * @org@osgi@service@event@Implementation-Title
+ * </pre>
+ *
+ * This will be the header Implementation-Title in the
+ * org/osgi/service/event named section.
+ *
+ * @param manifest
+ * @param header
+ */
+ private void doNameSection(Manifest manifest, String header) {
+ String path = header.replace('@', '/');
+ int n = path.lastIndexOf('/');
+ // Must succeed because we start with @
+ String name = path.substring(n + 1);
+ // Skip first /
+ path = path.substring(1, n);
+ if (name.length() != 0 && path.length() != 0) {
+ Attributes attrs = manifest.getAttributes(path);
+ if (attrs == null) {
+ attrs = new Attributes();
+ manifest.getEntries().put(path, attrs);
+ }
+ attrs.putValue(name, getProperty(header));
+ } else {
+ warning(
+ "Invalid header (starts with @ but does not seem to be for the Name section): %s",
+ header);
+ }
+ }
+
+ /**
+ * Clear the key part of a header. I.e. remove everything from the first ';'
+ *
+ * @param value
+ * @return
+ */
+ public String getBsn() {
+ String value = getProperty(BUNDLE_SYMBOLICNAME);
+ if (value == null) {
+ if (getPropertiesFile() != null)
+ value = getPropertiesFile().getName();
+
+ if (value == null || value.equals("bnd.bnd"))
+ value = getBase().getName();
+ else if (value.endsWith(".bnd"))
+ value = value.substring(0, value.length() - 4);
+ }
+
+ if (value == null)
+ return "untitled";
+
+ int n = value.indexOf(';');
+ if (n > 0)
+ value = value.substring(0, n);
+ return value.trim();
+ }
+
+ public String _bsn(String args[]) {
+ return getBsn();
+ }
+
+ /**
+ * Calculate an export header solely based on the contents of a JAR file
+ *
+ * @param bundle
+ * The jar file to analyze
+ * @return
+ */
+ public String calculateExportsFromContents(Jar bundle) {
+ String ddel = "";
+ StringBuffer sb = new StringBuffer();
+ Map<String, Map<String, Resource>> map = bundle.getDirectories();
+ for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+ String directory = (String) i.next();
+ if (directory.equals("META-INF")
+ || directory.startsWith("META-INF/"))
+ continue;
+ if (directory.equals("OSGI-OPT")
+ || directory.startsWith("OSGI-OPT/"))
+ continue;
+ if (directory.equals("/"))
+ continue;
+
+ if (directory.endsWith("/"))
+ directory = directory.substring(0, directory.length() - 1);
+
+ directory = directory.replace('/', '.');
+ sb.append(ddel);
+ sb.append(directory);
+ ddel = ",";
+ }
+ return sb.toString();
+ }
+
+
+ public Map<String, Map<String, String>> getBundleClasspath() {
+ return bundleClasspath;
+ }
+
+ public Map<String, Map<String, String>> getContained() {
+ return contained;
+ }
+
+ public Map<String, Map<String, String>> getExports() {
+ return exports;
+ }
+
+ public Map<String, Map<String, String>> getImports() {
+ return imports;
+ }
+
+ public Jar getJar() {
+ return dot;
+ }
+
+ public Map<String, Map<String, String>> getReferred() {
+ return referred;
+ }
+
+ /**
+ * Return the set of unreachable code depending on exports and the bundle
+ * activator.
+ *
+ * @return
+ */
+ public Set<String> getUnreachable() {
+ Set<String> unreachable = new HashSet<String>(uses.keySet()); // all
+ for (Iterator<String> r = exports.keySet().iterator(); r.hasNext();) {
+ String packageName = r.next();
+ removeTransitive(packageName, unreachable);
+ }
+ if (activator != null) {
+ String pack = activator.substring(0, activator.lastIndexOf('.'));
+ removeTransitive(pack, unreachable);
+ }
+ return unreachable;
+ }
+
+ public Map<String, Set<String>> getUses() {
+ return uses;
+ }
+
+ /**
+ * Get the version from the manifest, a lot of work!
+ *
+ * @return version or unknown.
+ */
+ public String getVersion() {
+ return getBndInfo("version", "<unknown version>");
+ }
+
+ public long getBndLastModified() {
+ String time = getBndInfo("modified", "0");
+ try {
+ return Long.parseLong(time);
+ } catch (Exception e) {
+ }
+ return 0;
+ }
+
+ public String getBndInfo(String key, String defaultValue) {
+ if (bndInfo == null) {
+ bndInfo = new Properties();
+ try {
+ InputStream in = Analyzer.class.getResourceAsStream("bnd.info");
+ if (in != null) {
+ bndInfo.load(in);
+ in.close();
+ }
+ } catch (IOException ioe) {
+ warning("Could not read bnd.info in "
+ + Analyzer.class.getPackage() + ioe);
+ }
+ }
+ return bndInfo.getProperty(key, defaultValue);
+ }
+
+ /**
+ * Merge the existing manifest with the instructions.
+ *
+ * @param manifest
+ * The manifest to merge with
+ * @throws IOException
+ */
+ public void mergeManifest(Manifest manifest) throws IOException {
+ if (manifest != null) {
+ Attributes attributes = manifest.getMainAttributes();
+ for (Iterator<Object> i = attributes.keySet().iterator(); i
+ .hasNext();) {
+ Name name = (Name) i.next();
+ String key = name.toString();
+ // Dont want instructions
+ if (key.startsWith("-"))
+ continue;
+
+ if (getProperty(key) == null)
+ setProperty(key, (String) attributes.get(name));
+ }
+ }
+ }
+
+ public void setBase(File file) {
+ super.setBase(file);
+ getProperties().put("project.dir", getBase().getAbsolutePath());
+ }
+
+ /**
+ * Set the classpath for this analyzer by file.
+ *
+ * @param classpath
+ * @throws IOException
+ */
+ public void setClasspath(File[] classpath) throws IOException {
+ List<Jar> list = new ArrayList<Jar>();
+ for (int i = 0; i < classpath.length; i++) {
+ if (classpath[i].exists()) {
+ Jar current = new Jar(classpath[i]);
+ list.add(current);
+ } else {
+ error("Missing file on classpath: " + classpath[i]);
+ }
+ }
+ for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
+ addClasspath(i.next());
+ }
+ }
+
+ public void setClasspath(Jar[] classpath) {
+ for (int i = 0; i < classpath.length; i++) {
+ addClasspath(classpath[i]);
+ }
+ }
+
+ public void setClasspath(String[] classpath) {
+ for (int i = 0; i < classpath.length; i++) {
+ Jar jar = getJarFromName(classpath[i], " setting classpath");
+ if (jar != null)
+ addClasspath(jar);
+ }
+ }
+
+ /**
+ * Set the JAR file we are going to work in. This will read the JAR in
+ * memory.
+ *
+ * @param jar
+ * @return
+ * @throws IOException
+ */
+ public Jar setJar(File jar) throws IOException {
+ Jar jarx = new Jar(jar);
+ addClose(jarx);
+ return setJar(jarx);
+ }
+
+ /**
+ * Set the JAR directly we are going to work on.
+ *
+ * @param jar
+ * @return
+ */
+ public Jar setJar(Jar jar) {
+ this.dot = jar;
+ return jar;
+ }
+
+ protected void begin() {
+ super.begin();
+
+ updateModified(getBndLastModified(), "bnd last modified");
+ String doNotCopy = getProperty(DONOTCOPY);
+ if (doNotCopy != null)
+ Analyzer.doNotCopy = Pattern.compile(doNotCopy);
+
+ verifyManifestHeadersCase(getProperties());
+ }
+
+ /**
+ * Check if the given class or interface name is contained in the jar.
+ *
+ * @param interfaceName
+ * @return
+ */
+ public 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);
+ }
+
+ /**
+ * Try to get a Jar from a file name/path or a url, or in last resort from
+ * the classpath name part of their files.
+ *
+ * @param name
+ * URL or filename relative to the base
+ * @param from
+ * Message identifying the caller for errors
+ * @return null or a Jar with the contents for the name
+ */
+ Jar getJarFromName(String name, String from) {
+ File file = new File(name);
+ if (!file.isAbsolute())
+ file = new File(getBase(), name);
+
+ if (file.exists())
+ try {
+ Jar jar = new Jar(file);
+ addClose(jar);
+ return jar;
+ } catch (Exception e) {
+ error("Exception in parsing jar file for " + from + ": " + name
+ + " " + e);
+ }
+ // It is not a file ...
+ try {
+ // Lets try a URL
+ URL url = new URL(name);
+ Jar jar = new Jar(fileName(url.getPath()));
+ addClose(jar);
+ URLConnection connection = url.openConnection();
+ InputStream in = connection.getInputStream();
+ long lastModified = connection.getLastModified();
+ if (lastModified == 0)
+ // We assume the worst :-(
+ lastModified = System.currentTimeMillis();
+ EmbeddedResource.build(jar, in, lastModified);
+ in.close();
+ return jar;
+ } catch (IOException ee) {
+ // Check if we have files on the classpath
+ // that have the right name, allows us to specify those
+ // names instead of the full path.
+ for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
+ Jar entry = cp.next();
+ if (entry.source != null && entry.source.getName().equals(name)) {
+ return entry;
+ }
+ }
+ // error("Can not find jar file for " + from + ": " + name);
+ }
+ return null;
+ }
+
+ private String fileName(String path) {
+ int n = path.lastIndexOf('/');
+ if (n > 0)
+ return path.substring(n + 1);
+ return path;
+ }
+
+ /**
+ *
+ * @param manifest
+ * @throws Exception
+ */
+ void merge(Manifest result, Manifest old) throws IOException {
+ if (old != null) {
+ for (Iterator<Map.Entry<Object, Object>> e = old
+ .getMainAttributes().entrySet().iterator(); e.hasNext();) {
+ Map.Entry<Object, Object> entry = e.next();
+ Attributes.Name name = (Attributes.Name) entry.getKey();
+ String value = (String) entry.getValue();
+ if (name.toString().equalsIgnoreCase("Created-By"))
+ name = new Attributes.Name("Originally-Created-By");
+ if (!result.getMainAttributes().containsKey(name))
+ result.getMainAttributes().put(name, value);
+ }
+
+ // do not overwrite existing entries
+ Map<String, Attributes> oldEntries = old.getEntries();
+ Map<String, Attributes> newEntries = result.getEntries();
+ for (Iterator<Map.Entry<String, Attributes>> e = oldEntries
+ .entrySet().iterator(); e.hasNext();) {
+ Map.Entry<String, Attributes> entry = e.next();
+ if (!newEntries.containsKey(entry.getKey())) {
+ newEntries.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ String stem(String name) {
+ int n = name.lastIndexOf('.');
+ if (n > 0)
+ return name.substring(0, n);
+ else
+ return name;
+ }
+
+ /**
+ * Bnd is case sensitive for the instructions so we better check people are
+ * not using an invalid case. We do allow this to set headers that should
+ * not be processed by us but should be used by the framework.
+ *
+ * @param properties
+ * Properties to verify.
+ */
+
+ void verifyManifestHeadersCase(Properties properties) {
+ for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
+ String header = (String) i.next();
+ for (int j = 0; j < headers.length; j++) {
+ if (!headers[j].equals(header)
+ && headers[j].equalsIgnoreCase(header)) {
+ warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
+ + header + " and expecting: " + headers[j]);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * We will add all exports to the imports unless there is a -noimport
+ * directive specified on an export. This directive is skipped for the
+ * manifest.
+ *
+ * We also remove any version parameter so that augmentImports can do the
+ * version policy.
+ *
+ */
+ Map<String, Map<String, String>> addExportsToImports(
+ Map<String, Map<String, String>> exports) {
+ Map<String, Map<String, String>> importsFromExports = newHashMap();
+ for (Map.Entry<String, Map<String, String>> packageEntry : exports
+ .entrySet()) {
+ String packageName = packageEntry.getKey();
+ Map<String, String> parameters = packageEntry.getValue();
+ String noimport = (String) parameters.get(NO_IMPORT_DIRECTIVE);
+ if (noimport == null || !noimport.equalsIgnoreCase("true")) {
+ if (parameters.containsKey(VERSION_ATTRIBUTE)) {
+ parameters = newMap(parameters);
+ parameters.remove(VERSION_ATTRIBUTE);
+ }
+ importsFromExports.put(packageName, parameters);
+ }
+ }
+ return importsFromExports;
+ }
+
+ /**
+ * Create the imports/exports by parsing
+ *
+ * @throws IOException
+ */
+ void analyzeClasspath() throws IOException {
+ classpathExports = newHashMap();
+ for (Iterator<Jar> c = getClasspath().iterator(); c.hasNext();) {
+ Jar current = c.next();
+ checkManifest(current);
+ for (Iterator<String> j = current.getDirectories().keySet()
+ .iterator(); j.hasNext();) {
+ String dir = j.next();
+ Resource resource = current.getResource(dir + "/packageinfo");
+ if (resource != null) {
+ InputStream in = resource.openInputStream();
+ String version = parsePackageInfo(in);
+ in.close();
+ setPackageInfo(dir, "version", version);
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * @param jar
+ */
+ void checkManifest(Jar jar) {
+ try {
+ Manifest m = jar.getManifest();
+ if (m != null) {
+ String exportHeader = m.getMainAttributes().getValue(
+ EXPORT_PACKAGE);
+ if (exportHeader != null) {
+ Map<String, Map<String, String>> exported = parseHeader(exportHeader);
+ if (exported != null)
+ classpathExports.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 (String packageName : imports.keySet()) {
+ setProperty(CURRENT_PACKAGE, packageName);
+ try {
+ Map<String, String> importAttributes = imports.get(packageName);
+ Map<String, String> exporterAttributes = classpathExports
+ .get(packageName);
+ if (exporterAttributes == null)
+ exporterAttributes = exports.get(packageName);
+
+ if (exporterAttributes != null) {
+ augmentVersion(importAttributes, exporterAttributes);
+ augmentMandatory(importAttributes, exporterAttributes);
+ if (exporterAttributes.containsKey(IMPORT_DIRECTIVE))
+ importAttributes.put(IMPORT_DIRECTIVE,
+ exporterAttributes.get(IMPORT_DIRECTIVE));
+ }
+
+ // Convert any attribute values that have macros.
+ for (String key : importAttributes.keySet()) {
+ String value = importAttributes.get(key);
+ if (value.indexOf('$') >= 0) {
+ value = getReplacer().process(value);
+ importAttributes.put(key, value);
+ }
+ }
+
+ // You can add a remove-attribute: directive with a regular
+ // expression for attributes that need to be removed. We also
+ // remove all attributes that have a value of !. This allows
+ // you to use macros with ${if} to remove values.
+ String remove = importAttributes
+ .remove(REMOVE_ATTRIBUTE_DIRECTIVE);
+ Instruction removeInstr = null;
+
+ if (remove != null)
+ removeInstr = Instruction.getPattern(remove);
+
+ for (Iterator<Map.Entry<String, String>> i = importAttributes
+ .entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String, String> entry = i.next();
+ if (entry.getValue().equals("!"))
+ i.remove();
+ else if (removeInstr != null
+ && removeInstr.matches((String) entry.getKey()))
+ i.remove();
+ else {
+ // Not removed ...
+ }
+ }
+
+ } finally {
+ unsetProperty(CURRENT_PACKAGE);
+ }
+ }
+ }
+
+ /**
+ * If we use an import with mandatory attributes we better all use them
+ *
+ * @param currentAttributes
+ * @param exporter
+ */
+ private void augmentMandatory(Map<String, String> currentAttributes,
+ Map<String, String> exporter) {
+ String mandatory = (String) exporter.get("mandatory:");
+ if (mandatory != null) {
+ String[] attrs = mandatory.split("\\s*,\\s*");
+ for (int i = 0; i < attrs.length; i++) {
+ if (!currentAttributes.containsKey(attrs[i]))
+ currentAttributes.put(attrs[i], exporter.get(attrs[i]));
+ }
+ }
+ }
+
+ /**
+ * Check if we can augment the version from the exporter.
+ *
+ * We allow the version in the import to specify a @ which is replaced with
+ * the exporter's version.
+ *
+ * @param currentAttributes
+ * @param exporter
+ */
+ private void augmentVersion(Map<String, String> currentAttributes,
+ Map<String, String> exporter) {
+
+ String exportVersion = (String) exporter.get("version");
+ if (exportVersion == null)
+ exportVersion = (String) exporter.get("specification-version");
+ if (exportVersion == null)
+ return;
+
+ exportVersion = cleanupVersion(exportVersion);
+
+ setProperty("@", exportVersion);
+
+ String importRange = currentAttributes.get("version");
+ if (importRange != null) {
+ importRange = cleanupVersion(importRange);
+ importRange = getReplacer().process(importRange);
+ } else
+ importRange = getVersionPolicy();
+
+ unsetProperty("@");
+
+ // See if we can borrow the version
+ // we mist replace the ${@} with the version we
+ // found this can be useful if you want a range to start
+ // with the found version.
+ currentAttributes.put("version", importRange);
+ }
+
+ /**
+ * Add the uses clauses
+ *
+ * @param exports
+ * @param uses
+ * @throws MojoExecutionException
+ */
+ void doUses(Map<String, Map<String, String>> exports,
+ Map<String, Set<String>> uses,
+ Map<String, Map<String, String>> imports) {
+ if ("true".equalsIgnoreCase(getProperty(NOUSES)))
+ return;
+
+ for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
+ String packageName = i.next();
+ setProperty(CURRENT_PACKAGE, packageName);
+ try {
+ Map<String, String> clause = exports.get(packageName);
+ String override = clause.get(USES_DIRECTIVE);
+ if (override == null)
+ override = USES_USES;
+
+ Set<String> usedPackages = uses.get(packageName);
+ if (usedPackages != null) {
+ // Only do a uses on exported or imported packages
+ // and uses should also not contain our own package
+ // name
+ Set<String> sharedPackages = new HashSet<String>();
+ sharedPackages.addAll(imports.keySet());
+ sharedPackages.addAll(exports.keySet());
+ usedPackages.retainAll(sharedPackages);
+ usedPackages.remove(packageName);
+
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+ for (Iterator<String> u = usedPackages.iterator(); u
+ .hasNext();) {
+ String usedPackage = u.next();
+ if (!usedPackage.startsWith("java.")) {
+ sb.append(del);
+ sb.append(usedPackage);
+ del = ",";
+ }
+ }
+ if (override.indexOf('$') >= 0) {
+ setProperty(CURRENT_USES, sb.toString());
+ override = getReplacer().process(override);
+ unsetProperty(CURRENT_USES);
+ } else
+ // This is for backward compatibility 0.0.287
+ // can be deprecated over time
+ override = override
+ .replaceAll(USES_USES, sb.toString()).trim();
+ if (override.endsWith(","))
+ override = override.substring(0, override.length() - 1);
+ if (override.startsWith(","))
+ override = override.substring(1);
+ if (override.length() > 0) {
+ clause.put("uses:", override);
+ }
+ }
+ } finally {
+ unsetProperty(CURRENT_PACKAGE);
+ }
+ }
+ }
+
+ /**
+ * Transitively remove all elemens from unreachable through the uses link.
+ *
+ * @param name
+ * @param unreachable
+ */
+ void removeTransitive(String name, Set<String> unreachable) {
+ if (!unreachable.contains(name))
+ return;
+
+ unreachable.remove(name);
+
+ Set<String> ref = uses.get(name);
+ if (ref != null) {
+ for (Iterator<String> r = ref.iterator(); r.hasNext();) {
+ String element = (String) r.next();
+ removeTransitive(element, unreachable);
+ }
+ }
+ }
+
+ /**
+ * Helper method to set the package info
+ *
+ * @param dir
+ * @param key
+ * @param value
+ */
+ void setPackageInfo(String dir, String key, String value) {
+ if (value != null) {
+ String pack = dir.replace('/', '.');
+ Map<String, String> map = classpathExports.get(pack);
+ if (map == null) {
+ map = new HashMap<String, String>();
+ classpathExports.put(pack, map);
+ }
+ map.put(key, value);
+ }
+ }
+
+ public void close() {
+ super.close();
+ if (dot != null)
+ dot.close();
+
+ if (classpath != null)
+ for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
+ Jar jar = j.next();
+ jar.close();
+ }
+ }
+
+ /**
+ * Findpath looks through the contents of the JAR and finds paths that end
+ * with the given regular expression
+ *
+ * ${findpath (; reg-expr (; replacement)? )? }
+ *
+ * @param args
+ * @return
+ */
+ public String _findpath(String args[]) {
+ return findPath("findpath", args, true);
+ }
+
+ public String _findname(String args[]) {
+ return findPath("findname", args, false);
+ }
+
+ String findPath(String name, String[] args, boolean fullPathName) {
+ if (args.length > 3) {
+ warning("Invalid nr of arguments to " + name + " "
+ + Arrays.asList(args) + ", syntax: ${" + name
+ + " (; reg-expr (; replacement)? )? }");
+ return null;
+ }
+
+ String regexp = ".*";
+ String replace = null;
+
+ switch (args.length) {
+ case 3:
+ replace = args[2];
+ case 2:
+ regexp = args[1];
+ }
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+
+ Pattern expr = Pattern.compile(regexp);
+ for (Iterator<String> e = dot.getResources().keySet().iterator(); e
+ .hasNext();) {
+ String path = e.next();
+ if (!fullPathName) {
+ int n = path.lastIndexOf('/');
+ if (n >= 0) {
+ path = path.substring(n + 1);
+ }
+ }
+
+ Matcher m = expr.matcher(path);
+ if (m.matches()) {
+ if (replace != null)
+ path = m.replaceAll(replace);
+
+ sb.append(del);
+ sb.append(path);
+ del = ", ";
+ }
+ }
+ return sb.toString();
+ }
+
+ public void putAll(Map<String, String> additional, boolean force) {
+ for (Iterator<Map.Entry<String, String>> i = additional.entrySet()
+ .iterator(); i.hasNext();) {
+ Map.Entry<String, String> entry = i.next();
+ if (force || getProperties().get(entry.getKey()) == null)
+ setProperty((String) entry.getKey(), (String) entry.getValue());
+ }
+ }
+
+ boolean firstUse = true;
+
+ public List<Jar> getClasspath() {
+ if (firstUse) {
+ firstUse = false;
+ String cp = getProperty(CLASSPATH);
+ if (cp != null)
+ for (String s : split(cp)) {
+ Jar jar = getJarFromName(s, "getting classpath");
+ if (jar != null)
+ addClasspath(jar);
+ }
+ }
+ return classpath;
+ }
+
+ public void addClasspath(Jar jar) {
+ if (isPedantic() && jar.getResources().isEmpty())
+ warning("There is an empty jar or directory on the classpath: "
+ + jar.getName());
+
+ classpath.add(jar);
+ }
+
+ public void addClasspath(File cp) throws IOException {
+ if (!cp.exists())
+ warning("File on classpath that does not exist: " + cp);
+ Jar jar = new Jar(cp);
+ addClose(jar);
+ classpath.add(jar);
+ }
+
+ public void clear() {
+ classpath.clear();
+ }
+
+ public Jar getTarget() {
+ return dot;
+ }
+
+ public Map<String, Clazz> analyzeBundleClasspath(Jar dot,
+ Map<String, Map<String, String>> bundleClasspath,
+ Map<String, Map<String, String>> contained,
+ Map<String, Map<String, String>> referred,
+ Map<String, Set<String>> uses) throws IOException {
+ Map<String, Clazz> classSpace = new HashMap<String, Clazz>();
+
+ if (bundleClasspath.isEmpty()) {
+ analyzeJar(dot, "", classSpace, contained, referred, uses);
+ } else {
+ for (String path : bundleClasspath.keySet()) {
+ if (path.equals(".")) {
+ analyzeJar(dot, "", classSpace, contained, referred, uses);
+ continue;
+ }
+ //
+ // There are 3 cases:
+ // - embedded JAR file
+ // - directory
+ // - error
+ //
+
+ Resource resource = dot.getResource(path);
+ if (resource != null) {
+ try {
+ Jar jar = new Jar(path);
+ addClose(jar);
+ EmbeddedResource.build(jar, resource);
+ analyzeJar(jar, "", classSpace, contained, referred,
+ uses);
+ } catch (Exception e) {
+ warning("Invalid bundle classpath entry: " + path + " "
+ + e);
+ }
+ } else {
+ if (dot.getDirectories().containsKey(path)) {
+ analyzeJar(dot, path + '/', classSpace, contained,
+ referred, uses);
+ } else {
+ warning("No sub JAR or directory " + path);
+ }
+ }
+ }
+ }
+ return classSpace;
+ }
+
+ /**
+ * We traverse through all the classes that we can find and calculate the
+ * contained and referred set and uses. This method ignores the Bundle
+ * classpath.
+ *
+ * @param jar
+ * @param contained
+ * @param referred
+ * @param uses
+ * @throws IOException
+ */
+ private void analyzeJar(Jar jar, String prefix,
+ Map<String, Clazz> classSpace,
+ Map<String, Map<String, String>> contained,
+ Map<String, Map<String, String>> referred,
+ Map<String, Set<String>> uses) throws IOException {
+
+ next: for (String path : jar.getResources().keySet()) {
+ if (path.startsWith(prefix)) {
+ String relativePath = path.substring(prefix.length());
+ String pack = getPackage(relativePath);
+
+ if (pack != null && !contained.containsKey(pack)) {
+ if (!isMetaData(relativePath)) {
+
+ Map<String, String> map = new LinkedHashMap<String, String>();
+ contained.put(pack, map);
+ Resource pinfo = jar.getResource(prefix
+ + pack.replace('.', '/') + "/packageinfo");
+ if (pinfo != null) {
+ InputStream in = pinfo.openInputStream();
+ String version = parsePackageInfo(in);
+ in.close();
+ if (version != null)
+ map.put("version", version);
+ }
+ }
+ }
+
+ if (path.endsWith(".class")) {
+ Resource resource = jar.getResource(path);
+ Clazz clazz;
+
+ try {
+ InputStream in = resource.openInputStream();
+ clazz = new Clazz(relativePath, in);
+ in.close();
+ } catch (Throwable e) {
+ error("Invalid class file: " + relativePath, e);
+ e.printStackTrace();
+ continue next;
+ }
+
+ String calculatedPath = clazz.getClassName() + ".class";
+ if (!calculatedPath.equals(relativePath))
+ error("Class in different directory than declared. Path from class name is "
+ + calculatedPath
+ + " but the path in the jar is "
+ + relativePath
+ + " from " + jar);
+
+ classSpace.put(relativePath, clazz);
+ referred.putAll(clazz.getReferred());
+
+ // Add all the used packages
+ // to this package
+ Set<String> t = uses.get(pack);
+ if (t == null)
+ uses.put(pack, t = new LinkedHashSet<String>());
+ t.addAll(clazz.getReferred().keySet());
+ t.remove(pack);
+ }
+ }
+ }
+ }
+
+ /**
+ * Clean up version parameters. Other builders use more fuzzy definitions of
+ * the version syntax. This method cleans up such a version to match an OSGi
+ * version.
+ *
+ * @param VERSION_STRING
+ * @return
+ */
+ static Pattern fuzzyVersion = Pattern
+ .compile(
+ "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
+ Pattern.DOTALL);
+ static Pattern fuzzyVersionRange = Pattern
+ .compile(
+ "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
+ Pattern.DOTALL);
+ static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)",
+ Pattern.DOTALL);
+
+ static Pattern nummeric = Pattern.compile("\\d*");
+
+ static public String cleanupVersion(String version) {
+ if (Verifier.VERSIONRANGE.matcher(version).matches())
+ return version;
+
+ Matcher m = fuzzyVersionRange.matcher(version);
+ if (m.matches()) {
+ String prefix = m.group(1);
+ String first = m.group(2);
+ String last = m.group(3);
+ String suffix = m.group(4);
+ return prefix + cleanupVersion(first) + "," + cleanupVersion(last)
+ + suffix;
+ } else {
+ m = fuzzyVersion.matcher(version);
+ if (m.matches()) {
+ StringBuffer result = new StringBuffer();
+ String major = m.group(1);
+ String minor = m.group(3);
+ String micro = m.group(5);
+ String qualifier = m.group(7);
+
+ if (major != null) {
+ result.append(major);
+ if (minor != null) {
+ result.append(".");
+ result.append(minor);
+ if (micro != null) {
+ result.append(".");
+ result.append(micro);
+ if (qualifier != null) {
+ result.append(".");
+ cleanupModifier(result, qualifier);
+ }
+ } else if (qualifier != null) {
+ result.append(".0.");
+ cleanupModifier(result, qualifier);
+ }
+ } else if (qualifier != null) {
+ result.append(".0.0.");
+ cleanupModifier(result, qualifier);
+ }
+ return result.toString();
+ }
+ }
+ }
+ return version;
+ }
+
+ static void cleanupModifier(StringBuffer result, String modifier) {
+ Matcher m = fuzzyModifier.matcher(modifier);
+ if (m.matches())
+ modifier = m.group(2);
+
+ for (int i = 0; i < modifier.length(); i++) {
+ char c = modifier.charAt(i);
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
+ result.append(c);
+ }
+ }
+
+ /**
+ * Decide if the package is a metadata package.
+ *
+ * @param pack
+ * @return
+ */
+ boolean isMetaData(String pack) {
+ for (int i = 0; i < METAPACKAGES.length; i++) {
+ if (pack.startsWith(METAPACKAGES[i]))
+ return true;
+ }
+ return false;
+ }
+
+ public String getPackage(String clazz) {
+ int n = clazz.lastIndexOf('/');
+ if (n < 0)
+ return ".";
+ return clazz.substring(0, n).replace('/', '.');
+ }
+
+ //
+ // We accept more than correct OSGi versions because in a later
+ // phase we actually cleanup maven versions. But it is a bit yucky
+ //
+ static String parsePackageInfo(InputStream jar) throws IOException {
+ try {
+ Properties p = new Properties();
+ p.load(jar);
+ jar.close();
+ if (p.containsKey("version")) {
+ return p.getProperty("version");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public String getVersionPolicy() {
+ return getProperty(VERSIONPOLICY, "${version;==;${@}}");
+ }
+
+ /**
+ * The extends macro traverses all classes and returns a list of class names
+ * that extend a base class.
+ */
+
+ static String _classesHelp = "${classes;'implementing'|'extending'|'importing'|'named'|'version'|'any';<pattern>}, Return a list of class fully qualified class names that extend/implement/import any of the contained classes matching the pattern\n";
+
+ public String _classes(String args[]) {
+ // Macro.verifyCommand(args, _classesHelp, new
+ // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
+ // null}, 3,3);
+ Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
+ for (int i = 1; i < args.length; i += 2) {
+ if (args.length < i + 1)
+ throw new IllegalArgumentException(
+ "${classes} macro must have odd number of arguments. "
+ + _classesHelp);
+
+ String typeName = args[i];
+ Clazz.QUERY type = null;
+ if (typeName.equals("implementing")
+ || typeName.equals("implements"))
+ type = Clazz.QUERY.IMPLEMENTS;
+ else if (typeName.equals("extending") || typeName.equals("extends"))
+ type = Clazz.QUERY.EXTENDS;
+ else if (typeName.equals("importing") || typeName.equals("imports"))
+ type = Clazz.QUERY.IMPORTS;
+ else if (typeName.equals("all"))
+ type = Clazz.QUERY.ANY;
+ else if (typeName.equals("version"))
+ type = Clazz.QUERY.VERSION;
+ else if (typeName.equals("named"))
+ type = Clazz.QUERY.NAMED;
+
+ if (type == null)
+ throw new IllegalArgumentException(
+ "${classes} has invalid type: " + typeName + ". "
+ + _classesHelp);
+ // The argument is declared as a dotted name but the classes
+ // use a slashed named. So convert the name before we make it a
+ // instruction.
+ String pattern = args[i + 1].replace('.', '/');
+ Instruction instr = Instruction.getPattern(pattern);
+
+ for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
+ Clazz clazz = c.next();
+ if (!clazz.is(type, instr, classspace))
+ c.remove();
+ }
+ }
+ if (matched.isEmpty())
+ return "";
+
+ return join(matched);
+ }
+
+ /**
+ * Get the exporter of a package ...
+ */
+
+ public String _exporters(String args[]) throws Exception {
+ Macro
+ .verifyCommand(
+ args,
+ "${exporters;<packagename>}, returns the list of jars that export the given package",
+ null, 2, 2);
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ String pack = args[1].replace('.', '/');
+ for (Jar jar : classpath) {
+ if (jar.getDirectories().containsKey(pack)) {
+ sb.append(del);
+ sb.append(jar.getName());
+ }
+ }
+ return sb.toString();
+ }
+
+ public Map<String, Clazz> getClassspace() {
+ return classspace;
+ }
+
+}
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..2da1980
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
@@ -0,0 +1,1135 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.bnd.make.*;
+import aQute.bnd.service.*;
+
+/**
+ * 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.17 $
+ */
+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;
+
+ private static final File[] EMPTY_FILE = new File[0];
+
+ List<File> sourcePath = new ArrayList<File>();
+ Pattern NAME_URL = Pattern
+ .compile("(.*)(http://.*)");
+
+ Make make = new Make(this);
+
+ 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);
+ }
+ }
+
+ if (getProperty(NOMANIFEST) == null)
+ dot.setManifest(manifest);
+ else
+ dot.setNoManifest(true);
+
+ // 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);
+ List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
+
+ Map<String, Map<String, String>> infos = parseHeader(signing);
+ for (Map.Entry<String, Map<String, String>> entry : infos.entrySet()) {
+ for (SignerPlugin signer : signers) {
+ signer.sign(this, entry.getKey());
+ }
+ }
+ }
+
+ 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>(), null);
+
+ // 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_DIRECTIVE);
+ 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>> privateMap = replaceWitInstruction(
+ getHeader(PRIVATE_PACKAGE), PRIVATE_PACKAGE);
+ Map<Instruction, Map<String, String>> exportMap = replaceWitInstruction(
+ getHeader(EXPORT_PACKAGE), EXPORT_PACKAGE);
+
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ privateMap.putAll(replaceWitInstruction(parseHeader(getProperty(
+ Constants.TESTPACKAGES, "test;presence:=optional")),
+ TESTPACKAGES));
+ }
+ if (!privateMap.isEmpty())
+ doExpand(jar, "Private-Package, or -testpackages", privateMap, true);
+
+ if (!exportMap.isEmpty()) {
+ Jar exports = new Jar("exports");
+ doExpand(exports, "Export-Package", exportMap, true);
+ jar.addAll(exports);
+ exports.close();
+ }
+
+ if (privateMap.isEmpty() && exportMap.isEmpty() && !isResourceOnly()) {
+ warning("Neither Export-Package, Private-Package, -testpackages is set, therefore no packages will be included");
+ }
+ }
+
+ /**
+ *
+ * @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-OPT/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(INCLUDERESOURCE);
+ if (includes == null)
+ includes = getProperty("Include-Resource");
+ } else
+ warning("Please use -includeresource 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];
+
+ // Handle directives
+ if (sourceFile.isDirectory()) {
+ 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);
+ }
+
+ InstructionFilter iFilter = null;
+ if (filter != null) {
+ iFilter = new InstructionFilter(Instruction.getPattern(filter), recursive);
+ } else {
+ iFilter = new InstructionFilter(null, recursive);
+ }
+
+ destinationPath = checkDestinationPath(destinationPath);
+
+ File[] files = resolveFiles(sourceFile, iFilter, recursive);
+ for (File file : files) {
+ String dp;
+ if (flatten) {
+ if (destinationPath.length() == 0) {
+ dp = file.getName();
+ } else {
+ dp = destinationPath + File.separator + file.getName();
+ }
+ } else {
+ dp = destinationPath + file.getParentFile().getAbsolutePath().substring(sourceFile.getAbsolutePath().length());
+ if (dp.length() > 0) {
+ dp += File.separator + file.getName();
+ } else {
+ dp = file.getName();
+ }
+ }
+ copy(jar, dp, file, preprocess, extra);
+ }
+ return;
+ }
+ }
+
+ destinationPath = checkDestinationPath(destinationPath);
+
+ if (!sourceFile.exists()) {
+ noSuchFile(jar, name, extra, source, destinationPath);
+ } else
+ copy(jar, destinationPath, sourceFile, preprocess, extra);
+ }
+ }
+
+ private String checkDestinationPath(String destinationPath) {
+
+ // 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);
+ return destinationPath;
+ }
+
+ private File[] resolveFiles(File dir, FileFilter filter, boolean recursive) {
+ return resolveFiles(dir, filter, null, recursive);
+ }
+
+ private File[] resolveFiles(File dir, FileFilter filter, File[] files, boolean recursive) {
+ if (files == null) {
+ files = EMPTY_FILE;
+ }
+
+ if (Analyzer.doNotCopy.matcher(dir.getName()).matches()) {
+ return files;
+ }
+
+ File[] fs = dir.listFiles(filter);
+ for (File file : fs) {
+ if (file.isDirectory()) {
+ if (recursive) {
+ files = resolveFiles(file, filter, files, recursive);
+ }
+ } else {
+ if (files.length == 0) {
+ files = new File[] { file };
+ } else {
+ File[] newFiles = new File[files.length + 1];
+ System.arraycopy(files, 0, newFiles, 0, files.length);
+ newFiles[newFiles.length - 1] = file;
+ files = newFiles;
+ }
+ }
+ }
+ return files;
+ }
+
+ 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) {
+ 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 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);
+ }
+ String x = extra.get("extra");
+ if (x != null)
+ resource.setExtra(x);
+ 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()]);
+ }
+
+ 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[]) 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();
+ }
+
+ public void removeBundleSpecificHeaders() {
+ Set<String> set = new HashSet<String>(Arrays
+ .asList(BUNDLE_SPECIFIC_HEADERS));
+ setForceLocal(set);
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java b/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
new file mode 100644
index 0000000..e09e00f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
@@ -0,0 +1,11 @@
+package aQute.lib.osgi;
+
+public interface ClassDataCollector {
+ void classBegin(int access, String name);
+ void extendsClass(String name);
+ void implementsInterfaces(String name[]);
+ void field(int access, String descriptor);
+ void method(int access, String descriptor);
+ void addReference(String token);
+ void classEnd();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java b/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
new file mode 100644
index 0000000..93fe6ac
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
@@ -0,0 +1,832 @@
+/* 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.nio.*;
+import java.util.*;
+
+public class Clazz {
+ public static enum QUERY {
+ IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION
+ };
+
+ static protected class Assoc {
+ Assoc(byte tag, int a, int b) {
+ this.tag = tag;
+ this.a = a;
+ this.b = b;
+ }
+
+ byte tag;
+ int a;
+ int b;
+ }
+
+ final static byte SkipTable[] = { 0, // 0 non existent
+ -1, // 1 CONSTANT_utf8 UTF 8, handled in
+ // method
+ -1, // 2
+ 4, // 3 CONSTANT_Integer
+ 4, // 4 CONSTANT_Float
+ 8, // 5 CONSTANT_Long (index +=2!)
+ 8, // 6 CONSTANT_Double (index +=2!)
+ -1, // 7 CONSTANT_Class
+ 2, // 8 CONSTANT_String
+ 4, // 9 CONSTANT_FieldRef
+ 4, // 10 CONSTANT_MethodRef
+ 4, // 11 CONSTANT_InterfaceMethodRef
+ 4, // 12 CONSTANT_NameAndType
+ };
+
+ String className;
+ Object pool[];
+ int intPool[];
+ Map<String, Map<String, String>> imports = new HashMap<String, Map<String, String>>();
+ String path;
+
+ // static String type = "([BCDFIJSZ\\[]|L[^<>]+;)";
+ // static Pattern descriptor = Pattern.compile("\\(" + type + "*\\)(("
+ // + type + ")|V)");
+ int minor = 0;
+ int major = 0;
+
+ String sourceFile;
+ Set<String> xref;
+ Set<Integer> classes;
+ Set<Integer> descriptors;
+ int forName = 0;
+ int class$ = 0;
+ String[] interfaces;
+ String zuper;
+ ClassDataCollector cd = new ClassDataCollector() {
+ public void addReference(
+ String token) {
+ }
+
+ public void classBegin(
+ int access,
+ String name) {
+ }
+
+ public void classEnd() {
+ }
+
+ public void extendsClass(
+ String name) {
+ }
+
+ public void field(
+ int access,
+ String descriptor) {
+ }
+
+ public void implementsInterfaces(
+ String[] name) {
+ }
+
+ public void method(
+ int access,
+ String descriptor) {
+ }
+ };
+
+ public Clazz(String path) {
+ this.path = path;
+ }
+
+ public Clazz(String path, InputStream in) throws IOException {
+ this.path = path;
+ DataInputStream din = new DataInputStream(in);
+ parseClassFile(din);
+ din.close();
+ }
+
+ Set<String> parseClassFile(DataInputStream in) throws IOException {
+
+ xref = new HashSet<String>();
+ classes = new HashSet<Integer>();
+ descriptors = new HashSet<Integer>();
+
+ boolean crawl = false; // Crawl the byte code
+ int magic = in.readInt();
+ if (magic != 0xCAFEBABE)
+ throw new IOException("Not a valid class file (no CAFEBABE header)");
+
+ minor = in.readUnsignedShort(); // minor version
+ major = in.readUnsignedShort(); // major version
+ int count = in.readUnsignedShort();
+ pool = new Object[count];
+ intPool = new int[count];
+
+ process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
+ byte tag = in.readByte();
+ switch (tag) {
+ case 0:
+ break process;
+ case 1:
+ constantUtf8(in, poolIndex);
+ break;
+
+ // For some insane optimization reason are
+ // the long and the double two entries in the
+ // constant pool. See 4.4.5
+ case 5:
+ constantLong(in, poolIndex);
+ poolIndex++;
+ break;
+
+ case 6:
+ constantDouble(in, poolIndex);
+ poolIndex++;
+ break;
+
+ case 7:
+ constantClass(in, poolIndex);
+ break;
+
+ case 8:
+ constantString(in, poolIndex);
+ break;
+
+ case 10: // Method ref
+ methodRef(in, poolIndex);
+ break;
+
+ // Name and Type
+ case 12:
+ nameAndType(in, poolIndex, tag);
+ break;
+
+ // We get the skip count for each record type
+ // from the SkipTable. This will also automatically
+ // abort when
+ default:
+ if (tag == 2)
+ throw new IOException("Invalid tag " + tag);
+ in.skipBytes(SkipTable[tag]);
+ break;
+ }
+ }
+
+ pool(pool, intPool);
+ /*
+ * Parse after the constant pool, code thanks to Hans Christian
+ * Falkenberg
+ */
+
+ int access_flags = in.readUnsignedShort(); // access
+ int this_class = in.readUnsignedShort();
+ className = (String) pool[intPool[this_class]];
+ cd.classBegin(access_flags, className);
+
+ try {
+
+ int super_class = in.readUnsignedShort();
+ zuper = (String) pool[intPool[super_class]];
+ if (zuper != null) {
+ addReference(zuper);
+ cd.extendsClass(zuper);
+ }
+
+ int interfacesCount = in.readUnsignedShort();
+ if (interfacesCount > 0) {
+ interfaces = new String[interfacesCount];
+ for (int i = 0; i < interfacesCount; i++)
+ interfaces[i] = (String) pool[intPool[in
+ .readUnsignedShort()]];
+ cd.implementsInterfaces(interfaces);
+ }
+
+ int fieldsCount = in.readUnsignedShort();
+ for (int i = 0; i < fieldsCount; i++) {
+ access_flags = in.readUnsignedShort(); // skip access flags
+ int name_index = in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+
+ // Java prior to 1.5 used a weird
+ // static variable to hold the com.X.class
+ // result construct. If it did not find it
+ // it would create a variable class$com$X
+ // that would be used to hold the class
+ // object gotten with Class.forName ...
+ // Stupidly, they did not actively use the
+ // class name for the field type, so bnd
+ // would not see a reference. We detect
+ // this case and add an artificial descriptor
+ String name = pool[name_index].toString(); // name_index
+ if (name.startsWith("class$")) {
+ crawl = true;
+ }
+ cd.field(access_flags, pool[descriptor_index].toString());
+ descriptors.add(new Integer(descriptor_index));
+ doAttributes(in, false);
+ }
+
+ //
+ // Check if we have to crawl the code to find
+ // the ldc(_w) <string constant> invokestatic Class.forName
+ // if so, calculate the method ref index so we
+ // can do this efficiently
+ //
+ if (crawl) {
+ forName = findMethod("java/lang/Class", "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ class$ = findMethod(className, "class$",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ }
+
+ //
+ // Handle the methods
+ //
+ int methodCount = in.readUnsignedShort();
+ for (int i = 0; i < methodCount; i++) {
+ access_flags = in.readUnsignedShort();
+ /* int name_index = */in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+ // String s = (String) pool[name_index];
+ descriptors.add(new Integer(descriptor_index));
+ if ( pool[descriptor_index ] == null ) {
+ System.out.println("Value in pool=null, descriptor_index=" +descriptor_index);
+ }
+ cd.method(access_flags, pool[descriptor_index].toString());
+ doAttributes(in, crawl);
+ }
+
+ doAttributes(in, false);
+
+ //
+ // Now iterate over all classes we found and
+ // parse those as well. We skip duplicates
+ //
+
+ for (Iterator<Integer> e = classes.iterator(); e.hasNext();) {
+ int class_index = e.next().intValue();
+ doClassReference((String) pool[class_index]);
+ }
+
+ //
+ // Parse all the descriptors we found
+ //
+
+ for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
+ Integer index = e.next();
+ String prototype = (String) pool[index.intValue()];
+ if (prototype != null)
+ parseDescriptor(prototype);
+ else
+ System.err.println("Unrecognized descriptor: " + index);
+ }
+ Set<String> xref = this.xref;
+ reset();
+ return xref;
+ } finally {
+ cd.classEnd();
+ }
+ }
+
+ protected void pool(Object[] pool, int[] intPool) {
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @param tag
+ * @throws IOException
+ */
+ protected void nameAndType(DataInputStream in, int poolIndex, byte tag)
+ throws IOException {
+ int name_index = in.readUnsignedShort();
+ int descriptor_index = in.readUnsignedShort();
+ descriptors.add(new Integer(descriptor_index));
+ pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @param tag
+ * @throws IOException
+ */
+ private void methodRef(DataInputStream in, int poolIndex)
+ throws IOException {
+ int class_index = in.readUnsignedShort();
+ int name_and_type_index = in.readUnsignedShort();
+ pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ private void constantString(DataInputStream in, int poolIndex)
+ throws IOException {
+ int string_index = in.readUnsignedShort();
+ intPool[poolIndex] = string_index;
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ protected void constantClass(DataInputStream in, int poolIndex)
+ throws IOException {
+ int class_index = in.readUnsignedShort();
+ classes.add(new Integer(class_index));
+ intPool[poolIndex] = class_index;
+ }
+
+ /**
+ * @param in
+ * @throws IOException
+ */
+ protected void constantDouble(DataInputStream in, int poolIndex)
+ throws IOException {
+ in.skipBytes(8);
+ }
+
+ /**
+ * @param in
+ * @throws IOException
+ */
+ protected void constantLong(DataInputStream in, int poolIndex)
+ throws IOException {
+ in.skipBytes(8);
+ }
+
+ /**
+ * @param in
+ * @param poolIndex
+ * @throws IOException
+ */
+ protected void constantUtf8(DataInputStream in, int poolIndex)
+ throws IOException {
+ // CONSTANT_Utf8
+
+ String name = in.readUTF();
+ xref.add(name);
+ pool[poolIndex] = name;
+ }
+
+ /**
+ * Find a method reference in the pool that points to the given class,
+ * methodname and descriptor.
+ *
+ * @param clazz
+ * @param methodname
+ * @param descriptor
+ * @return index in constant pool
+ */
+ private int findMethod(String clazz, String methodname, String descriptor) {
+ for (int i = 1; i < pool.length; i++) {
+ if (pool[i] instanceof Assoc) {
+ Assoc methodref = (Assoc) pool[i];
+ if (methodref.tag == 10) {
+ // Method ref
+ int class_index = methodref.a;
+ int class_name_index = intPool[class_index];
+ if (clazz.equals(pool[class_name_index])) {
+ int name_and_type_index = methodref.b;
+ Assoc name_and_type = (Assoc) pool[name_and_type_index];
+ if (name_and_type.tag == 12) {
+ // Name and Type
+ int name_index = name_and_type.a;
+ int type_index = name_and_type.b;
+ if (methodname.equals(pool[name_index])) {
+ if (descriptor.equals(pool[type_index])) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ private void doClassReference(String next) {
+ if (next != null) {
+ String normalized = normalize(next);
+ if (normalized != null) {
+ cd.addReference(next);
+ String pack = getPackage(normalized);
+ packageReference(pack);
+ }
+ } else
+ throw new IllegalArgumentException("Invalid class, parent=");
+ }
+
+ /**
+ * Called for each attribute in the class, field, or method.
+ *
+ * @param in
+ * The stream
+ * @throws IOException
+ */
+ private void doAttributes(DataInputStream in, boolean crawl)
+ throws IOException {
+ int attributesCount = in.readUnsignedShort();
+ for (int j = 0; j < attributesCount; j++) {
+ // skip name CONSTANT_Utf8 pointer
+ doAttribute(in, crawl);
+ }
+ }
+
+ /**
+ * Process a single attribute, if not recognized, skip it.
+ *
+ * @param in
+ * the data stream
+ * @throws IOException
+ */
+ private void doAttribute(DataInputStream in, boolean crawl)
+ throws IOException {
+ int attribute_name_index = in.readUnsignedShort();
+ String attributeName = (String) pool[attribute_name_index];
+ long attribute_length = in.readInt();
+ attribute_length &= 0xFFFFFFFF;
+ if ("RuntimeVisibleAnnotations".equals(attributeName))
+ doAnnotations(in);
+ else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
+ doParameterAnnotations(in);
+ else if ("SourceFile".equals(attributeName))
+ doSourceFile(in);
+ else if ("Code".equals(attributeName) && crawl)
+ doCode(in);
+// else if ("Signature".equals(attributeName))
+// doSignature(in);
+ else {
+ if (attribute_length > 0x7FFFFFFF) {
+ throw new IllegalArgumentException("Attribute > 2Gb");
+ }
+ in.skipBytes((int) attribute_length);
+ }
+ }
+
+ /**
+ * Handle a signature
+ *
+ * <pre>
+ * Signature_attribute {
+ * u2 attribute_name_index;
+ * u4 attribute_length;
+ * u2 signature_index;
+ * }
+ * </pre>
+ */
+
+// void doSignature(DataInputStream in) throws IOException {
+// /*int attribute_length =*/ in.readInt();
+// int signature_index = in.readUnsignedShort();
+// String signature = (String) pool[signature_index];
+// }
+
+ /**
+ * <pre>
+ * Code_attribute {
+ * u2 attribute_name_index;
+ * u4 attribute_length;
+ * u2 max_stack;
+ * u2 max_locals;
+ * u4 code_length;
+ * u1 code[code_length];
+ * u2 exception_table_length;
+ * { u2 start_pc;
+ * u2 end_pc;
+ * u2 handler_pc;
+ * u2 catch_type;
+ * } exception_table[exception_table_length];
+ * u2 attributes_count;
+ * attribute_info attributes[attributes_count];
+ * }
+ * </pre>
+ *
+ * @param in
+ * @param pool
+ * @throws IOException
+ */
+ private void doCode(DataInputStream in) throws IOException {
+ /* int max_stack = */in.readUnsignedShort();
+ /* int max_locals = */in.readUnsignedShort();
+ int code_length = in.readInt();
+ byte code[] = new byte[code_length];
+ in.readFully(code);
+ crawl(code);
+ int exception_table_length = in.readUnsignedShort();
+ in.skipBytes(exception_table_length * 8);
+ doAttributes(in, false);
+ }
+
+ /**
+ * We must find Class.forName references ...
+ *
+ * @param code
+ */
+ protected void crawl(byte[] code) {
+ ByteBuffer bb = ByteBuffer.wrap(code);
+ bb.order(ByteOrder.BIG_ENDIAN);
+ int lastReference = -1;
+
+ while (bb.remaining() > 0) {
+ int instruction = 0xFF & bb.get();
+ switch (instruction) {
+ case OpCodes.ldc:
+ lastReference = 0xFF & bb.get();
+ break;
+
+ case OpCodes.ldc_w:
+ lastReference = 0xFFFF & bb.getShort();
+ break;
+
+ case OpCodes.invokestatic:
+ int methodref = 0xFFFF & bb.getShort();
+ if ((methodref == forName || methodref == class$)
+ && lastReference != -1
+ && pool[intPool[lastReference]] instanceof String) {
+ String clazz = (String) pool[intPool[lastReference]];
+ doClassReference(clazz.replace('.', '/'));
+ }
+ break;
+
+ case OpCodes.tableswitch:
+ // Skip to place divisible by 4
+ while ((bb.position() & 0x3) != 0)
+ bb.get();
+ /* int deflt = */
+ bb.getInt();
+ int low = bb.getInt();
+ int high = bb.getInt();
+ bb.position(bb.position() + (high - low + 1) * 4);
+ lastReference = -1;
+ break;
+
+ case OpCodes.lookupswitch:
+ // Skip to place divisible by 4
+ while ((bb.position() & 0x3) != 0)
+ bb.get();
+ /* deflt = */
+ bb.getInt();
+ int npairs = bb.getInt();
+ bb.position(bb.position() + npairs * 8);
+ lastReference = -1;
+ break;
+
+ default:
+ lastReference = -1;
+ bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
+ }
+ }
+ }
+
+ private void doSourceFile(DataInputStream in) throws IOException {
+ int sourcefile_index = in.readUnsignedShort();
+ this.sourceFile = pool[sourcefile_index].toString();
+ }
+
+ private void doParameterAnnotations(DataInputStream in) throws IOException {
+ int num_parameters = in.readUnsignedByte();
+ for (int p = 0; p < num_parameters; p++) {
+ int num_annotations = in.readUnsignedShort(); // # of annotations
+ for (int a = 0; a < num_annotations; a++) {
+ doAnnotation(in);
+ }
+ }
+ }
+
+ private void doAnnotations(DataInputStream in) throws IOException {
+ int num_annotations = in.readUnsignedShort(); // # of annotations
+ for (int a = 0; a < num_annotations; a++) {
+ doAnnotation(in);
+ }
+ }
+
+ private void doAnnotation(DataInputStream in) throws IOException {
+ int type_index = in.readUnsignedShort();
+ descriptors.add(new Integer(type_index));
+ int num_element_value_pairs = in.readUnsignedShort();
+ for (int v = 0; v < num_element_value_pairs; v++) {
+ /* int element_name_index = */in.readUnsignedShort();
+ doElementValue(in);
+ }
+ }
+
+ private void doElementValue(DataInputStream in) throws IOException {
+ int tag = in.readUnsignedByte();
+ switch (tag) {
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ case 's':
+ /* int const_value_index = */
+ in.readUnsignedShort();
+ break;
+
+ case 'e':
+ int type_name_index = in.readUnsignedShort();
+ descriptors.add(new Integer(type_name_index));
+ /* int const_name_index = */
+ in.readUnsignedShort();
+ break;
+
+ case 'c':
+ int class_info_index = in.readUnsignedShort();
+ descriptors.add(new Integer(class_info_index));
+ break;
+
+ case '@':
+ doAnnotation(in);
+ break;
+
+ case '[':
+ int num_values = in.readUnsignedShort();
+ for (int i = 0; i < num_values; i++) {
+ doElementValue(in);
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ "Invalid value for Annotation ElementValue tag " + tag);
+ }
+ }
+
+ void packageReference(String pack) {
+ if (pack.indexOf('<') >= 0)
+ System.out.println("Oops: " + pack);
+ if (!imports.containsKey(pack))
+ imports.put(pack, new LinkedHashMap<String, String>());
+ }
+
+ public void parseDescriptor(String prototype) {
+ if (prototype.startsWith("("))
+ parseMethodDescriptor(prototype);
+ else
+ addReference(prototype);
+ }
+
+ void parseMethodDescriptor(String prototype) {
+ int last = prototype.indexOf(')');
+ if (last < 0)
+ throw new IllegalArgumentException(
+ "Invalid method descriptor in class file: " + className
+ + " " + prototype);
+
+ for (int i = 1; i < last; i++) {
+ if (prototype.charAt(i) == 'L') {
+ int next = prototype.indexOf(';', i);
+ addReference(prototype.substring(i, next));
+ i = next;
+ }
+ }
+ addReference(prototype.substring(last + 1));
+ }
+
+ private void addReference(String token) {
+ cd.addReference(token);
+ while (token.startsWith("["))
+ token = token.substring(1);
+
+ if (token.startsWith("L")) {
+ String clazz = normalize(token.substring(1));
+ if (clazz.startsWith("java/"))
+ return;
+ String pack = getPackage(clazz);
+ packageReference(pack);
+ }
+ }
+
+ static String normalize(String s) {
+ if (s.startsWith("[L"))
+ return normalize(s.substring(2));
+ if (s.startsWith("["))
+ if (s.length() == 2)
+ return null;
+ else
+ return normalize(s.substring(1));
+ if (s.endsWith(";"))
+ return normalize(s.substring(0, s.length() - 1));
+ return s + ".class";
+ }
+
+ public static String getPackage(String clazz) {
+ int n = clazz.lastIndexOf('/');
+ if (n < 0)
+ return ".";
+ return clazz.substring(0, n).replace('/', '.');
+ }
+
+ public Map<String, Map<String, String>> getReferred() {
+ return imports;
+ }
+
+ String getClassName() {
+ return className;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Set<String> xref(InputStream in) throws IOException {
+ DataInputStream din = new DataInputStream(in);
+ Set<String> set = parseClassFile(din);
+ din.close();
+ return set;
+ }
+
+ public String getSourceFile() {
+ return sourceFile;
+ }
+
+ /**
+ * .class construct for different compilers
+ *
+ * sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
+ * 1.5 ldc_w (class) 1.6 "
+ *
+ * eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
+ * 1.5 ldc (class) 1.6 "
+ *
+ * 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
+ * variable that decodes the class name. For eclipse, the class$0 gives away
+ * we have a reference encoded in a string.
+ * compilerversions/compilerversions.jar contains test versions of all
+ * versions/compilers.
+ */
+
+ public void reset() {
+ pool = null;
+ intPool = null;
+ xref = null;
+ classes = null;
+ descriptors = null;
+ }
+
+ public boolean is(QUERY query, Instruction instr,
+ Map<String, Clazz> classspace) {
+ switch (query) {
+ case ANY:
+ return true;
+
+ case NAMED:
+ if (instr.matches(getClassName()))
+ return !instr.isNegated();
+ return false;
+
+ case VERSION:
+ String v = major + "/" + minor;
+ if (instr.matches(v))
+ return !instr.isNegated();
+ return false;
+
+ case IMPLEMENTS:
+ for (int i = 0; interfaces != null && i < interfaces.length; i++) {
+ if (instr.matches(interfaces[i]))
+ return !instr.isNegated();
+ }
+ break;
+ case EXTENDS:
+ if (zuper == null)
+ return false;
+
+ if (instr.matches(zuper))
+ return !instr.isNegated();
+ break;
+
+ case IMPORTS:
+ for (String imp : imports.keySet()) {
+ if (instr.matches(imp.replace('.', '/')))
+ return !instr.isNegated();
+ }
+ }
+
+ if (zuper == null || classspace == null)
+ return false;
+
+ Clazz clazz = classspace.get(zuper);
+ if (clazz == null)
+ return false;
+
+ return clazz.is(query, instr, classspace);
+ }
+
+ public String toString() {
+ return getFQN();
+ }
+
+ public String getFQN() {
+ return getClassName().replace('/', '.');
+ }
+
+ public void setClassDataCollector(ClassDataCollector cd) {
+ this.cd = cd;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
new file mode 100644
index 0000000..8e54aae
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Constants.java
@@ -0,0 +1,197 @@
+package aQute.lib.osgi;
+
+import java.util.*;
+import java.util.regex.*;
+
+public interface Constants {
+ /*
+ * Defined in OSGi
+ */
+ /**
+ * @syntax Bundle-ActivationPolicy ::= policy ( ’;’ directive )* policy ::=
+ * ’lazy’
+ */
+ String BUNDLE_ACTIVATION_POLICY = "Bundle-ActivationPolicy";
+ String BUNDLE_ACTIVATOR = "Bundle-Activator";
+ String BUNDLE_BLUEPRINT = "Bundle-Copyright";
+ String BUNDLE_CATEGORY = "Bundle-Category";
+ String BUNDLE_CLASSPATH = "Bundle-ClassPath";
+ String BUNDLE_CONTACTADDRESS = "Bundle-ContactAddress";
+ String BUNDLE_COPYRIGHT = "Bundle-Copyright";
+ String BUNDLE_DESCRIPTION = "Bundle-Description";
+ String BUNDLE_DOCURL = "Bundle-DocURL";
+ String BUNDLE_ICON = "Bundle-Icon";
+ String BUNDLE_LICENSE = "Bundle-License";
+ String BUNDLE_LOCALIZATION = "Bundle-Localization";
+ String BUNDLE_MANIFESTVERSION = "Bundle-ManifestVersion";
+ String BUNDLE_NAME = "Bundle-Name";
+ String BUNDLE_NATIVECODE = "Bundle-NativeCode";
+ String BUNDLE_REQUIREDEXECUTIONENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+ String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
+ String BUNDLE_UPDATELOCATION = "Bundle-UpdateLocation";
+ String BUNDLE_VENDOR = "Bundle-Vendor";
+ String BUNDLE_VERSION = "Bundle-Version";
+ String DYNAMICIMPORT_PACKAGE = "DynamicImport-Package";
+ String EXPORT_PACKAGE = "Export-Package";
+ String EXPORT_SERVICE = "Export-Service";
+ String FRAGMENT_HOST = "Fragment-Host";
+ String IMPORT_PACKAGE = "Import-Package";
+ String IMPORT_SERVICE = "Import-Service";
+ String REQUIRE_BUNDLE = "Require-Bundle";
+ String SERVICE_COMPONENT = "Service-Component";
+
+ String PRIVATE_PACKAGE = "Private-Package";
+ String IGNORE_PACKAGE = "Ignore-Package";
+ String INCLUDE_RESOURCE = "Include-Resource";
+ String CONDITIONAL_PACKAGE = "Conditional-Package";
+ String BND_LASTMODIFIED = "Bnd-LastModified";
+ String CREATED_BY = "Created-By";
+ String TOOL = "Tool";
+ String TESTCASES = "Test-Cases";
+
+ 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, TESTCASES };
+
+ String BUILDPATH = "-buildpath";
+ String BUMPPOLICY = "-bumppolicy";
+ String CONDUIT = "-conduit";
+ String DEPENDSON = "-dependson";
+ String DEPLOYREPO = "-deployrepo";
+ String DONOTCOPY = "-donotcopy";
+ String DEBUG = "-debug";
+ String EXPORT_CONTENTS = "-exportcontents";
+ String FAIL_OK = "-failok";
+ String INCLUDE = "-include";
+ String INCLUDERESOURCE = "-includeresource";
+ String MAKE = "-make";
+ String MANIFEST = "-manifest";
+ String NOEXTRAHEADERS = "-noextraheaders";
+ String NOMANIFEST = "-nomanifest";
+ String NOUSES = "-nouses";
+ String NOPE = "-nope";
+ String PEDANTIC = "-pedantic";
+ String PLUGIN = "-plugin";
+ String POM = "-pom";
+ String RELEASEREPO = "-releaserepo";
+ String REMOVE_HEADERS = "-removeheaders";
+ String RESOURCEONLY = "-resourceonly";
+ String SOURCES = "-sources";
+ String SOURCEPATH = "-sourcepath";
+ String SUB = "-sub";
+ String RUNPROPERTIES = "-runproperties";
+ String RUNSYSTEMPACKAGES = "-runsystempackages";
+ String RUNBUNDLES = "-runbundles";
+ String RUNPATH = "-runpath";
+ String RUNVM = "-runvm";
+
+ String REPORTNEWER = "-reportnewer";
+ String SIGN = "-sign";
+ String TESTPACKAGES = "-testpackages";
+ String TESTREPORT = "-testreport";
+ String TESTBUNDLES = "-testbundles";
+ String UNDERTEST = "-undertest";
+ String VERBOSE = "-verbose";
+ String VERSIONPOLICY = "-versionpolicy";
+
+ // Deprecated
+ String CLASSPATH = "-classpath";
+
+ String options[] = {
+ BUILDPATH, BUMPPOLICY, CONDUIT, CLASSPATH, DEPENDSON, DONOTCOPY,
+ EXPORT_CONTENTS, FAIL_OK, INCLUDE, INCLUDERESOURCE, MAKE, MANIFEST,
+ NOEXTRAHEADERS, NOUSES, NOPE, PEDANTIC, PLUGIN, POM,
+ REMOVE_HEADERS, RESOURCEONLY, SOURCES, SOURCEPATH, SOURCES,
+ SOURCEPATH, SUB, RUNBUNDLES, RUNPATH, RUNSYSTEMPACKAGES,
+ RUNPROPERTIES, REPORTNEWER, UNDERTEST, TESTBUNDLES, TESTPACKAGES,
+ TESTREPORT, VERBOSE, NOMANIFEST, DEPLOYREPO, RELEASEREPO };
+
+ // Ignore bundle specific headers. These bundles do not make
+ // a lot of sense to inherit
+ String[] BUNDLE_SPECIFIC_HEADERS = new String[] {
+ INCLUDE_RESOURCE, BUNDLE_ACTIVATOR, BUNDLE_CLASSPATH, BUNDLE_NAME,
+ BUNDLE_NATIVECODE, BUNDLE_SYMBOLICNAME, IMPORT_PACKAGE,
+ EXPORT_PACKAGE, DYNAMICIMPORT_PACKAGE, FRAGMENT_HOST,
+ REQUIRE_BUNDLE, PRIVATE_PACKAGE, EXPORT_CONTENTS, TESTCASES,
+ NOMANIFEST };
+
+ char DUPLICATE_MARKER = '~';
+
+ String SPLIT_PACKAGE_DIRECTIVE = "-split-package:";
+ String IMPORT_DIRECTIVE = "-import:";
+ String NO_IMPORT_DIRECTIVE = "-noimport:";
+ String REMOVE_ATTRIBUTE_DIRECTIVE = "-remove-attribute:";
+
+ String USES_DIRECTIVE = "uses:";
+ String MANDATORY_DIRECTIVE = "mandatory:";
+ String INCLUDE_DIRECTIVE = "include:";
+ String EXCLUDE_DIRECTIVE = "exclude:";
+ String PRESENCE_DIRECTIVE = "presence:";
+ String SINGLETON_DIRECTIVE = "singleton:";
+ String EXTENSION_DIRECTIVE = "extension:";
+ String VISIBILITY_DIRECTIVE = "visibility:";
+ String FRAGMENT_ATTACHMENT_DIRECTIVE = "fragment-attachment:";
+ String RESOLUTION_DIRECTIVE = "resolution:";
+ String PATH_DIRECTIVE = "path:";
+ String SIZE_ATTRIBUTE = "size";
+ String LINK_ATTRIBUTE = "link";
+ String NAME_ATTRIBUTE = "name";
+ String DESCRIPTION_ATTRIBUTE = "description";
+ String OSNAME_ATTRIBUTE = "osname";
+ String OSVERSION_ATTRIBUTE = "osversion";
+ String PROCESSOR_ATTRIBUTE = "processor";
+ String LANGUAGE_ATTRIBUTE = "language";
+ String SELECTION_FILTER_ATTRIBUTE = "selection-filter";
+ String BLUEPRINT_WAIT_FOR_DEPENDENCIES_ATTRIBUTE = "blueprint.wait-for-dependencies";
+ String BLUEPRINT_TIMEOUT_ATTRIBUTE = "blueprint.timeout";
+ String VERSION_ATTRIBUTE = "version";
+ String BUNDLE_SYMBOLIC_NAME_ATTRIBUTE = "bundle-symbolic-name";
+ String BUNDLE_VERSION_ATTRIBUTE = "bundle-version";
+
+ String KEYSTORE_LOCATION_DIRECTIVE = "keystore:";
+ String KEYSTORE_PROVIDER_DIRECTIVE = "provider:";
+ String KEYSTORE_PASSWORD_DIRECTIVE = "password:";
+ String SIGN_PASSWORD_DIRECTIVE = "sign-password:";
+
+ String NONE = "none";
+
+ String directives[] = {
+ SPLIT_PACKAGE_DIRECTIVE, NO_IMPORT_DIRECTIVE, IMPORT_DIRECTIVE,
+ RESOLUTION_DIRECTIVE, INCLUDE_DIRECTIVE, USES_DIRECTIVE,
+ EXCLUDE_DIRECTIVE, KEYSTORE_LOCATION_DIRECTIVE,
+ KEYSTORE_PROVIDER_DIRECTIVE, KEYSTORE_PASSWORD_DIRECTIVE,
+ SIGN_PASSWORD_DIRECTIVE,
+
+ // TODO
+ };
+
+ String USES_USES = "<<USES>>";
+ String CURRENT_USES = "@uses";
+ String IMPORT_REFERENCE = "reference";
+ String IMPORT_PRIVATE = "private";
+ String[] importDirectives = {
+ IMPORT_REFERENCE, IMPORT_PRIVATE };
+
+ static final Pattern VALID_PROPERTY_TYPES = Pattern
+ .compile("(String|Long|Double|Float|Integer|Byte|Character|Boolean|Short)");
+
+ String DEFAULT_BND_EXTENSION = ".bnd";
+ String DEFAULT_JAR_EXTENSION = ".jar";
+ String DEFAULT_BAR_EXTENSION = ".bar";
+ String[] METAPACKAGES = {
+ "META-INF", "OSGI-INF", "OSGI-OPT" };
+
+ int STRATEGY_HIGHEST = 1;
+ int STRATEGY_LOWEST = -1;
+
+ String CURRENT_VERSION = "@";
+ String CURRENT_PACKAGE = "@package";
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java
new file mode 100644
index 0000000..771dbf7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/EmbeddedResource.java
@@ -0,0 +1,88 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.zip.*;
+
+public class EmbeddedResource implements Resource {
+ byte data[];
+ long lastModified;
+ String extra;
+
+ public EmbeddedResource(byte data[], long lastModified) {
+ this.data = data;
+ this.lastModified = lastModified;
+ }
+
+ public InputStream openInputStream() throws FileNotFoundException {
+ return new ByteArrayInputStream(data);
+ }
+
+ public void write(OutputStream out) throws IOException {
+ out.write(data);
+ }
+
+ public String toString() {
+ return ":" + data.length + ":";
+ }
+
+ public static void build(Jar jar, InputStream in, long lastModified) throws IOException {
+ ZipInputStream jin = new ZipInputStream(in);
+ ZipEntry entry = jin.getNextEntry();
+ while (entry != null) {
+ if (!entry.isDirectory()) {
+ byte data[] = collect(jin);
+ jar.putResource(entry.getName(), new EmbeddedResource(data, lastModified), true);
+ }
+ entry = jin.getNextEntry();
+ }
+ jin.close();
+ }
+
+ /**
+ * Convenience method to turn an inputstream into a byte array. The method
+ * uses a recursive algorithm to minimize memory usage.
+ *
+ * @param in stream with data
+ * @param offset where we are in the stream
+ * @returns byte array filled with data
+ */
+ static byte[] collect(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ copy(in,out);
+ return out.toByteArray();
+ }
+
+ static void copy(InputStream in, OutputStream out) throws IOException {
+ int available = in.available();
+ if ( available <= 10000)
+ available = 64000;
+ byte [] buffer = new byte[available];
+ int size;
+ while ( (size=in.read(buffer))>0)
+ out.write(buffer,0,size);
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public static void build(Jar sub, Resource resource) throws IOException {
+ InputStream in = resource.openInputStream();
+ build(sub,in, resource.lastModified());
+ in.close();
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public long size() {
+ return data.length;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java
new file mode 100644
index 0000000..fa70e21
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/FileResource.java
@@ -0,0 +1,86 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.regex.Pattern;
+
+public class FileResource implements Resource {
+ File file;
+ String extra;
+
+ public FileResource(File file) {
+ this.file = file;
+ }
+
+ public InputStream openInputStream() throws FileNotFoundException {
+ return new FileInputStream(file);
+ }
+
+ public static void build(Jar jar, File directory, Pattern doNotCopy) {
+ traverse(
+ jar,
+ directory.getAbsolutePath().length(),
+ directory,
+ doNotCopy);
+ }
+
+ public String toString() {
+ return ":" + file.getName() + ":";
+ }
+
+ public void write(OutputStream out) throws IOException {
+ copy(this, out);
+ }
+
+ static synchronized void copy(Resource resource, OutputStream out)
+ throws IOException {
+ InputStream in = resource.openInputStream();
+ try {
+ byte buffer[] = new byte[20000];
+ int size = in.read(buffer);
+ while (size > 0) {
+ out.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+
+ static void traverse(Jar jar, int rootlength, File directory,
+ Pattern doNotCopy) {
+ if (doNotCopy.matcher(directory.getName()).matches())
+ return;
+
+ File files[] = directory.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].isDirectory())
+ traverse(jar, rootlength, files[i], doNotCopy);
+ else {
+ String path = files[i].getAbsolutePath().substring(
+ rootlength + 1);
+ if (File.separatorChar != '/')
+ path = path.replace(File.separatorChar, '/');
+ jar.putResource(path, new FileResource(files[i]), true);
+ }
+ }
+ }
+
+ public long lastModified() {
+ return file.lastModified();
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public long size() {
+ return (int) file.length();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
new file mode 100644
index 0000000..a185ff7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Instruction.java
@@ -0,0 +1,113 @@
+/*
+ * $Header: /cvsroot/bnd/aQute.bnd/src/aQute/lib/osgi/Instruction.java,v 1.1 2009/01/19 14:08:30 pkriens Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aQute.lib.osgi;
+
+import java.util.regex.*;
+
+public class Instruction {
+ Pattern pattern;
+ String instruction;
+ boolean negated;
+ boolean optional;
+
+ public Instruction(String instruction, boolean negated) {
+ this.instruction = instruction;
+ this.negated = negated;
+ }
+
+ public boolean matches(String value) {
+ return getMatcher(value).matches();
+ }
+
+ public boolean isNegated() {
+ return negated;
+ }
+
+ public String getPattern() {
+ return instruction;
+ }
+
+ /**
+ * Convert a string based pattern to a regular expression based pattern.
+ * This is called an instruction, this object makes it easier to handle the
+ * different cases
+ *
+ * @param string
+ * @return
+ */
+ public static Instruction getPattern(String string) {
+ boolean negated = false;
+ if (string.startsWith("!")) {
+ negated = true;
+ string = string.substring(1);
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int c = 0; c < string.length(); c++) {
+ switch (string.charAt(c)) {
+ case '.':
+ sb.append("\\.");
+ break;
+ case '*':
+ sb.append(".*");
+ break;
+ case '?':
+ sb.append(".?");
+ break;
+ default:
+ sb.append(string.charAt(c));
+ break;
+ }
+ }
+ string = sb.toString();
+ if (string.endsWith("\\..*")) {
+ sb.append("|");
+ sb.append(string.substring(0, string.length() - 4));
+ }
+ return new Instruction(sb.toString(), negated);
+ }
+
+ public String toString() {
+ return getPattern();
+ }
+
+ public Matcher getMatcher(String value) {
+ if (pattern == null) {
+ pattern = Pattern.compile(instruction);
+ }
+ return pattern.matcher(value);
+ }
+
+ public int hashCode() {
+ return instruction.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ return other != null && (other instanceof Instruction)
+ && instruction.equals(((Instruction) other).instruction);
+ }
+
+ public void setOptional() {
+ optional = true;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java b/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java
new file mode 100644
index 0000000..81e5b75
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/InstructionFilter.java
@@ -0,0 +1,34 @@
+/* Copyright 2009 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.File;
+import java.io.FileFilter;
+
+public class InstructionFilter implements FileFilter {
+
+ private Instruction instruction;
+ private boolean recursive;
+
+ public InstructionFilter (Instruction instruction, boolean recursive) {
+ this.instruction = instruction;
+ this.recursive = recursive;
+ }
+ public boolean isRecursive() {
+ return recursive;
+ }
+ public boolean accept(File pathname) {
+ if (Analyzer.doNotCopy.matcher(pathname.getName()).matches()) {
+ return false;
+ }
+
+ if (pathname.isDirectory() && isRecursive()) {
+ return true;
+ }
+
+ if (instruction == null) {
+ return true;
+ }
+ return !instruction.isNegated() & instruction.matches(pathname.getName());
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java b/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java
new file mode 100644
index 0000000..dfed21f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Jar.java
@@ -0,0 +1,429 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.libg.reporter.*;
+
+public class Jar implements Closeable {
+ public static final Object[] EMPTY_ARRAY = new Jar[0];
+ Map<String, Resource> resources = new TreeMap<String, Resource>();
+ Map<String, Map<String, Resource>> directories = new TreeMap<String, Map<String, Resource>>();
+ Manifest manifest;
+ boolean manifestFirst;
+ String name;
+ File source;
+ ZipFile zipFile;
+ long lastModified;
+ String lastModifiedReason;
+ Reporter reporter;
+ boolean doNotTouchManifest;
+ boolean nomanifest;
+
+ public Jar(String name) {
+ this.name = name;
+ }
+
+ public Jar(String name, File dirOrFile) throws ZipException, IOException {
+ this(name);
+ source = dirOrFile;
+ if (dirOrFile.isDirectory())
+ FileResource.build(this, dirOrFile, Analyzer.doNotCopy);
+ else {
+ zipFile = ZipResource.build(this, dirOrFile);
+ }
+ }
+
+ public Jar(String name, InputStream in, long lastModified)
+ throws IOException {
+ this(name);
+ EmbeddedResource.build(this, in, lastModified);
+ }
+
+ public Jar(String name, String path) throws IOException {
+ this(name);
+ File f = new File(path);
+ InputStream in = new FileInputStream(f);
+ EmbeddedResource.build(this, in, f.lastModified());
+ in.close();
+ }
+
+ public Jar(File jar) throws IOException {
+ this(getName(jar), jar);
+ }
+
+ /**
+ * Make the JAR file name the project name if we get a src or bin directory.
+ *
+ * @param f
+ * @return
+ */
+ private static String getName(File f) {
+ f = f.getAbsoluteFile();
+ String name = f.getName();
+ if (name.equals("bin") || name.equals("src"))
+ return f.getParentFile().getName();
+ else {
+ if (name.endsWith(".jar"))
+ name = name.substring(0, name.length() - 4);
+ return name;
+ }
+ }
+
+ public Jar(String string, InputStream resourceAsStream) throws IOException {
+ this(string, resourceAsStream, 0);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return "Jar:" + name;
+ }
+
+ public boolean putResource(String path, Resource resource) {
+ return putResource(path, resource, true);
+ }
+
+ public boolean putResource(String path, Resource resource, boolean overwrite) {
+ updateModified(resource.lastModified(), path);
+
+ if (path.equals("META-INF/MANIFEST.MF")) {
+ manifest = null;
+ if (resources.isEmpty())
+ manifestFirst = true;
+ }
+ String dir = getDirectory(path);
+ Map<String, Resource> s = directories.get(dir);
+ if (s == null) {
+ s = new TreeMap<String, Resource>();
+ directories.put(dir, s);
+ int n = dir.lastIndexOf('/');
+ while (n > 0) {
+ String dd = dir.substring(0, n);
+ if (directories.containsKey(dd))
+ break;
+ directories.put(dd, null);
+ n = dd.lastIndexOf('/');
+ }
+ }
+ boolean duplicate = s.containsKey(path);
+ if (!duplicate || overwrite) {
+ resources.put(path, resource);
+ s.put(path, resource);
+ }
+ return duplicate;
+ }
+
+ public Resource getResource(String path) {
+ return resources.get(path);
+ }
+
+ private String getDirectory(String path) {
+ int n = path.lastIndexOf('/');
+ if (n < 0)
+ return "";
+
+ return path.substring(0, n);
+ }
+
+ public Map<String, Map<String, Resource>> getDirectories() {
+ return directories;
+ }
+
+ public Map<String, Resource> getResources() {
+ return resources;
+ }
+
+ public boolean addDirectory(Map<String, Resource> directory,
+ boolean overwrite) {
+ boolean duplicates = false;
+ if (directory == null)
+ return false;
+
+ for (Map.Entry<String, Resource> entry : directory.entrySet()) {
+ String key = entry.getKey();
+ if (!key.endsWith(".java")) {
+ duplicates |= putResource(key, (Resource) entry.getValue(),
+ overwrite);
+ }
+ }
+ return duplicates;
+ }
+
+ public Manifest getManifest() throws IOException {
+ if (manifest == null) {
+ Resource manifestResource = getResource("META-INF/MANIFEST.MF");
+ if (manifestResource != null) {
+ InputStream in = manifestResource.openInputStream();
+ manifest = new Manifest(in);
+ in.close();
+ }
+ }
+ return manifest;
+ }
+
+ public boolean exists(String path) {
+ return resources.containsKey(path);
+ }
+
+ public void setManifest(Manifest manifest) {
+ manifestFirst = true;
+ this.manifest = manifest;
+ }
+
+ public void write(File file) throws Exception {
+ try {
+ OutputStream out = new FileOutputStream(file);
+ write(out);
+ out.close();
+ return;
+
+ } catch (Exception t) {
+ file.delete();
+ throw t;
+ }
+ }
+
+ public void write(String file) throws Exception {
+ write(new File(file));
+ }
+
+ public void write(OutputStream out) throws IOException {
+ ZipOutputStream jout = nomanifest ? new ZipOutputStream(out) : new JarOutputStream(out);
+ Set<String> done = new HashSet<String>();
+
+ Set<String> directories = new HashSet<String>();
+ if (doNotTouchManifest) {
+ writeResource(jout, directories, "META-INF/MANIFEST.MF",
+ getResource("META-INF/MANIFEST.MF"));
+ done.add("META-INF/MANIFEST.MF");
+ } else if (!nomanifest)
+ doManifest(done, jout);
+
+ for (Map.Entry<String, Resource> entry : getResources().entrySet()) {
+ // Skip metainf contents
+ if (!done.contains(entry.getKey()))
+ writeResource(jout, directories, (String) entry.getKey(),
+ (Resource) entry.getValue());
+ }
+ jout.finish();
+ }
+
+ private void doManifest(Set<String> done, ZipOutputStream jout)
+ throws IOException {
+ if ( nomanifest )
+ return;
+
+ JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
+ jout.putNextEntry(ze);
+ writeManifest(jout);
+ jout.closeEntry();
+ done.add(ze.getName());
+ }
+
+ /**
+ * Cleanup the manifest for writing. Cleaning up consists of adding a space
+ * after any \n to prevent the manifest to see this newline as a delimiter.
+ *
+ * @param out
+ * Output
+ * @throws IOException
+ */
+
+ public void writeManifest(OutputStream out) throws IOException {
+ writeManifest(getManifest(), out);
+ }
+
+ public static void writeManifest(Manifest manifest, OutputStream out)
+ throws IOException {
+
+ manifest = clean(manifest);
+ manifest.write(out);
+ }
+
+ private static Manifest clean(Manifest org) {
+
+ Manifest result = new Manifest();
+ for (Map.Entry<?, ?> entry : org.getMainAttributes().entrySet()) {
+ String nice = clean((String) entry.getValue());
+ result.getMainAttributes().put(entry.getKey(), nice);
+ }
+ for (String name : org.getEntries().keySet()) {
+ Attributes attrs = result.getAttributes(name);
+ if (attrs == null) {
+ attrs = new Attributes();
+ result.getEntries().put(name, attrs);
+ }
+
+ for (Map.Entry<?, ?> entry : org.getAttributes(name).entrySet()) {
+ String nice = clean((String) entry.getValue());
+ attrs.put((Attributes.Name) entry.getKey(), nice);
+ }
+ }
+ return result;
+ }
+
+ private static String clean(String s) {
+ if (s.indexOf('\n') < 0)
+ return s;
+
+ StringBuffer sb = new StringBuffer(s);
+ for (int i = 0; i < sb.length(); i++) {
+ if (sb.charAt(i) == '\n')
+ sb.insert(++i, ' ');
+ }
+ return sb.toString();
+ }
+
+ private void writeResource(ZipOutputStream jout, Set<String> directories,
+ String path, Resource resource) throws IOException {
+ if (resource == null)
+ return;
+
+ createDirectories(directories, jout, path);
+ ZipEntry ze = new ZipEntry(path);
+ ze.setMethod(ZipEntry.DEFLATED);
+ long lastModified = resource.lastModified();
+ if (lastModified == 0L) {
+ lastModified = System.currentTimeMillis();
+ }
+ ze.setTime(lastModified);
+ if (resource.getExtra() != null)
+ ze.setExtra(resource.getExtra().getBytes());
+ jout.putNextEntry(ze);
+ try {
+ resource.write(jout);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot write resource: " + path
+ + " " + e);
+ }
+ jout.closeEntry();
+ }
+
+ void createDirectories(Set<String> directories, ZipOutputStream zip,
+ String name) throws IOException {
+ int index = name.lastIndexOf('/');
+ if (index > 0) {
+ String path = name.substring(0, index);
+ if (directories.contains(path))
+ return;
+ createDirectories(directories, zip, path);
+ ZipEntry ze = new ZipEntry(path + '/');
+ zip.putNextEntry(ze);
+ zip.closeEntry();
+ directories.add(path);
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * 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 sub, Pattern filter) {
+ boolean dupl = false;
+ for (String name : sub.getResources().keySet()) {
+ if ("META-INF/MANIFEST.MF".equals(name))
+ continue;
+
+ if (filter == null || filter.matcher(name).matches())
+ dupl |= putResource(name, sub.getResource(name), true);
+ }
+ return dupl;
+ }
+
+ public void close() {
+ if (zipFile != null)
+ try {
+ zipFile.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ resources = null;
+ directories = null;
+ manifest = null;
+ source = null;
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public void updateModified(long time, String reason) {
+ if (time > lastModified) {
+ lastModified = time;
+ lastModifiedReason = reason;
+ }
+ }
+
+ public void setReporter(Reporter reporter) {
+ this.reporter = reporter;
+ }
+
+ public boolean hasDirectory(String path) {
+ return directories.get(path) != null;
+ }
+
+ public List<String> getPackages() {
+ List<String> list = new ArrayList<String>(directories.size());
+
+ for (Iterator<String> i = directories.keySet().iterator(); i.hasNext();) {
+ String path = i.next();
+ String pack = path.replace('/', '.');
+ list.add(pack);
+ }
+ return list;
+ }
+
+ public File getSource() {
+ return source;
+ }
+
+ public boolean addAll(Jar src) {
+ return addAll(src, null);
+ }
+
+ public boolean rename(String oldPath, String newPath) {
+ Resource resource = remove(oldPath);
+ if (resource == null)
+ return false;
+
+ return putResource(newPath, resource);
+ }
+
+ public Resource remove(String path) {
+ Resource resource = resources.remove(path);
+ String dir = getDirectory(path);
+ Map<String, Resource> mdir = directories.get(dir);
+ // must be != null
+ mdir.remove(path);
+ return resource;
+ }
+
+ /**
+ * Make sure nobody touches the manifest! If the bundle is signed, we do not
+ * want anybody to touch the manifest after the digests have been
+ * calculated.
+ */
+ public void setDoNotTouchManifest() {
+ doNotTouchManifest = true;
+ }
+
+ public void setNoManifest(boolean b) {
+ nomanifest = b;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java
new file mode 100644
index 0000000..b633de1
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/JarResource.java
@@ -0,0 +1,42 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public class JarResource implements Resource {
+ Jar jar;
+ String extra;
+
+ public JarResource(Jar jar ) {
+ this.jar = jar;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public long lastModified() {
+ return jar.lastModified();
+ }
+
+
+ public void write(OutputStream out) throws IOException {
+ jar.write(out);
+ }
+
+ public InputStream openInputStream() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ out.close();
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ return in;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public Jar getJar() {
+ return jar;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java b/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java
new file mode 100644
index 0000000..12ab273
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Macro.java
@@ -0,0 +1,841 @@
+/* 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.lang.reflect.*;
+import java.net.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.libg.sed.*;
+import aQute.libg.version.*;
+
+/**
+ * Provide a macro processor. This processor can replace variables in strings
+ * based on a properties and a domain. The domain can implement functions that
+ * start with a "_" and take args[], the names of these functions are available
+ * as functions in the macro processor (without the _). Macros can nest to any
+ * depth but may not contain loops.
+ *
+ */
+public class Macro implements Replacer {
+ Properties properties;
+ Processor domain;
+ Object targets[];
+ boolean flattening;
+
+ public Macro(Properties properties, Processor domain, Object... targets) {
+ this.properties = properties;
+ this.domain = domain;
+ this.targets = targets;
+ if (targets != null) {
+ for (Object o : targets) {
+ assert o != null;
+ }
+ }
+ }
+
+ public Macro(Processor processor) {
+ this(new Properties(), processor);
+ }
+
+ public String process(String line) {
+ return process(line, null);
+ }
+
+ String process(String line, Link link) {
+ StringBuffer sb = new StringBuffer();
+ process(line, 0, '\u0000', '\u0000', sb, link);
+ return sb.toString();
+ }
+
+ int process(CharSequence org, int index, char begin, char end,
+ StringBuffer result, Link link) {
+ StringBuilder line = new StringBuilder(org);
+ int nesting = 1;
+
+ StringBuffer variable = new StringBuffer();
+ outer: while (index < line.length()) {
+ char c1 = line.charAt(index++);
+ if (c1 == end) {
+ if (--nesting == 0) {
+ result.append(replace(variable.toString(), link));
+ return index;
+ }
+ } else if (c1 == begin)
+ nesting++;
+ else if (c1 == '\\' && index < line.length() - 1
+ && line.charAt(index) == '$') {
+ // remove the escape backslash and interpret the dollar as a
+ // literal
+ index++;
+ variable.append('$');
+ continue outer;
+ } else if (c1 == '$' && index < line.length() - 2) {
+ char c2 = line.charAt(index);
+ char terminator = getTerminator(c2);
+ if (terminator != 0) {
+ index = process(line, index + 1, c2, terminator, variable,
+ link);
+ continue outer;
+ }
+ }
+ variable.append(c1);
+ }
+ result.append(variable);
+ return index;
+ }
+
+ public static char getTerminator(char c) {
+ switch (c) {
+ case '(':
+ return ')';
+ case '[':
+ return ']';
+ case '{':
+ return '}';
+ case '<':
+ return '>';
+ case '\u00ab': // Guillemet double << >>
+ return '\u00bb';
+ case '\u2039': // Guillemet single
+ return '\u203a';
+ }
+ return 0;
+ }
+
+ protected String replace(String key, Link link) {
+ if (link != null && link.contains(key))
+ return "${infinite:" + link.toString() + "}";
+
+ if (key != null) {
+ key = key.trim();
+ if (key.length() > 0) {
+ String value = (String) properties.getProperty(key);
+ if (value != null)
+ return process(value, new Link(link, key));
+
+ value = doCommands(key);
+ if (value != null)
+ return process(value, new Link(link, key));
+
+ if (key != null && key.trim().length() > 0) {
+ value = System.getProperty(key);
+ if (value != null)
+ return value;
+ }
+ if (!flattening)
+ domain.warning("No translation found for macro: " + key);
+ } else {
+ domain.warning("Found empty macro key");
+ }
+ } else {
+ domain.warning("Found null macro key");
+ }
+ return "${" + key + "}";
+ }
+
+ /**
+ * Parse the key as a command. A command consist of parameters separated by
+ * ':'.
+ *
+ * @param key
+ * @return
+ */
+ static Pattern commands = Pattern.compile("(?<!\\\\);");
+
+ private String doCommands(String key) {
+ String[] args = commands.split(key);
+ if (args == null || args.length == 0)
+ return null;
+
+ for (int i = 0; i < args.length; i++)
+ if (args[i].indexOf('\\') >= 0)
+ args[i] = args[i].replaceAll("\\\\;", ";");
+
+ Processor rover = domain;
+ while (rover != null) {
+ String result = doCommand(rover, args[0], args);
+ if (result != null)
+ return result;
+
+ rover = rover.getParent();
+ }
+
+ for (int i = 0; targets != null && i < targets.length; i++) {
+ String result = doCommand(targets[i], args[0], args);
+ if (result != null)
+ return result;
+ }
+
+ return doCommand(this, args[0], args);
+ }
+
+ private String doCommand(Object target, String method, String[] args) {
+ if (target == null)
+ ; // System.out.println("Huh? Target should never be null " +
+ // domain);
+ else {
+ String cname = "_" + method.replaceAll("-", "_");
+ try {
+ Method m = target.getClass().getMethod(cname,
+ new Class[] { String[].class });
+ return (String) m.invoke(target, new Object[] { args });
+ } catch (NoSuchMethodException e) {
+ // Ignore
+ } catch (InvocationTargetException e) {
+ domain.warning("Exception in replace: " + e.getCause());
+ e.printStackTrace();
+ } catch (Exception e) {
+ domain.warning("Exception in replace: " + e + " method="
+ + method);
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a unique list where the duplicates are removed.
+ *
+ * @param args
+ * @return
+ */
+ static String _uniqHelp = "${uniq;<list> ...}";
+
+ public String _uniq(String args[]) {
+ verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
+ Set<String> set = new LinkedHashSet<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], set);
+ }
+ return Processor.join(set, ",");
+ }
+
+ public String _filter(String args[]) {
+ return filter(args, false);
+ }
+
+ public String _filterout(String args[]) {
+ return filter(args, true);
+
+ }
+
+ static String _filterHelp = "${%s;<list>;<regex>}";
+
+ String filter(String[] args, boolean include) {
+ verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
+
+ Collection<String> list = new ArrayList<String>(Processor
+ .split(args[1]));
+ Pattern pattern = Pattern.compile(args[2]);
+
+ for (Iterator<String> i = list.iterator(); i.hasNext();) {
+ if (pattern.matcher(i.next()).matches() == include)
+ i.remove();
+ }
+ return Processor.join(list);
+ }
+
+ static String _sortHelp = "${sort;<list>...}";
+
+ public String _sort(String args[]) {
+ verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ Collections.sort(result);
+ return Processor.join(result);
+ }
+
+ static String _joinHelp = "${join;<list>...}";
+
+ public String _join(String args[]) {
+
+ verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ return Processor.join(result);
+ }
+
+ static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";
+
+ public String _if(String args[]) {
+ verifyCommand(args, _ifHelp, null, 3, 4);
+ String condition = args[1].trim();
+ if (condition.length() != 0)
+ return args[2];
+ if (args.length > 3)
+ return args[3];
+ else
+ return "";
+ }
+
+ public String _now(String args[]) {
+ return new Date().toString();
+ }
+
+ public static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
+
+ public String _fmodified(String args[]) throws Exception {
+ verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
+
+ long time = 0;
+ Collection<String> names = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], names);
+ }
+ for (String name : names) {
+ File f = new File(name);
+ if (f.exists() && f.lastModified() > time)
+ time = f.lastModified();
+ }
+ return "" + time;
+ }
+
+ public String _long2date(String args[]) {
+ try {
+ return new Date(Long.parseLong(args[1])).toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "not a valid long";
+ }
+
+ public String _literal(String args[]) {
+ if (args.length != 2)
+ throw new RuntimeException(
+ "Need a value for the ${literal;<value>} macro");
+ return "${" + args[1] + "}";
+ }
+
+ public String _def(String args[]) {
+ if (args.length != 2)
+ throw new RuntimeException(
+ "Need a value for the ${def;<value>} macro");
+
+ String value = properties.getProperty(args[1]);
+ if (value == null)
+ return "";
+ else
+ return value;
+ }
+
+ /**
+ *
+ * replace ; <list> ; regex ; replace
+ *
+ * @param args
+ * @return
+ */
+ public String _replace(String args[]) {
+ if (args.length != 4) {
+ domain.warning("Invalid nr of arguments to replace "
+ + Arrays.asList(args));
+ return null;
+ }
+
+ String list[] = args[1].split("\\s*,\\s*");
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+ for (int i = 0; i < list.length; i++) {
+ String element = list[i].trim();
+ if (!element.equals("")) {
+ sb.append(del);
+ sb.append(element.replaceAll(args[2], args[3]));
+ del = ", ";
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public String _warning(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.warning(process(args[i]));
+ }
+ return "";
+ }
+
+ public String _error(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.error(process(args[i]));
+ }
+ return "";
+ }
+
+ /**
+ * toclassname ; <path>.class ( , <path>.class ) *
+ *
+ * @param args
+ * @return
+ */
+ static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
+
+ public String _toclassname(String args[]) {
+ verifyCommand(args, _toclassnameHelp, null, 2, 2);
+ Collection<String> paths = Processor.split(args[1]);
+
+ List<String> names = new ArrayList<String>(paths.size());
+ for (String path : paths) {
+ if (path.endsWith(".class")) {
+ String name = path.substring(0, path.length() - 6).replace('/',
+ '.');
+ names.add(name);
+ } else if (path.endsWith(".java")) {
+ String name = path.substring(0, path.length() - 5).replace('/',
+ '.');
+ names.add(name);
+ } else {
+ domain
+ .warning("in toclassname, "
+ + args[1]
+ + " is not a class path because it does not end in .class");
+ }
+ }
+ return Processor.join(names, ",");
+ }
+
+ /**
+ * toclassname ; <path>.class ( , <path>.class ) *
+ *
+ * @param args
+ * @return
+ */
+
+ static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
+
+ public String _toclasspath(String args[]) {
+ verifyCommand(args, _toclasspathHelp, null, 2, 3);
+ boolean cl= true;
+ if (args.length>2)
+ cl = new Boolean(args[2]);
+
+ Collection<String> names = Processor.split(args[1]);
+ Collection<String> paths = new ArrayList<String>(names.size());
+ for (String name : names) {
+ String path = name.replace('.', '/') + (cl ? ".class" : "");
+ paths.add(path);
+ }
+ return Processor.join(paths, ",");
+ }
+
+ public String _dir(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${dir;...}");
+ return null;
+ } else {
+ String del = "";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ if (f.exists() && f.getParentFile().exists()) {
+ sb.append(del);
+ sb.append(f.getParentFile().getAbsolutePath());
+ del = ",";
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public String _basename(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${basename;...}");
+ return null;
+ } else {
+ String del = "";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ if (f.exists() && f.getParentFile().exists()) {
+ sb.append(del);
+ sb.append(f.getName());
+ del = ",";
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public String _isfile(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${isfile;...}");
+ return null;
+ } else {
+ boolean isfile = true;
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isfile &= f.isFile();
+ }
+ return isfile ? "true" : "false";
+ }
+
+ }
+
+ public String _isdir(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${isdir;...}");
+ return null;
+ } else {
+ boolean isdir = true;
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isdir &= f.isDirectory();
+ }
+ return isdir ? "true" : "false";
+ }
+
+ }
+
+ public String _tstamp(String args[]) {
+ String format = "yyyyMMddHHmm";
+ long now = System.currentTimeMillis();
+
+ if (args.length > 1) {
+ format = args[1];
+ if (args.length > 2) {
+ now = Long.parseLong(args[2]);
+ if (args.length > 3) {
+ domain.warning("Too many arguments for tstamp: "
+ + Arrays.toString(args));
+ }
+ }
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ return sdf.format(new Date(now));
+ }
+
+ /**
+ * Wildcard a directory. The lists can contain Instruction that are matched
+ * against the given directory
+ *
+ * ${wc;<dir>;<list>(;<list>)*}
+ *
+ * @author aqute
+ *
+ */
+
+ public String _lsr(String args[]) {
+ return ls(args, true);
+ }
+
+ public String _lsa(String args[]) {
+ return ls(args, false);
+ }
+
+ String ls(String args[], boolean relative) {
+ if (args.length < 2)
+ throw new IllegalArgumentException(
+ "the ${ls} macro must at least have a directory as parameter");
+
+ File dir = new File(args[1]);
+ if (!dir.isAbsolute())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter is not absolute: "
+ + dir);
+
+ if (!dir.exists())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter does not exist: "
+ + dir);
+
+ if (!dir.isDirectory())
+ throw new IllegalArgumentException(
+ "the ${ls} macro directory parameter points to a file instead of a directory: "
+ + dir);
+
+ String[] files = dir.list();
+ List<String> result;
+
+ if (args.length < 3) {
+ result = Arrays.asList(files);
+ } else
+ result = new ArrayList<String>();
+
+ for (int i = 2; i < args.length; i++) {
+ String parts[] = args[i].split("\\s*,\\s*");
+ for (String pattern : parts) {
+ // So make it in to an instruction
+ Instruction instr = Instruction.getPattern(pattern);
+
+ // For each project, match it against the instruction
+ for (int f = 0; f < files.length; f++) {
+ if (files[f] != null) {
+ if (instr.matches(files[f])) {
+ if (!instr.isNegated()) {
+ if (relative)
+ result.add(files[f]);
+ else
+ result.add(new File(dir, files[f])
+ .getAbsolutePath());
+ }
+ files[f] = null;
+ }
+ }
+ }
+ }
+ }
+ return Processor.join(result, ",");
+ }
+
+ public String _currenttime(String args[]) {
+ return Long.toString(System.currentTimeMillis());
+ }
+
+ /**
+ * Modify a version to set a version policy. Thed policy is a mask that is
+ * mapped to a version.
+ *
+ * <pre>
+ * + increment
+ * - decrement
+ * = maintain
+ * ˜ discard
+ *
+ * ==+ = maintain major, minor, increment micro, discard qualifier
+ * ˜˜˜= = just get the qualifier
+ * version="[${version;==;${@}},${version;=+;${@}})"
+ * </pre>
+ *
+ *
+ *
+ *
+ * @param args
+ * @return
+ */
+ static String _versionHelp = "${version;<mask>;<version>}, modify a version\n"
+ + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+ + "M ::= '+' | '-' | MQ\n"
+ + "MQ ::= '~' | '='";
+ static Pattern _versionPattern[] = new Pattern[] { null, null,
+ Pattern.compile("[-+=~]{0,3}[=~]?"), Verifier.VERSION };
+
+ public String _version(String args[]) {
+ verifyCommand(args, _versionHelp, null, 3, 3);
+
+ String mask = args[1];
+
+ Version version = new Version(args[2]);
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+
+ for (int i = 0; i < mask.length(); i++) {
+ char c = mask.charAt(i);
+ String result = null;
+ if (c != '~') {
+ if (i == 3) {
+ result = version.getQualifier();
+ } else if (Character.isDigit(c)) {
+ // Handle masks like +00, =+0
+ result = String.valueOf(c);
+ } else {
+ int x = version.get(i);
+ switch (c) {
+ case '+':
+ x++;
+ break;
+ case '-':
+ x--;
+ break;
+ case '=':
+ break;
+ }
+ result = Integer.toString(x);
+ }
+ if (result != null) {
+ sb.append(del);
+ del = ".";
+ sb.append(result);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * System command. Execute a command and insert the result.
+ *
+ * @param args
+ * @param help
+ * @param patterns
+ * @param low
+ * @param high
+ */
+ public String _system(String args[]) throws Exception {
+ verifyCommand(args,
+ "${system;<command>[;<in>]}, execute a system command", null,
+ 2, 3);
+ String command = args[1];
+ String input = null;
+
+ if (args.length > 2) {
+ input = args[2];
+ }
+
+ Process process = Runtime.getRuntime().exec(command, null,
+ domain.getBase());
+ if (input != null) {
+ process.getOutputStream().write(input.getBytes("UTF-8"));
+ }
+ process.getOutputStream().close();
+
+ String s = getString(process.getInputStream());
+ process.getInputStream().close();
+ int exitValue = process.waitFor();
+ if (exitValue != 0) {
+ domain.error("System command " + command + " failed with "
+ + exitValue);
+ }
+ return s.trim();
+ }
+
+ /**
+ * Get the contents of a file.
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+
+ public String _cat(String args[]) throws IOException {
+ verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2,
+ 2);
+ File f = domain.getFile(args[1]);
+ if (f.isFile()) {
+ InputStream in = new FileInputStream(f);
+ return getString(in);
+ } else if (f.isDirectory()) {
+ return Arrays.toString(f.list());
+ } else {
+ try {
+ URL url = new URL(args[1]);
+ InputStream in = url.openStream();
+ return getString(in);
+ } catch (MalformedURLException mfue) {
+ // Ignore here
+ }
+ return null;
+ }
+ }
+
+ public static String getString(InputStream in) throws IOException {
+ try {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+ String line = null;
+ while ((line = rdr.readLine()) != null) {
+ sb.append(line);
+ sb.append("\n");
+ }
+ return sb.toString();
+ } finally {
+ in.close();
+ }
+ }
+
+ public static void verifyCommand(String args[], String help,
+ Pattern[] patterns, int low, int high) {
+ String message = "";
+ if (args.length > high) {
+ message = "too many arguments";
+ } else if (args.length < low) {
+ message = "too few arguments";
+ } else {
+ for (int i = 0; patterns != null && i < patterns.length
+ && i < args.length - 1; i++) {
+ if (patterns[i] != null
+ && !patterns[i].matcher(args[i + 1]).matches()) {
+ message += String.format(
+ "Argument %s (%s) does not match %s\n", i, args[i],
+ patterns[i].pattern());
+ }
+ }
+ }
+ if (message.length() != 0) {
+ StringBuilder sb = new StringBuilder();
+ String del = "${";
+ for (String arg : args) {
+ sb.append(del);
+ sb.append(arg);
+ del = ";";
+ }
+ sb.append("}, is not understood. ");
+ sb.append(message);
+ throw new IllegalArgumentException(sb.toString());
+ }
+ }
+
+ // Helper class to track expansion of variables
+ // on the stack.
+ static class Link {
+ Link previous;
+ String key;
+
+ public Link(Link previous, String key) {
+ this.previous = previous;
+ this.key = key;
+ }
+
+ public boolean contains(String key) {
+ if (this.key.equals(key))
+ return true;
+
+ if (previous == null)
+ return false;
+
+ return previous.contains(key);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ String del = "[";
+ for (Link r = this; r != null; r = r.previous) {
+ sb.append(del);
+ sb.append(r.key);
+ del = ",";
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Take all the properties and translate them to actual values. This method
+ * takes the set properties and traverse them over all entries, including
+ * the default properties for that properties. The values no longer contain
+ * macros.
+ *
+ * @return A new Properties with the flattened values
+ */
+ public Properties getFlattenedProperties() {
+ // Some macros only work in a lower processor, so we
+ // do not report unknown macros while flattening
+ flattening = true;
+ try {
+ Properties flattened = new Properties();
+ for (Enumeration<?> e = properties.propertyNames(); e
+ .hasMoreElements();) {
+ String key = (String) e.nextElement();
+ if (!key.startsWith("_"))
+ if ( key.startsWith("-"))
+ flattened.put(key, properties.getProperty(key));
+ else
+ flattened.put(key, process(properties.getProperty(key)));
+ }
+ return flattened;
+ } finally {
+ flattening = false;
+ }
+ };
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java b/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java
new file mode 100644
index 0000000..f0d3134
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/OpCodes.java
@@ -0,0 +1,1196 @@
+package aQute.lib.osgi;
+
+public class OpCodes {
+ final static short nop = 0x00; // [No change] performs
+ // no
+ // operation
+ final static short aconst_null = 0x01; // ? null pushes a null
+ // reference onto the stack
+ final static short iconst_m1 = 0x02; // ? -1 loads the int
+ // value -1
+ // onto the stack
+ final static short iconst_0 = 0x03; // ? 0 loads the int
+ // value 0
+ // onto the stack
+ final static short iconst_1 = 0x04; // ? 1 loads the int
+ // value 1
+ // onto the stack
+ final static short iconst_2 = 0x05; // ? 2 loads the int
+ // value 2
+ // onto the stack
+ final static short iconst_3 = 0x06; // ? 3 loads the int
+ // value 3
+ // onto the stack
+ final static short iconst_4 = 0x07; // ? 4 loads the int
+ // value 4
+ // onto the stack
+ final static short iconst_5 = 0x08; // ? 5 loads the int
+ // value 5
+ // onto the stack
+ final static short lconst_0 = 0x09; // ? 0L pushes the long
+ // 0 onto
+ // the stack
+ final static short bipush = 0x10; // byte ? value pushes a
+ // byte
+ // onto the stack as an integer
+ // value
+ final static short sipush = 0x11; // byte1, byte2 ? value
+ // pushes a
+ // signed integer (byte1 << 8 +
+ // byte2) onto the stack
+ final static short ldc = 0x12; // index ? value pushes
+ // a
+ // constant #index from a
+ // constant pool (String, int,
+ // float or class type) onto the
+ // stack
+ final static short ldc_w = 0x13; // indexbyte1,
+ // indexbyte2 ?
+ // value pushes a constant
+ // #index from a constant pool
+ // (String, int, float or class
+ // type) onto the stack (wide
+ // index is constructed as
+ // indexbyte1 << 8 + indexbyte2)
+ final static short ldc2_w = 0x14; // indexbyte1,
+ // indexbyte2 ?
+ // value pushes a constant
+ // #index from a constant pool
+ // (double or long) onto the
+ // stack (wide index is
+ // constructed as indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short iload = 0x15; // index ? value loads
+ // an int
+ // value from a variable #index
+ final static short lload = 0x16; // index ? value load a
+ // long
+ // value from a local variable
+ // #index
+ final static short fload = 0x17; // index ? value loads a
+ // float
+ // value from a local variable
+ // #index
+ final static short dload = 0x18; // index ? value loads a
+ // double
+ // value from a local variable
+ // #index
+ final static short aload = 0x19; // index ? objectref
+ // loads a
+ // reference onto the stack from
+ // a local variable #index
+ final static short lload_2 = 0x20; // ? value load a long
+ // value
+ // from a local variable 2
+ final static short lload_3 = 0x21; // ? value load a long
+ // value
+ // from a local variable 3
+ final static short fload_0 = 0x22; // ? value loads a float
+ // value
+ // from local variable 0
+ final static short fload_1 = 0x23; // ? value loads a float
+ // value
+ // from local variable 1
+ final static short fload_2 = 0x24; // ? value loads a float
+ // value
+ // from local variable 2
+ final static short fload_3 = 0x25; // ? value loads a float
+ // value
+ // from local variable 3
+ final static short dload_0 = 0x26; // ? value loads a
+ // double from
+ // local variable 0
+ final static short dload_1 = 0x27; // ? value loads a
+ // double from
+ // local variable 1
+ final static short dload_2 = 0x28; // ? value loads a
+ // double from
+ // local variable 2
+ final static short dload_3 = 0x29; // ? value loads a
+ // double from
+ // local variable 3
+ final static short faload = 0x30; // arrayref, index ?
+ // value loads
+ // a float from an array
+ final static short daload = 0x31; // arrayref, index ?
+ // value loads
+ // a double from an array
+ final static short aaload = 0x32; // arrayref, index ?
+ // value loads
+ // onto the stack a reference
+ // from an array
+ final static short baload = 0x33; // arrayref, index ?
+ // value loads
+ // a byte or Boolean value from
+ // an array
+ final static short caload = 0x34; // arrayref, index ?
+ // value loads
+ // a char from an array
+ final static short saload = 0x35; // arrayref, index ?
+ // value load
+ // short from array
+ final static short istore = 0x36; // index value ? store
+ // int value
+ // into variable #index
+ final static short lstore = 0x37; // index value ? store a
+ // long
+ // value in a local variable
+ // #index
+ final static short fstore = 0x38; // index value ? stores
+ // a float
+ // value into a local variable
+ // #index
+ final static short dstore = 0x39; // index value ? stores
+ // a double
+ // value into a local variable
+ // #index
+ final static short lstore_1 = 0x40; // value ? store a long
+ // value in
+ // a local variable 1
+ final static short lstore_2 = 0x41; // value ? store a long
+ // value in
+ // a local variable 2
+ final static short lstore_3 = 0x42; // value ? store a long
+ // value in
+ // a local variable 3
+ final static short fstore_0 = 0x43; // value ? stores a
+ // float value
+ // into local variable 0
+ final static short fstore_1 = 0x44; // value ? stores a
+ // float value
+ // into local variable 1
+ final static short fstore_2 = 0x45; // value ? stores a
+ // float value
+ // into local variable 2
+ final static short fstore_3 = 0x46; // value ? stores a
+ // float value
+ // into local variable 3
+ final static short dstore_0 = 0x47; // value ? stores a
+ // double into
+ // local variable 0
+ final static short dstore_1 = 0x48; // value ? stores a
+ // double into
+ // local variable 1
+ final static short dstore_2 = 0x49; // value ? stores a
+ // double into
+ // local variable 2
+ final static short lastore = 0x50; // arrayref, index,
+ // value ?
+ // store a long to an array
+ final static short fastore = 0x51; // arreyref, index,
+ // value ?
+ // stores a float in an array
+ final static short dastore = 0x52; // arrayref, index,
+ // value ?
+ // stores a double into an array
+ final static short aastore = 0x53; // arrayref, index,
+ // value ?
+ // stores into a reference to an
+ // array
+ final static short bastore = 0x54; // arrayref, index,
+ // value ?
+ // stores a byte or Boolean
+ // value into an array
+ final static short castore = 0x55; // arrayref, index,
+ // value ?
+ // stores a char into an array
+ final static short sastore = 0x56; // arrayref, index,
+ // value ?
+ // store short to array
+ final static short pop = 0x57; // value ? discards the
+ // top
+ // value on the stack
+ final static short pop2 = 0x58; // {value2, value1} ?
+ // discards
+ // the top two values on the
+ // stack (or one value, if it is
+ // a double or long)
+ final static short dup = 0x59; // value ? value, value
+ // duplicates the value on top
+ // of the stack
+ final static short iadd = 0x60; // value1, value2 ?
+ // result adds
+ // two ints together
+ final static short ladd = 0x61; // value1, value2 ?
+ // result add
+ // two longs
+ final static short fadd = 0x62; // value1, value2 ?
+ // result adds
+ // two floats
+ final static short dadd = 0x63; // value1, value2 ?
+ // result adds
+ // two doubles
+ final static short isub = 0x64; // value1, value2 ?
+ // result int
+ // subtract
+ final static short lsub = 0x65; // value1, value2 ?
+ // result
+ // subtract two longs
+ final static short fsub = 0x66; // value1, value2 ?
+ // result
+ // subtracts two floats
+ final static short dsub = 0x67; // value1, value2 ?
+ // result
+ // subtracts a double from
+ // another
+ final static short imul = 0x68; // value1, value2 ?
+ // result
+ // multiply two integers
+ final static short lmul = 0x69; // value1, value2 ?
+ // result
+ // multiplies two longs
+ final static short irem = 0x70; // value1, value2 ?
+ // result
+ // logical int remainder
+ final static short lrem = 0x71; // value1, value2 ?
+ // result
+ // remainder of division of two
+ // longs
+ final static short frem = 0x72; // value1, value2 ?
+ // result gets
+ // the remainder from a division
+ // between two floats
+ final static short drem = 0x73; // value1, value2 ?
+ // result gets
+ // the remainder from a division
+ // between two doubles
+ final static short ineg = 0x74; // value ? result negate
+ // int
+ final static short lneg = 0x75; // value ? result
+ // negates a long
+ final static short fneg = 0x76; // value ? result
+ // negates a
+ // float
+ final static short dneg = 0x77; // value ? result
+ // negates a
+ // double
+ final static short ishl = 0x78; // value1, value2 ?
+ // result int
+ // shift left
+ final static short lshl = 0x79; // value1, value2 ?
+ // result
+ // bitwise shift left of a long
+ // value1 by value2 positions
+ final static short ior = 0x80; // value1, value2 ?
+ // result
+ // logical int or
+ final static short lor = 0x81; // value1, value2 ?
+ // result
+ // bitwise or of two longs
+ final static short ixor = 0x82; // value1, value2 ?
+ // result int
+ // xor
+ final static short lxor = 0x83; // value1, value2 ?
+ // result
+ // bitwise exclusive or of two
+ // longs
+ final static short iinc = 0x84; // index, const [No
+ // change]
+ // increment local variable
+ // #index by signed byte const
+ final static short i2l = 0x85; // value ? result
+ // converts an
+ // int into a long
+ final static short i2f = 0x86; // value ? result
+ // converts an
+ // int into a float
+ final static short i2d = 0x87; // value ? result
+ // converts an
+ // int into a double
+ final static short l2i = 0x88; // value ? result
+ // converts a
+ // long to an int
+ final static short l2f = 0x89; // value ? result
+ // converts a
+ // long to a float
+ final static short d2f = 0x90; // value ? result
+ // converts a
+ // double to a float
+ final static short i2b = 0x91; // value ? result
+ // converts an
+ // int into a byte
+ final static short i2c = 0x92; // value ? result
+ // converts an
+ // int into a character
+ final static short i2s = 0x93; // value ? result
+ // converts an
+ // int into a short
+ final static short lcmp = 0x94; // value1, value2 ?
+ // result
+ // compares two longs values
+ final static short fcmpl = 0x95; // value1, value2 ?
+ // result
+ // compares two floats
+ final static short fcmpg = 0x96; // value1, value2 ?
+ // result
+ // compares two floats
+ final static short dcmpl = 0x97; // value1, value2 ?
+ // result
+ // compares two doubles
+ final static short dcmpg = 0x98; // value1, value2 ?
+ // result
+ // compares two doubles
+ final static short ifeq = 0x99; // branchbyte1,
+ // branchbyte2
+ // value ? if value is 0, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short lconst_1 = 0x0a; // ? 1L pushes the long
+ // 1 onto
+ // the stack
+ final static short fconst_0 = 0x0b; // ? 0.0f pushes 0.0f on
+ // the
+ // stack
+ final static short fconst_1 = 0x0c; // ? 1.0f pushes 1.0f on
+ // the
+ // stack
+ final static short fconst_2 = 0x0d; // ? 2.0f pushes 2.0f on
+ // the
+ // stack
+ final static short dconst_0 = 0x0e; // ? 0.0 pushes the
+ // constant 0.0
+ // onto the stack
+ final static short dconst_1 = 0x0f; // ? 1.0 pushes the
+ // constant 1.0
+ // onto the stack
+ final static short iload_0 = 0x1a; // ? value loads an int
+ // value
+ // from variable 0
+ final static short iload_1 = 0x1b; // ? value loads an int
+ // value
+ // from variable 1
+ final static short iload_2 = 0x1c; // ? value loads an int
+ // value
+ // from variable 2
+ final static short iload_3 = 0x1d; // ? value loads an int
+ // value
+ // from variable 3
+ final static short lload_0 = 0x1e; // ? value load a long
+ // value
+ // from a local variable 0
+ final static short lload_1 = 0x1f; // ? value load a long
+ // value
+ // from a local variable 1
+ final static short aload_0 = 0x2a; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 0
+ final static short aload_1 = 0x2b; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 1
+ final static short aload_2 = 0x2c; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 2
+ final static short aload_3 = 0x2d; // ? objectref loads a
+ // reference
+ // onto the stack from local
+ // variable 3
+ final static short iaload = 0x2e; // arrayref, index ?
+ // value loads
+ // an int from an array
+ final static short laload = 0x2f; // arrayref, index ?
+ // value load
+ // a long from an array
+ final static short astore = 0x3a; // index objectref ?
+ // stores a
+ // reference into a local
+ // variable #index
+ final static short istore_0 = 0x3b; // value ? store int
+ // value into
+ // variable 0
+ final static short istore_1 = 0x3c; // value ? store int
+ // value into
+ // variable 1
+ final static short istore_2 = 0x3d; // value ? store int
+ // value into
+ // variable 2
+ final static short istore_3 = 0x3e; // value ? store int
+ // value into
+ // variable 3
+ final static short lstore_0 = 0x3f; // value ? store a long
+ // value in
+ // a local variable 0
+ final static short dstore_3 = 0x4a; // value ? stores a
+ // double into
+ // local variable 3
+ final static short astore_0 = 0x4b; // objectref ? stores a
+ // reference into local variable
+ // 0
+ final static short astore_1 = 0x4c; // objectref ? stores a
+ // reference into local variable
+ // 1
+ final static short astore_2 = 0x4d; // objectref ? stores a
+ // reference into local variable
+ // 2
+ final static short astore_3 = 0x4e; // objectref ? stores a
+ // reference into local variable
+ // 3
+ final static short iastore = 0x4f; // arrayref, index,
+ // value ?
+ // stores an int into an array
+ final static short dup_x1 = 0x5a; // value2, value1 ?
+ // value1,
+ // value2, value1 inserts a copy
+ // of the top value into the
+ // stack two values from the top
+ final static short dup_x2 = 0x5b; // value3, value2,
+ // value1 ?
+ // value1, value3, value2,
+ // value1 inserts a copy of the
+ // top value into the stack two
+ // (if value2 is double or long
+ // it takes up the entry of
+ // value3, too) or three values
+ // (if value2 is neither double
+ // nor long) from the top
+ final static short dup2 = 0x5c; // {value2, value1} ?
+ // {value2,
+ // value1}, {value2, value1}
+ // duplicate top two stack words
+ // (two values, if value1 is not
+ // double nor long; a single
+ // value, if value1 is double or
+ // long)
+ final static short dup2_x1 = 0x5d; // value3, {value2,
+ // value1} ?
+ // {value2, value1}, value3,
+ // {value2, value1} duplicate
+ // two words and insert beneath
+ // third word (see explanation
+ // above)
+ final static short dup2_x2 = 0x5e; // {value4, value3},
+ // {value2,
+ // value1} ? {value2, value1},
+ // {value4, value3}, {value2,
+ // value1} duplicate two words
+ // and insert beneath fourth
+ // word
+ final static short swap = 0x5f; // value2, value1 ?
+ // value1,
+ // value2 swaps two top words on
+ // the stack (note that value1
+ // and value2 must not be double
+ // or long)
+ final static short fmul = 0x6a; // value1, value2 ?
+ // result
+ // multiplies two floats
+ final static short dmul = 0x6b; // value1, value2 ?
+ // result
+ // multiplies two doubles
+ final static short idiv = 0x6c; // value1, value2 ?
+ // result
+ // divides two integers
+ final static short ldiv = 0x6d; // value1, value2 ?
+ // result
+ // divide two longs
+ final static short fdiv = 0x6e; // value1, value2 ?
+ // result
+ // divides two floats
+ final static short ddiv = 0x6f; // value1, value2 ?
+ // result
+ // divides two doubles
+ final static short ishr = 0x7a; // value1, value2 ?
+ // result int
+ // shift right
+ final static short lshr = 0x7b; // value1, value2 ?
+ // result
+ // bitwise shift right of a long
+ // value1 by value2 positions
+ final static short iushr = 0x7c; // value1, value2 ?
+ // result int
+ // shift right
+ final static short lushr = 0x7d; // value1, value2 ?
+ // result
+ // bitwise shift right of a long
+ // value1 by value2 positions,
+ // unsigned
+ final static short iand = 0x7e; // value1, value2 ?
+ // result
+ // performs a logical and on two
+ // integers
+ final static short land = 0x7f; // value1, value2 ?
+ // result
+ // bitwise and of two longs
+ final static short l2d = 0x8a; // value ? result
+ // converts a
+ // long to a double
+ final static short f2i = 0x8b; // value ? result
+ // converts a
+ // float to an int
+ final static short f2l = 0x8c; // value ? result
+ // converts a
+ // float to a long
+ final static short f2d = 0x8d; // value ? result
+ // converts a
+ // float to a double
+ final static short d2i = 0x8e; // value ? result
+ // converts a
+ // double to an int
+ final static short d2l = 0x8f; // value ? result
+ // converts a
+ // double to a long
+ final static short ifne = 0x9a; // branchbyte1,
+ // branchbyte2
+ // value ? if value is not 0,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short iflt = 0x9b; // branchbyte1,
+ // branchbyte2
+ // value ? if value is less than
+ // 0, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifge = 0x9c; // branchbyte1,
+ // branchbyte2
+ // value ? if value is greater
+ // than or equal to 0, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifgt = 0x9d; // branchbyte1,
+ // branchbyte2
+ // value ? if value is greater
+ // than 0, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifle = 0x9e; // branchbyte1,
+ // branchbyte2
+ // value ? if value is less than
+ // or equal to 0, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpeq = 0x9f; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if ints are
+ // equal, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpne = 0xa0; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if ints are
+ // not equal, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmplt = 0xa1; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // less than value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpge = 0xa2; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // greater than or equal to
+ // value2, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmpgt = 0xa3; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // greater than value2, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_icmple = 0xa4; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if value1 is
+ // less than or equal to value2,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_acmpeq = 0xa5; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if
+ // references are equal, branch
+ // to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short if_acmpne = 0xa6; // branchbyte1,
+ // branchbyte2
+ // value1, value2 ? if
+ // references are not equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short goto_ = 0xa7; // branchbyte1,
+ // branchbyte2 [no
+ // change] goes to another
+ // instruction at branchoffset
+ // (signed short constructed
+ // from unsigned bytes
+ // branchbyte1 << 8 +
+ // branchbyte2)
+ final static short jsr = 0xa8; // branchbyte1,
+ // branchbyte2 ?
+ // address jump to subroutine at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2) and place the
+ // return address on the stack
+ final static short ret = 0xa9; // index [No change]
+ // continue
+ // execution from address taken
+ // from a local variable #index
+ // (the asymmetry with jsr is
+ // intentional)
+ final static short tableswitch = 0xaa; // [0-3 bytes padding],
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // lowbyte1, lowbyte2, lowbyte3,
+ // lowbyte4, highbyte1,
+ // highbyte2, highbyte3,
+ // highbyte4, jump offsets...
+ // index ? continue execution
+ // from an address in the table
+ // at offset index
+ final static short lookupswitch = 0xab; // <0-3 bytes padding>,
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // npairs1, npairs2, npairs3,
+ // npairs4, match-offset
+ // pairs... key ? a target
+ // address is looked up from a
+ // table using a key and
+ // execution continues from the
+ // instruction at that address
+ final static short ireturn = 0xac; // value ? [empty]
+ // returns an
+ // integer from a method
+ final static short lreturn = 0xad; // value ? [empty]
+ // returns a
+ // long value
+ final static short freturn = 0xae; // value ? [empty]
+ // returns a
+ // float
+ final static short dreturn = 0xaf; // value ? [empty]
+ // returns a
+ // double from a method
+ final static short areturn = 0xb0; // objectref ? [empty]
+ // returns a
+ // reference from a method
+ final static short return_ = 0xb1; // ? [empty] return void
+ // from
+ // method
+ final static short getstatic = 0xb2; // index1, index2 ?
+ // value gets a
+ // static field value of a
+ // class, where the field is
+ // identified by field reference
+ // in the constant pool index
+ // (index1 << 8 + index2)
+ final static short putstatic = 0xb3; // indexbyte1,
+ // indexbyte2 value
+ // ? set static field to value
+ // in a class, where the field
+ // is identified by a field
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short getfield = 0xb4; // index1, index2
+ // objectref ?
+ // value gets a field value of
+ // an object objectref, where
+ // the field is identified by
+ // field reference in the
+ // constant pool index (index1
+ // << 8 + index2)
+ final static short putfield = 0xb5; // indexbyte1,
+ // indexbyte2
+ // objectref, value ? set field
+ // to value in an object
+ // objectref, where the field is
+ // identified by a field
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short invokevirtual = 0xb6; // indexbyte1,
+ // indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke virtual method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short invokespecial = 0xb7; // indexbyte1,
+ // indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke instance method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short invokestatic = 0xb8; // indexbyte1,
+ // indexbyte2 [arg1,
+ // arg2, ...] ? invoke a static
+ // method, where the method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short invokeinterface = 0xb9; // indexbyte1,
+ // indexbyte2,
+ // count, 0 objectref, [arg1,
+ // arg2, ...] ? invokes an
+ // interface method on object
+ // objectref, where the
+ // interface method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short xxxunusedxxx = 0xba; // this opcode is
+ // reserved "for
+ // historical reasons"
+ final static short new_ = 0xbb; // indexbyte1,
+ // indexbyte2 ?
+ // objectref creates new object
+ // of type identified by class
+ // reference in constant pool
+ // index (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short newarray = 0xbc; // atype count ?
+ // arrayref
+ // creates new array with count
+ // elements of primitive type
+ // identified by atype
+ final static short anewarray = 0xbd; // indexbyte1,
+ // indexbyte2 count
+ // ? arrayref creates a new
+ // array of references of length
+ // count and component type
+ // identified by the class
+ // reference index (indexbyte1
+ // << 8 + indexbyte2) in the
+ // constant pool
+ final static short arraylength = 0xbe; // arrayref ? length
+ // gets the
+ // length of an array
+ final static short athrow = 0xbf; // objectref ? [empty],
+ // objectref throws an error or
+ // exception (notice that the
+ // rest of the stack is cleared,
+ // leaving only a reference to
+ // the Throwable)
+ final static short checkcast = 0xc0; // indexbyte1,
+ // indexbyte2
+ // objectref ? objectref checks
+ // whether an objectref is of a
+ // certain type, the class
+ // reference of which is in the
+ // constant pool at index
+ // (indexbyte1 << 8 +
+ // indexbyte2)
+ final static short instanceof_ = 0xc1; // indexbyte1,
+ // indexbyte2
+ // objectref ? result determines
+ // if an object objectref is of
+ // a given type, identified by
+ // class reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ final static short monitorenter = 0xc2; // objectref ? enter
+ // monitor for
+ // object ("grab the lock" -
+ // start of synchronized()
+ // section)
+ final static short monitorexit = 0xc3; // objectref ? exit
+ // monitor for
+ // object ("release the lock" -
+ // end of synchronized()
+ // section)
+ final static short wide = 0xc4; // opcode, indexbyte1,
+ // indexbyte2
+ final static short multianewarray = 0xc5; // indexbyte1,
+ // indexbyte2,
+ // dimensions count1,
+ // [count2,...] ? arrayref
+ // create a new array of
+ // dimensions dimensions with
+ // elements of type identified
+ // by class reference in
+ // constant pool index
+ // (indexbyte1 << 8 +
+ // indexbyte2); the sizes of
+ // each dimension is identified
+ // by count1, [count2, etc]
+ final static short ifnull = 0xc6; // branchbyte1,
+ // branchbyte2
+ // value ? if value is null,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short ifnonnull = 0xc7; // branchbyte1,
+ // branchbyte2
+ // value ? if value is not null,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned
+ // bytes branchbyte1 << 8 +
+ // branchbyte2)
+ final static short goto_w = 0xc8; // branchbyte1,
+ // branchbyte2,
+ // branchbyte3, branchbyte4 [no
+ // change] goes to another
+ // instruction at branchoffset
+ // (signed int constructed from
+ // unsigned bytes branchbyte1 <<
+ // 24 + branchbyte2 << 16 +
+ // branchbyte3 << 8 +
+ // branchbyte4)
+ final static short jsr_w = 0xc9; // branchbyte1,
+ // branchbyte2,
+ // branchbyte3, branchbyte4 ?
+ // address jump to subroutine at
+ // branchoffset (signed int
+ // constructed from unsigned
+ // bytes branchbyte1 << 24 +
+ // branchbyte2 << 16 +
+ // branchbyte3 << 8 +
+ // branchbyte4) and place the
+ // return address on the stack
+ final static short breakpoint = 0xca; // reserved for
+ // breakpoints in
+ // Java debuggers; should not
+ // appear in any class file
+ final static short impdep1 = 0xfe; // reserved for
+ // implementation-dependent
+ // operations within debuggers;
+ // should not appear in any
+ // class file
+ final static short impdep2 = 0xff; // reserved for
+ // implementation-dependent
+ // operations within debuggers;
+ // should not appear in any
+ // class file
+
+ final static byte OFFSETS[] = new byte[256];
+
+ static {
+ OFFSETS[bipush] = 1; // byte ? value pushes a byte onto the
+ // stack as an integer value
+ OFFSETS[sipush] = 2; // byte1, byte2 ? value pushes a signed
+ // integer (byte1 << 8 + byte2) onto the
+ // stack
+ OFFSETS[ldc] = 1; // index ? value pushes a constant
+ // #index from a constant pool (String,
+ // int, float or class type) onto the
+ // stack
+ OFFSETS[ldc_w] = 2; // indexbyte1, indexbyte2 ? value pushes
+ // a constant #index from a constant
+ // pool (String, int, float or class
+ // type) onto the stack (wide index is
+ // constructed as indexbyte1 << 8 +
+ // indexbyte2)
+ OFFSETS[ldc2_w] = 2; // indexbyte1, indexbyte2 ? value pushes
+ // a constant #index from a constant
+ // pool (double or long) onto the stack
+ // (wide index is constructed as
+ // indexbyte1 << 8 + indexbyte2)
+ OFFSETS[iload] = 1; // index ? value loads an int value from
+ // a variable #index
+ OFFSETS[lload] = 1; // index ? value load a long value from
+ // a local variable #index
+ OFFSETS[fload] = 1; // index ? value loads a float value
+ // from a local variable #index
+ OFFSETS[dload] = 1; // index ? value loads a double value
+ // from a local variable #index
+ OFFSETS[aload] = 1; // index ? objectref loads a reference
+ // onto the stack from a local variable
+ // #index
+ OFFSETS[istore] = 1; // index value ? store int value into
+ // variable #index
+ OFFSETS[lstore] = 1; // index value ? store a long value in a
+ // local variable #index
+ OFFSETS[fstore] = 1; // index value ? stores a float value
+ // into a local variable #index
+ OFFSETS[dstore] = 1; // index value ? stores a double value
+ // into a local variable #index
+ OFFSETS[iinc] = 2; // index, const [No change] increment
+ // local variable #index by signed byte
+ // const
+ OFFSETS[ifeq] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is 0, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[astore] = 1; // index objectref ? stores a reference
+ // into a local variable #index
+ OFFSETS[ifne] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is not 0, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[iflt] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is less than 0, branch to
+ // instruction at branchoffset (signed
+ // short constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifge] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is greater than or equal to 0,
+ // branch to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[ifgt] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is greater than 0, branch to
+ // instruction at branchoffset (signed
+ // short constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifle] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is less than or equal to 0,
+ // branch to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmpeq] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if ints are equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmpne] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if ints are not equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmplt] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is less than
+ // value2, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_icmpge] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is greater
+ // than or equal to value2, branch
+ // to instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmpgt] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is greater
+ // than value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_icmple] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if value1 is less than
+ // or equal to value2, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[if_acmpeq] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if references are equal,
+ // branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[if_acmpne] = 2; // branchbyte1, branchbyte2 value1,
+ // value2 ? if references are not
+ // equal, branch to instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[goto_] = 2; // branchbyte1, branchbyte2 [no change]
+ // goes to another instruction at
+ // branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[jsr] = 2; // branchbyte1, branchbyte2 ? address
+ // jump to subroutine at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2) and place the return
+ // address on the stack
+ OFFSETS[ret] = 1; // index [No change] continue execution
+ // from address taken from a local
+ // variable #index (the asymmetry with
+ // jsr is intentional)
+ OFFSETS[tableswitch] = -1; // [0-3 bytes padding],
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // lowbyte1, lowbyte2, lowbyte3,
+ // lowbyte4, highbyte1,
+ // highbyte2, highbyte3,
+ // highbyte4, jump offsets...
+ // index ? continue execution
+ // from an address in the table
+ // at offset index
+ OFFSETS[lookupswitch] = -1; // <0-3 bytes padding>,
+ // defaultbyte1, defaultbyte2,
+ // defaultbyte3, defaultbyte4,
+ // npairs1, npairs2, npairs3,
+ // npairs4, match-offset
+ // pairs... key ? a target
+ // address is looked up from a
+ // table using a key and
+ // execution continues from the
+ // instruction at that address
+ OFFSETS[getstatic] = 2; // index1, index2 ? value gets a
+ // static field value of a class,
+ // where the field is identified by
+ // field reference in the constant
+ // pool index (index1 << 8 + index2)
+ OFFSETS[putstatic] = 2; // indexbyte1, indexbyte2 value ?
+ // set static field to value in a
+ // class, where the field is
+ // identified by a field reference
+ // index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[getfield] = 2; // index1, index2 objectref ? value
+ // gets a field value of an object
+ // objectref, where the field is
+ // identified by field reference in
+ // the constant pool index (index1
+ // << 8 + index2)
+ OFFSETS[putfield] = 2; // indexbyte1, indexbyte2 objectref,
+ // value ? set field to value in an
+ // object objectref, where the field
+ // is identified by a field
+ // reference index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[invokevirtual] = 2; // indexbyte1, indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke virtual method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ OFFSETS[invokespecial] = 2; // indexbyte1, indexbyte2
+ // objectref, [arg1, arg2, ...]
+ // ? invoke instance method on
+ // object objectref, where the
+ // method is identified by
+ // method reference index in
+ // constant pool (indexbyte1 <<
+ // 8 + indexbyte2)
+ OFFSETS[invokestatic] = 2; // indexbyte1, indexbyte2 [arg1,
+ // arg2, ...] ? invoke a static
+ // method, where the method is
+ // identified by method
+ // reference index in constant
+ // pool (indexbyte1 << 8 +
+ // indexbyte2)
+ OFFSETS[invokeinterface] = 2; // indexbyte1, indexbyte2,
+ // count, 0 objectref,
+ // [arg1, arg2, ...] ?
+ // invokes an interface
+ // method on object
+ // objectref, where the
+ // interface method is
+ // identified by method
+ // reference index in
+ // constant pool (indexbyte1
+ // << 8 + indexbyte2)
+ OFFSETS[new_] = 2; // indexbyte1, indexbyte2 ? objectref
+ // creates new object of type identified
+ // by class reference in constant pool
+ // index (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[newarray] = 1; // atype count ? arrayref creates
+ // new array with count elements of
+ // primitive type identified by
+ // atype
+ OFFSETS[anewarray] = 2; // indexbyte1, indexbyte2 count ?
+ // arrayref creates a new array of
+ // references of length count and
+ // component type identified by the
+ // class reference index (indexbyte1
+ // << 8 + indexbyte2) in the
+ // constant pool
+ OFFSETS[checkcast] = 2; // indexbyte1, indexbyte2 objectref
+ // ? objectref checks whether an
+ // objectref is of a certain type,
+ // the class reference of which is
+ // in the constant pool at index
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[instanceof_] = 2; // indexbyte1, indexbyte2 objectref
+ // ? result determines if an object
+ // objectref is of a given type,
+ // identified by class reference
+ // index in constant pool
+ // (indexbyte1 << 8 + indexbyte2)
+ OFFSETS[wide] = 3; // opcode, indexbyte1, indexbyte2
+ OFFSETS[multianewarray] = 3; // indexbyte1, indexbyte2,
+ // dimensions count1,
+ // [count2,...] ? arrayref
+ // create a new array of
+ // dimensions dimensions with
+ // elements of type identified
+ // by class reference in
+ // constant pool index
+ // (indexbyte1 << 8 +
+ // indexbyte2); the sizes of
+ // each dimension is identified
+ // by count1, [count2, etc]
+ OFFSETS[ifnull] = 2; // branchbyte1, branchbyte2 value ? if
+ // value is null, branch to instruction
+ // at branchoffset (signed short
+ // constructed from unsigned bytes
+ // branchbyte1 << 8 + branchbyte2)
+ OFFSETS[ifnonnull] = 2; // branchbyte1, branchbyte2 value ?
+ // if value is not null, branch to
+ // instruction at branchoffset
+ // (signed short constructed from
+ // unsigned bytes branchbyte1 << 8 +
+ // branchbyte2)
+ OFFSETS[goto_w] = 4; // branchbyte1, branchbyte2,
+ // branchbyte3, branchbyte4 [no change]
+ // goes to another instruction at
+ // branchoffset (signed int constructed
+ // from unsigned bytes branchbyte1 << 24
+ // + branchbyte2 << 16 + branchbyte3 <<
+ // 8 + branchbyte4)
+ OFFSETS[jsr_w] = 4; // branchbyte1, branchbyte2,
+ // branchbyte3, branchbyte4 ? address
+ // jump to subroutine at branchoffset
+ // (signed int constructed from unsigned
+ // bytes branchbyte1 << 24 + branchbyte2
+ // << 16 + branchbyte3 << 8 +
+ // branchbyte4) and place the return
+ // address on the stack
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java
new file mode 100644
index 0000000..8299af8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/PreprocessResource.java
@@ -0,0 +1,37 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public class PreprocessResource extends AbstractResource {
+ final Resource resource;
+ final Processor processor;
+
+ public PreprocessResource(Processor processor, Resource r) {
+ super(r.lastModified());
+ this.processor = processor;
+ this.resource = r;
+ extra = resource.getExtra();
+ }
+
+ protected byte[] getBytes() throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(2000);
+ OutputStreamWriter osw = new OutputStreamWriter(bout);
+ PrintWriter pw = new PrintWriter(osw);
+ InputStream in = resource.openInputStream();
+ try {
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+ String line = rdr.readLine();
+ while (line != null) {
+ line = processor.getReplacer().process(line);
+ pw.println(line);
+ line = rdr.readLine();
+ }
+ pw.flush();
+ byte [] data= bout.toByteArray();
+ return data;
+
+ } finally {
+ in.close();
+ }
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java b/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java
new file mode 100644
index 0000000..459b498
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Processor.java
@@ -0,0 +1,1039 @@
+/* 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.net.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.make.*;
+import aQute.bnd.service.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+
+public class Processor implements Reporter, Constants, Closeable {
+ // TODO handle include files out of date
+ public static String DEFAULT_PLUGINS = ""; // "aQute.lib.spring.SpringComponent";
+ // TODO make splitter skip eagerly whitespace so trim is not necessary
+ public static String LIST_SPLITTER = "\\\\?\\s*,\\s*";
+ private List<String> errors = new ArrayList<String>();
+ private List<String> warnings = new ArrayList<String>();
+ boolean pedantic;
+ boolean trace;
+ boolean exceptions;
+ boolean fileMustExist = true;
+
+ List<Object> plugins;
+ private File base = new File("").getAbsoluteFile();
+ private List<Closeable> toBeClosed = newList();
+
+ final Properties properties;
+ private Macro replacer;
+ private long lastModified;
+ private File propertiesFile;
+ private boolean fixup = true;
+ long modified;
+ Processor parent;
+ Set<File> included;
+ CL pluginLoader;
+ Collection<String> filter;
+
+ public Processor() {
+ properties = new Properties();
+ }
+
+ public Processor(Properties parent) {
+ properties = new Properties(parent);
+ }
+
+ public Processor(Processor parent) {
+ this(parent.properties);
+ this.parent = parent;
+ }
+
+ public void setParent(Processor processor) {
+ this.parent = processor;
+ }
+
+ public Processor getParent() {
+ return parent;
+ }
+
+ public Processor getTop() {
+ if (parent == null)
+ return this;
+ else
+ return parent.getTop();
+ }
+
+ public void getInfo(Processor processor, String prefix) {
+ if (isFailOk())
+ addAll(warnings, processor.getErrors(), prefix);
+ else
+ addAll(errors, processor.getErrors(), prefix);
+ addAll(warnings, processor.getWarnings(), prefix);
+
+ processor.errors.clear();
+ processor.warnings.clear();
+ }
+
+ public void getInfo(Processor processor) {
+ getInfo(processor, "");
+ }
+
+ private <T> void addAll(List<String> to, List<? extends T> from,
+ String prefix) {
+ for (T x : from) {
+ to.add(prefix + x);
+ }
+ }
+
+ public void warning(String string, Object... args) {
+ String s = String.format(string, args);
+ if (!warnings.contains(s))
+ warnings.add(s);
+ }
+
+ public void error(String string, Object... args) {
+ if (isFailOk())
+ warning(string, args);
+ else {
+ String s = String.format(string, args);
+ if (!errors.contains(s))
+ errors.add(s);
+ }
+ }
+
+ public void error(String string, Throwable t, Object... args) {
+ if (isFailOk())
+ warning(string + ": " + t, args);
+ else {
+ String s = String.format(string, args);
+ if (!errors.contains(s))
+ errors.add(s);
+ }
+ if (exceptions)
+ t.printStackTrace();
+ }
+
+ public List<String> getWarnings() {
+ return warnings;
+ }
+
+ public List<String> getErrors() {
+ return errors;
+ }
+
+ public Map<String, Map<String, String>> parseHeader(String value) {
+ return parseHeader(value, this);
+ }
+
+ /**
+ * Standard OSGi header parser.
+ *
+ * @param value
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ static public Map<String, Map<String, String>> parseHeader(String value,
+ Processor logger) {
+ return OSGiHeader.parseHeader(value, logger);
+ }
+
+ Map<String, Map<String, String>> getClauses(String header) {
+ return parseHeader(getProperty(header));
+ }
+
+ public void addClose(Closeable jar) {
+ toBeClosed.add(jar);
+ }
+
+ /**
+ * Remove all entries from a map that start with a specific prefix
+ *
+ * @param <T>
+ * @param source
+ * @param prefix
+ * @return
+ */
+ static <T> Map<String, T> removeKeys(Map<String, T> source, String prefix) {
+ Map<String, T> temp = new TreeMap<String, T>(source);
+ for (Iterator<String> p = temp.keySet().iterator(); p.hasNext();) {
+ String pack = (String) p.next();
+ if (pack.startsWith(prefix))
+ p.remove();
+ }
+ return temp;
+ }
+
+ public void progress(String s, Object... args) {
+ // System.out.println(s);
+ }
+
+ public boolean isPedantic() {
+ return pedantic;
+ }
+
+ public void setPedantic(boolean pedantic) { // System.out.println("Set
+ // pedantic: " + pedantic + " "
+ // + this );
+ this.pedantic = pedantic;
+ }
+
+ public static File getFile(File base, String file) {
+ File f = new File(file);
+ if (f.isAbsolute())
+ return f;
+ int n;
+
+ f = base.getAbsoluteFile();
+ while ((n = file.indexOf('/')) > 0) {
+ String first = file.substring(0, n);
+ file = file.substring(n + 1);
+ if (first.equals(".."))
+ f = f.getParentFile();
+ else
+ f = new File(f, first);
+ }
+ return new File(f, file).getAbsoluteFile();
+ }
+
+ public File getFile(String file) {
+ return getFile(base, file);
+ }
+
+ /**
+ * Return a list of plugins that implement the given class.
+ *
+ * @param clazz
+ * Each returned plugin implements this class/interface
+ * @return A list of plugins
+ */
+ public <T> List<T> getPlugins(Class<T> clazz) {
+ List<T> l = new ArrayList<T>();
+ List<Object> all = getPlugins();
+ for (Object plugin : all) {
+ if (clazz.isInstance(plugin))
+ l.add(clazz.cast(plugin));
+ }
+ return l;
+ }
+
+ /**
+ * Return a list of plugins. Plugins are defined with the -plugin command.
+ * They are class names, optionally associated with attributes. Plugins can
+ * implement the Plugin interface to see these attributes.
+ *
+ * Any object can be a plugin.
+ *
+ * @return
+ */
+ protected List<Object> getPlugins() {
+ // if (parent != null) {
+ // List<Object> val = parent.getPlugins();
+ // getInfo(parent);
+ // return val;
+ // }
+ if (this.plugins != null)
+ return this.plugins;
+
+ String spe = getProperty(Analyzer.PLUGIN, DEFAULT_PLUGINS);
+ Map<String, Map<String, String>> plugins = parseHeader(spe);
+ List<Object> list = new ArrayList<Object>();
+
+ // Add the default plugins. Only if non is specified
+ // will they be removed.
+ list.add(new MakeBnd());
+ list.add(new MakeCopy());
+ list.add(new ServiceComponent());
+
+ for (Map.Entry<String, Map<String, String>> entry : plugins.entrySet()) {
+ String key = (String) entry.getKey();
+ if (key.equals(NONE))
+ return this.plugins = newList();
+
+ try {
+ CL loader = getLoader();
+ String path = entry.getValue().get(PATH_DIRECTIVE);
+ if (path != null) {
+ File f = getFile(path).getAbsoluteFile();
+ loader.add(f.toURL());
+ }
+
+ trace("Using plugin %s", key);
+
+ // Plugins could use the same class with different
+ // parameters so we could have duplicate names Remove
+ // the ! added by the parser to make each name unique.
+ key = removeDuplicateMarker(key);
+
+ Class<?> c = (Class<?>) loader.loadClass(key);
+ Object plugin = c.newInstance();
+ if (plugin instanceof Plugin) {
+ ((Plugin) plugin).setProperties(entry.getValue());
+ ((Plugin) plugin).setReporter(this);
+ }
+ list.add(plugin);
+ } catch (Exception e) {
+
+ error("Problem loading the plugin: " + key + " exception: " + e);
+ }
+ }
+ return this.plugins = list;
+ }
+
+ public boolean isFailOk() {
+ String v = getProperty(Analyzer.FAIL_OK, null);
+ return v != null && v.equalsIgnoreCase("true");
+ }
+
+ public File getBase() {
+ return base;
+ }
+
+ public void setBase(File base) {
+ this.base = base;
+ }
+
+ public void clear() {
+ errors.clear();
+ warnings.clear();
+ }
+
+ public void trace(String msg, Object... parms) {
+ if (trace) {
+ System.out.printf("# " + msg + "\n", parms);
+ }
+ }
+
+ public <T> List<T> newList() {
+ return new ArrayList<T>();
+ }
+
+ public <T> Set<T> newSet() {
+ return new TreeSet<T>();
+ }
+
+ public static <K, V> Map<K, V> newMap() {
+ return new LinkedHashMap<K, V>();
+ }
+
+ public static <K, V> Map<K, V> newHashMap() {
+ return new HashMap<K, V>();
+ }
+
+ public <T> List<T> newList(Collection<T> t) {
+ return new ArrayList<T>(t);
+ }
+
+ public <T> Set<T> newSet(Collection<T> t) {
+ return new TreeSet<T>(t);
+ }
+
+ public <K, V> Map<K, V> newMap(Map<K, V> t) {
+ return new LinkedHashMap<K, V>(t);
+ }
+
+ public void close() {
+ for (Closeable c : toBeClosed) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ // Who cares?
+ }
+ }
+ toBeClosed = null;
+ }
+
+ public String _basedir(String args[]) {
+ if (base == null)
+ throw new IllegalArgumentException("No base dir set");
+
+ return base.getAbsolutePath();
+ }
+
+ /**
+ * Property handling ...
+ *
+ * @return
+ */
+
+ public Properties getProperties() {
+ if (fixup) {
+ fixup = false;
+ begin();
+ }
+
+ return properties;
+ }
+
+ public String getProperty(String key) {
+ return getProperty(key, null);
+ }
+
+ 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) {
+ for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
+ String key = (String) e.nextElement();
+ String value = properties.getProperty(key);
+ if (override || !getProperties().containsKey(key))
+ setProperty(key, value);
+ }
+ }
+
+ public void setProperties(Properties properties) {
+ doIncludes(getBase(), properties, new HashSet<String>());
+ this.properties.putAll(properties);
+ }
+
+ public void addProperties(File file) throws Exception {
+ addIncluded(file);
+ Properties p = loadProperties(file);
+ setProperties(p);
+ }
+
+ public synchronized void addIncluded(File file) {
+ if (included == null)
+ included = new HashSet<File>();
+ included.add(file);
+ }
+
+ /**
+ * Inspect the properties and if you find -includes parse the line included
+ * manifest files or properties files. The files are relative from the given
+ * base, this is normally the base for the analyzer.
+ *
+ * @param ubase
+ * @param p
+ * @param done
+ * @throws IOException
+ */
+ private void doIncludes(File ubase, Properties p, Set<String> done) {
+ String includes = p.getProperty(INCLUDE);
+ if (includes != null) {
+ includes = getReplacer().process(includes);
+ p.remove(INCLUDE);
+ Collection<String> clauses = parseHeader(includes).keySet();
+
+ for (String value : clauses) {
+ boolean fileMustExist = true;
+ boolean overwrite = true;
+ while (true) {
+ if (value.startsWith("-")) {
+ fileMustExist = false;
+ value = value.substring(1).trim();
+ } else if (value.startsWith("~")) {
+ // Overwrite properties!
+ overwrite = false;
+ value = value.substring(1).trim();
+ } else
+ break;
+ }
+ try {
+ File file = getFile(ubase, value).getAbsoluteFile();
+ if (file.isFile()) {
+ if (included != null && included.contains(file)) {
+ error("Cyclic include of " + file);
+ } else {
+ addIncluded(file);
+ updateModified(file.lastModified(), "Include "
+ + value);
+ InputStream in = new FileInputStream(file);
+ Properties sub;
+ if (file.getName().toLowerCase().endsWith(".mf")) {
+ sub = getManifestAsProperties(in);
+ } else
+ sub = loadProperties(in, file.getAbsolutePath());
+ in.close();
+
+ doIncludes(file.getParentFile(), sub, done);
+ // make sure we do not override properties
+ if (!overwrite)
+ sub.keySet().removeAll(p.keySet());
+ p.putAll(sub);
+ }
+ } else {
+ if (fileMustExist)
+ error("Included file "
+ + file
+ + (file.exists() ? " does not exist"
+ : " is directory"));
+ }
+ } catch (IOException e) {
+ if (fileMustExist)
+ error("Error in processing included file: " + value, e);
+ }
+ }
+ }
+ }
+
+ public void unsetProperty(String string) {
+ getProperties().remove(string);
+
+ }
+
+ public boolean refresh() {
+ if (propertiesFile == null)
+ return false;
+
+ boolean changed = false;
+ if (included != null) {
+ for (File file : included) {
+
+ if (file.lastModified() > modified) {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ // System.out.println("Modified " + modified + " file: "
+ // + propertiesFile.lastModified() + " diff "
+ // + (modified - propertiesFile.lastModified()));
+
+ // Date last = new Date(propertiesFile.lastModified());
+ // Date current = new Date(modified);
+ changed |= modified < propertiesFile.lastModified();
+ if (changed) {
+ included = null;
+ properties.clear();
+ plugins = null;
+ setProperties(propertiesFile, base);
+ propertiesChanged();
+ return true;
+ }
+ return false;
+ }
+
+ public void propertiesChanged() {
+ }
+
+ /**
+ * 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 IOException {
+ propertiesFile = propertiesFile.getAbsoluteFile();
+ setProperties(propertiesFile, propertiesFile.getParentFile());
+ }
+
+ public void setProperties(File propertiesFile, File base) {
+ this.propertiesFile = propertiesFile.getAbsoluteFile();
+ setBase(base);
+ try {
+ if (propertiesFile.isFile()) {
+ // System.out.println("Loading properties " + propertiesFile);
+ long modified = propertiesFile.lastModified();
+ if (modified > System.currentTimeMillis() + 100) {
+ System.out.println("Huh? This is in the future "
+ + propertiesFile);
+ this.modified = System.currentTimeMillis();
+ } else
+ this.modified = modified;
+
+ included = null;
+ Properties p = loadProperties(propertiesFile);
+ setProperties(p);
+ } else {
+ if (fileMustExist) {
+ error("No such properties file: " + propertiesFile);
+ }
+ }
+ } catch (IOException e) {
+ error("Could not load properties " + propertiesFile);
+ }
+ }
+
+ protected void begin() {
+ if (isTrue(getProperty(PEDANTIC)))
+ setPedantic(true);
+ }
+
+ public static boolean isTrue(String value) {
+ return "true".equalsIgnoreCase(value);
+ }
+
+ /**
+ * Get a property with a proper default
+ *
+ * @param headerName
+ * @param deflt
+ * @return
+ */
+ public String getProperty(String key, String deflt) {
+ String value;
+
+ if (filter != null && filter.contains(key)) {
+ value = (String) getProperties().get(key);
+ } else
+ value = getProperties().getProperty(key);
+
+ if (value != null)
+ return getReplacer().process(value);
+ else if (deflt != null)
+ return getReplacer().process(deflt);
+ else
+ return null;
+ }
+
+ /**
+ * Helper to load a properties file from disk.
+ *
+ * @param file
+ * @return
+ * @throws IOException
+ */
+ public Properties loadProperties(File file) throws IOException {
+ updateModified(file.lastModified(), "Properties file: " + file);
+ InputStream in = new FileInputStream(file);
+ Properties p = loadProperties(in, file.getAbsolutePath());
+ in.close();
+ return p;
+ }
+
+ Properties loadProperties(InputStream in, String name) throws IOException {
+ int n = name.lastIndexOf('/');
+ if (n > 0)
+ name = name.substring(0, n);
+ if (name.length() == 0)
+ name = ".";
+
+ try {
+ Properties p = new Properties();
+ p.load(in);
+ return replaceAll(p, "\\$\\{\\.\\}", name);
+ } catch (Exception e) {
+ error("Error during loading properties file: " + name + ", error:"
+ + e);
+ return new Properties();
+ }
+ }
+
+ /**
+ * Replace a string in all the values of the map. This can be used to
+ * preassign variables that change. I.e. the base directory ${.} for a
+ * loaded properties
+ */
+
+ public static Properties replaceAll(Properties p, String pattern,
+ String replacement) {
+ Properties result = new Properties();
+ for (Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator(); i
+ .hasNext();) {
+ Map.Entry<Object, Object> entry = i.next();
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ value = value.replaceAll(pattern, replacement);
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ /**
+ * Merge the attributes of two maps, where the first map can contain
+ * wildcarded names. The idea is that the first map contains patterns (for
+ * example *) with a set of attributes. These patterns are matched against
+ * the found packages in actual. If they match, the result is set with the
+ * merged set of attributes. It is expected that the instructions are
+ * ordered so that the instructor can define which pattern matches first.
+ * Attributes in the instructions override any attributes from the actual.<br/>
+ *
+ * A pattern is a modified regexp so it looks like globbing. The * becomes a .*
+ * just like the ? becomes a .?. '.' are replaced with \\. Additionally, if
+ * the pattern starts with an exclamation mark, it will remove that matches
+ * for that pattern (- the !) from the working set. So the following
+ * patterns should work:
+ * <ul>
+ * <li>com.foo.bar</li>
+ * <li>com.foo.*</li>
+ * <li>com.foo.???</li>
+ * <li>com.*.[^b][^a][^r]</li>
+ * <li>!com.foo.* (throws away any match for com.foo.*)</li>
+ * </ul>
+ * Enough rope to hang the average developer I would say.
+ *
+ *
+ * @param instructions
+ * the instructions with patterns. A
+ * @param actual
+ * the actual found packages
+ */
+
+ public static Map<String, Map<String, String>> merge(String type,
+ Map<String, Map<String, String>> instructions,
+ Map<String, Map<String, String>> actual, Set<String> superfluous,
+ Map<String, Map<String, String>> ignored) {
+ Map<String, Map<String, String>> toVisit = new HashMap<String, Map<String, String>>(
+ actual); // we do not want to ruin our
+ // original
+ Map<String, Map<String, String>> result = newMap();
+ for (Iterator<String> i = instructions.keySet().iterator(); i.hasNext();) {
+ String instruction = i.next();
+ String originalInstruction = instruction;
+
+ Map<String, String> instructedAttributes = instructions
+ .get(instruction);
+
+ // Check if we have a fixed (starts with '=') or a
+ // duplicate name. A fixed name is added to the output without
+ // checking against the contents. Duplicates are marked
+ // at the end. In that case we do not pick up any contained
+ // information but just add them to the output including the
+ // marker.
+ if (instruction.startsWith("=")) {
+ result.put(instruction.substring(1), instructedAttributes);
+ superfluous.remove(originalInstruction);
+ continue;
+ }
+ if (isDuplicate(instruction)) {
+ result.put(instruction, instructedAttributes);
+ superfluous.remove(originalInstruction);
+ continue;
+ }
+
+ Instruction instr = Instruction.getPattern(instruction);
+
+ for (Iterator<String> p = toVisit.keySet().iterator(); p.hasNext();) {
+ String packageName = p.next();
+
+ if (instr.matches(packageName)) {
+ superfluous.remove(originalInstruction);
+ if (!instr.isNegated()) {
+ Map<String, String> newAttributes = new HashMap<String, String>();
+ newAttributes.putAll(actual.get(packageName));
+ newAttributes.putAll(instructedAttributes);
+ result.put(packageName, newAttributes);
+ } else if (ignored != null) {
+ ignored.put(packageName, new HashMap<String, String>());
+ }
+ p.remove(); // Can never match again for another pattern
+ }
+ }
+
+ }
+ return result;
+ }
+
+ /**
+ * Print a standard Map based OSGi header.
+ *
+ * @param exports
+ * map { name => Map { attribute|directive => value } }
+ * @return the clauses
+ */
+ public static String printClauses(Map<String, Map<String, String>> exports,
+ String allowedDirectives) {
+ return printClauses(exports, allowedDirectives, false);
+ }
+
+ public static String printClauses(Map<String, Map<String, String>> exports,
+ String allowedDirectives, boolean checkMultipleVersions) {
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+ for (Iterator<String> i = exports.keySet().iterator(); i.hasNext();) {
+ String name = i.next();
+ Map<String, String> clause = exports.get(name);
+
+ // We allow names to be duplicated in the input
+ // by ending them with '~'. This is necessary to use
+ // the package names as keys. However, we remove these
+ // suffixes in the output so that you can set multiple
+ // exports with different attributes.
+ String outname = removeDuplicateMarker(name);
+ sb.append(del);
+ sb.append(outname);
+ printClause(clause, allowedDirectives, sb);
+ del = ",";
+ }
+ return sb.toString();
+ }
+
+ public static void printClause(Map<String, String> map,
+ String allowedDirectives, StringBuffer sb) {
+
+ for (Iterator<String> j = map.keySet().iterator(); j.hasNext();) {
+ String key = j.next();
+
+ // Skip directives we do not recognize
+ if (!key.startsWith("x-")
+ && key.endsWith(":")
+ && (allowedDirectives == null || 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("\"");
+ }
+ }
+
+ public Macro getReplacer() {
+ if (replacer == null)
+ return replacer = new Macro(getProperties(), this,
+ getMacroDomains());
+ else
+ return replacer;
+ }
+
+ /**
+ * This should be overridden by subclasses to add extra macro command
+ * domains on the search list.
+ *
+ * @return
+ */
+ protected Object[] getMacroDomains() {
+ return new Object[] {};
+ }
+
+ /**
+ * Return the properties but expand all macros. This always returns a new
+ * Properties object that can be used in any way.
+ *
+ * @return
+ */
+ public Properties getFlattenedProperties() {
+ return getReplacer().getFlattenedProperties();
+
+ }
+
+ public void updateModified(long time, String reason) {
+ if (time > lastModified) {
+ lastModified = time;
+ }
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Read a manifest but return a properties object.
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+ public static Properties getManifestAsProperties(InputStream in)
+ throws IOException {
+ Properties p = new Properties();
+ Manifest manifest = new Manifest(in);
+ for (Iterator<Object> it = manifest.getMainAttributes().keySet()
+ .iterator(); it.hasNext();) {
+ Attributes.Name key = (Attributes.Name) it.next();
+ String value = manifest.getMainAttributes().getValue(key);
+ p.put(key.toString(), value);
+ }
+ return p;
+ }
+
+ public File getPropertiesFile() {
+ return propertiesFile;
+ }
+
+ public void setFileMustExist(boolean mustexist) {
+ fileMustExist = mustexist;
+ }
+
+ static public String read(InputStream in) throws Exception {
+ InputStreamReader ir = new InputStreamReader(in);
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ char chars[] = new char[1000];
+ int size = ir.read(chars);
+ while (size > 0) {
+ sb.append(chars, 0, size);
+ size = ir.read(chars);
+ }
+ } finally {
+ ir.close();
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Join a list.
+ *
+ * @param args
+ * @return
+ */
+ public static String join(Collection<?> list, String delimeter) {
+ if (list == null)
+ return "";
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Object item : list) {
+ sb.append(del);
+ sb.append(item);
+ del = delimeter;
+ }
+ return sb.toString();
+ }
+
+ public static String join(Object[] list, String delimeter) {
+ if (list == null)
+ return "";
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (Object item : list) {
+ sb.append(del);
+ sb.append(item);
+ del = delimeter;
+ }
+ return sb.toString();
+ }
+
+ public static String join(Collection<?> list) {
+ return join(list, ",");
+ }
+
+ public static void split(String s, Collection<String> set) {
+
+ String elements[] = s.trim().split(LIST_SPLITTER);
+ for (String element : elements) {
+ if (element.length() > 0)
+ set.add(element);
+ }
+ }
+
+ public static Collection<String> split(String s) {
+ return split(s, LIST_SPLITTER);
+ }
+
+ public static Collection<String> split(String s, String splitter) {
+ if (s != null)
+ s = s.trim();
+ if (s == null || s.trim().length() == 0)
+ return Collections.emptyList();
+
+ return Arrays.asList(s.split(splitter));
+ }
+
+ public boolean isExceptions() {
+ return exceptions;
+ }
+
+ public void setExceptions(boolean exceptions) {
+ this.exceptions = exceptions;
+ }
+
+ /**
+ * Make the file short if it is inside our base directory, otherwise long.
+ *
+ * @param f
+ * @return
+ */
+ public String normalize(String f) {
+ if (f.startsWith(base.getAbsolutePath() + "/"))
+ return f.substring(base.getAbsolutePath().length() + 1);
+ else
+ return f;
+ }
+
+ public String normalize(File f) {
+ return normalize(f.getAbsolutePath());
+ }
+
+ public static String removeDuplicateMarker(String key) {
+ int i = key.length() - 1;
+ while (i >= 0 && key.charAt(i) == DUPLICATE_MARKER)
+ --i;
+
+ return key.substring(0, i + 1);
+ }
+
+ public static boolean isDuplicate(String name) {
+ return name.length() > 0
+ && name.charAt(name.length() - 1) == DUPLICATE_MARKER;
+ }
+
+ public void setTrace(boolean x) {
+ trace = x;
+ }
+
+ static class CL extends URLClassLoader {
+
+ CL() {
+ super(new URL[0], Processor.class.getClassLoader());
+ }
+
+ void add(URL url) {
+ URL urls[] = getURLs();
+ for (URL u : urls) {
+ if (u.equals(url))
+ return;
+ }
+ super.addURL(url);
+ }
+
+ }
+
+ private CL getLoader() {
+ if (pluginLoader == null)
+ pluginLoader = new CL();
+ return pluginLoader;
+ }
+
+ public boolean exists() {
+ return base != null && base.exists();
+ }
+
+ public boolean isOk() {
+ return isFailOk() || (getErrors().size() == 0);
+ }
+
+ public boolean isPerfect() {
+ return getErrors().size() == 0 && getWarnings().size() == 0;
+ }
+
+ public void setForceLocal(Collection<String> local) {
+ filter = local;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java b/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java
new file mode 100644
index 0000000..d619f47
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Resource.java
@@ -0,0 +1,13 @@
+/* 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.*;
+
+public interface Resource {
+ InputStream openInputStream() throws IOException ;
+ void write(OutputStream out) throws IOException;
+ long lastModified();
+ void setExtra(String extra);
+ String getExtra();
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java
new file mode 100644
index 0000000..f43ac91
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/URLResource.java
@@ -0,0 +1,39 @@
+/* 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.net.*;
+
+public class URLResource implements Resource {
+ URL url;
+ String extra;
+
+ public URLResource(URL url) {
+ this.url = url;
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return url.openStream();
+ }
+
+ public String toString() {
+ return ":" + url.getPath() + ":";
+ }
+
+ public void write(OutputStream out) throws IOException {
+ FileResource.copy(this, out);
+ }
+
+ public long lastModified() {
+ return -1;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java b/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
new file mode 100644
index 0000000..85b36d4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
@@ -0,0 +1,792 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.libg.qtokens.*;
+
+public class Verifier extends Analyzer {
+
+ Jar dot;
+ Manifest manifest;
+ Map<String, Map<String, String>> referred = newHashMap();
+ Map<String, Map<String, String>> contained = newHashMap();
+ Map<String, Set<String>> uses = newHashMap();
+ Map<String, Map<String, String>> mimports;
+ Map<String, Map<String, String>> mdynimports;
+ Map<String, Map<String, String>> mexports;
+ List<Jar> bundleClasspath;
+ Map<String, Map<String, String>> ignore = newHashMap(); // Packages
+ // to
+ // ignore
+
+ Map<String, Clazz> classSpace;
+ boolean r3;
+ boolean usesRequire;
+ boolean fragment;
+ Attributes main;
+
+ final static Pattern EENAME = Pattern
+ .compile("CDC-1\\.0/Foundation-1\\.0"
+ + "|CDC-1\\.1/Foundation-1\\.1"
+ + "|OSGi/Minimum-1\\.[1-9]"
+ + "|JRE-1\\.1"
+ + "|J2SE-1\\.2"
+ + "|J2SE-1\\.3"
+ + "|J2SE-1\\.4"
+ + "|J2SE-1\\.5"
+ + "|JavaSE-1\\.6"
+ + "|JavaSE-1\\.7"
+ + "|PersonalJava-1\\.1"
+ + "|PersonalJava-1\\.2"
+ + "|CDC-1\\.0/PersonalBasis-1\\.0"
+ + "|CDC-1\\.0/PersonalJava-1\\.0");
+
+ final static Pattern BUNDLEMANIFESTVERSION = Pattern
+ .compile("2");
+ public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
+ public final static Pattern SYMBOLICNAME = Pattern
+ .compile(SYMBOLICNAME_STRING);
+
+ public final static String VERSION_STRING = "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
+ public final static Pattern VERSION = Pattern
+ .compile(VERSION_STRING);
+ final static Pattern FILTEROP = Pattern
+ .compile("=|<=|>=|~=");
+ public final static Pattern VERSIONRANGE = Pattern
+ .compile("((\\(|\\[)"
+ + VERSION_STRING
+ + ","
+ + VERSION_STRING
+ + "(\\]|\\)))|"
+ + VERSION_STRING);
+ final static Pattern FILE = Pattern
+ .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
+ final static Pattern WILDCARDPACKAGE = Pattern
+ .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
+ public final static Pattern ISO639 = Pattern
+ .compile("[A-Z][A-Z]");
+ public final static Pattern HEADER_PATTERN = Pattern
+ .compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
+ public final static Pattern TOKEN = Pattern
+ .compile("[-a-zA-Z0-9_]+");
+
+ public final static Pattern NUMBERPATTERN = Pattern
+ .compile("\\d+");
+ public final static Pattern PATHPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern FQNPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern URLPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern ANYPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern FILTERPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern TRUEORFALSEPATTERN = Pattern.compile("true|false|TRUE|FALSE");
+ public static final Pattern WILDCARDNAMEPATTERN = Pattern.compile(".*");
+
+ public final static String EES[] = {
+ "CDC-1.0/Foundation-1.0",
+ "CDC-1.1/Foundation-1.1",
+ "OSGi/Minimum-1.0",
+ "OSGi/Minimum-1.1",
+ "OSGi/Minimum-1.2",
+ "JRE-1.1",
+ "J2SE-1.2",
+ "J2SE-1.3",
+ "J2SE-1.4",
+ "J2SE-1.5",
+ "JavaSE-1.6",
+ "JavaSE-1.7",
+ "PersonalJava-1.1",
+ "PersonalJava-1.2",
+ "CDC-1.0/PersonalBasis-1.0",
+ "CDC-1.0/PersonalJava-1.0"
+ };
+
+ public final static String OSNAMES[] = {
+ "AIX", // IBM
+ "DigitalUnix", // Compaq
+ "Embos", // Segger Embedded Software Solutions
+ "Epoc32", // SymbianOS Symbian OS
+ "FreeBSD", // Free BSD
+ "HPUX", // hp-ux Hewlett Packard
+ "IRIX", // Silicon Graphics
+ "Linux", // Open source
+ "MacOS", // Apple
+ "NetBSD", // Open source
+ "Netware", // Novell
+ "OpenBSD", // Open source
+ "OS2", // OS/2 IBM
+ "QNX", // procnto QNX
+ "Solaris", // Sun (almost an alias of SunOS)
+ "SunOS", // Sun Microsystems
+ "VxWorks", // WindRiver Systems
+ "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE",
+ "Windows2000", // Win2000
+ "Windows2003", // Win2003
+ "WindowsXP", "WindowsVista", };
+
+ public final static String PROCESSORNAMES[] = { "68k", // Motorola
+ // 68000
+ "ARM_LE", // Intel Strong ARM. Deprecated because it does not
+ // specify the endianness. See the following two rows.
+ "arm_le", // Intel Strong ARM Little Endian mode
+ "arm_be", // Intel String ARM Big Endian mode
+ "Alpha", //
+ "ia64n",// Hewlett Packard 64 bit
+ "ia64w",// Hewlett Packard 32 bit mode
+ "Ignite", // psc1k PTSC
+ "Mips", // SGI
+ "PArisc", // Hewlett Packard
+ "PowerPC", // power ppc Motorola/IBM Power PC
+ "Sh4", // Hitachi
+ "Sparc", // SUN
+ "S390", // IBM Mainframe 31 bit
+ "S390x", // IBM Mainframe 64-bit
+ "V850E", // NEC V850E
+ "x86", // pentium i386
+ "i486", // i586 i686 Intel& AMD 32 bit
+ "x86-64", };
+
+ Properties properties;
+
+ public Verifier(Jar jar) throws Exception {
+ this(jar, null);
+ }
+
+ public Verifier(Jar jar, Properties properties) throws Exception {
+ this.dot = jar;
+ this.properties = properties;
+ this.manifest = jar.getManifest();
+ if (manifest == null) {
+ manifest = new Manifest();
+ error("This file contains no manifest and is therefore not a bundle");
+ }
+ main = this.manifest.getMainAttributes();
+ verifyHeaders(main);
+ r3 = getHeader(Analyzer.BUNDLE_MANIFESTVERSION) == null;
+ usesRequire = getHeader(Analyzer.REQUIRE_BUNDLE) != null;
+ fragment = getHeader(Analyzer.FRAGMENT_HOST) != null;
+
+ bundleClasspath = getBundleClassPath();
+ mimports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.IMPORT_PACKAGE));
+ mdynimports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.DYNAMICIMPORT_PACKAGE));
+ mexports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.EXPORT_PACKAGE));
+
+ ignore = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.IGNORE_PACKAGE));
+ }
+
+ public Verifier() {
+ // TODO Auto-generated constructor stub
+ }
+
+ private void verifyHeaders(Attributes main) {
+ for (Object element : main.keySet()) {
+ Attributes.Name header = (Attributes.Name) element;
+ String h = header.toString();
+ if (!HEADER_PATTERN.matcher(h).matches())
+ error("Invalid Manifest header: " + h + ", pattern="
+ + HEADER_PATTERN);
+ }
+ }
+
+ private List<Jar> getBundleClassPath() {
+ List<Jar> list = newList();
+ String bcp = getHeader(Analyzer.BUNDLE_CLASSPATH);
+ if (bcp == null) {
+ list.add(dot);
+ } else {
+ Map<String, Map<String, String>> entries = parseHeader(bcp);
+ for (String jarOrDir : entries.keySet()) {
+ if (jarOrDir.equals(".")) {
+ list.add(dot);
+ } else {
+ if (jarOrDir.equals("/"))
+ jarOrDir = "";
+ if (jarOrDir.endsWith("/")) {
+ error("Bundle-Classpath directory must not end with a slash: "
+ + jarOrDir);
+ jarOrDir = jarOrDir.substring(0, jarOrDir.length() - 1);
+ }
+
+ Resource resource = dot.getResource(jarOrDir);
+ if (resource != null) {
+ try {
+ Jar sub = new Jar(jarOrDir);
+ addClose(sub);
+ EmbeddedResource.build(sub, resource);
+ if (!jarOrDir.endsWith(".jar"))
+ warning("Valid JAR file on Bundle-Classpath does not have .jar extension: "
+ + jarOrDir);
+ list.add(sub);
+ } catch (Exception e) {
+ error("Invalid embedded JAR file on Bundle-Classpath: "
+ + jarOrDir + ", " + e);
+ }
+ } else if (dot.getDirectories().containsKey(jarOrDir)) {
+ if (r3)
+ error("R3 bundles do not support directories on the Bundle-ClassPath: "
+ + jarOrDir);
+
+ try {
+ Jar sub = new Jar(jarOrDir);
+ addClose(sub);
+ for (Map.Entry<String, Resource> entry : dot
+ .getResources().entrySet()) {
+ if (entry.getKey().startsWith(jarOrDir))
+ sub.putResource(entry.getKey().substring(
+ jarOrDir.length() + 1), entry
+ .getValue());
+ }
+ list.add(sub);
+ } catch (Exception e) {
+ error("Invalid embedded directory file on Bundle-Classpath: "
+ + jarOrDir + ", " + e);
+ }
+ } else {
+ error("Cannot find a file or directory for Bundle-Classpath entry: "
+ + jarOrDir);
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ /*
+ * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
+ * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+ * optional ::= ’*’
+ */
+ public void verifyNative() {
+ String nc = getHeader("Bundle-NativeCode");
+ doNative(nc);
+ }
+
+ public void doNative(String nc) {
+ if (nc != null) {
+ QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
+ char del;
+ do {
+ do {
+ String name = qt.nextToken();
+ if (name == null) {
+ error("Can not parse name from bundle native code header: "
+ + nc);
+ return;
+ }
+ del = qt.getSeparator();
+ if (del == ';') {
+ if (dot != null && !dot.exists(name)) {
+ error("Native library not found in JAR: " + name);
+ }
+ } else {
+ String value = null;
+ if (del == '=')
+ value = qt.nextToken();
+
+ String key = name.toLowerCase();
+ if (key.equals("osname")) {
+ // ...
+ } else if (key.equals("osversion")) {
+ // verify version range
+ verify(value, VERSIONRANGE);
+ } else if (key.equals("language")) {
+ verify(value, ISO639);
+ } else if (key.equals("processor")) {
+ // verify(value, PROCESSORS);
+ } else if (key.equals("selection-filter")) {
+ // verify syntax filter
+ verifyFilter(value);
+ } else if (name.equals("*") && value == null) {
+ // Wildcard must be at end.
+ if (qt.nextToken() != null)
+ error("Bundle-Native code header may only END in wildcard: nc");
+ } else {
+ warning("Unknown attribute in native code: " + name
+ + "=" + value);
+ }
+ del = qt.getSeparator();
+ }
+ } while (del == ';');
+ } while (del == ',');
+ }
+ }
+
+ public void verifyFilter(String value) {
+ try {
+ verifyFilter(value, 0);
+ } catch (Exception e) {
+ error("Not a valid filter: " + value + e.getMessage());
+ }
+ }
+
+ private void verifyActivator() {
+ String bactivator = getHeader("Bundle-Activator");
+ if (bactivator != null) {
+ Clazz cl = loadClass(bactivator);
+ if (cl == null) {
+ int n = bactivator.lastIndexOf('.');
+ if (n > 0) {
+ String pack = bactivator.substring(0, n);
+ if (mimports.containsKey(pack))
+ return;
+ error("Bundle-Activator not found on the bundle class path nor in imports: "
+ + bactivator);
+ } else
+ error("Activator uses default package and is not local (default package can not be imported): "
+ + bactivator);
+ }
+ }
+ }
+
+ private Clazz loadClass(String className) {
+ String path = className.replace('.', '/') + ".class";
+ return (Clazz) classSpace.get(path);
+ }
+
+ private void verifyComponent() {
+ String serviceComponent = getHeader("Service-Component");
+ if (serviceComponent != null) {
+ Map<String, Map<String, String>> map = parseHeader(serviceComponent);
+ for (String component : map.keySet()) {
+ if (component.indexOf("*")<0 && !dot.exists(component)) {
+ error("Service-Component entry can not be located in JAR: "
+ + component);
+ } else {
+ // validate component ...
+ }
+ }
+ }
+ }
+
+ public void info() {
+ System.out.println("Refers : " + referred);
+ System.out.println("Contains : " + contained);
+ System.out.println("Manifest Imports : " + mimports);
+ System.out.println("Manifest Exports : " + mexports);
+ }
+
+ /**
+ * Invalid exports are exports mentioned in the manifest but not found on
+ * the classpath. This can be calculated with: exports - contains.
+ *
+ * Unfortunately, we also must take duplicate names into account. These
+ * duplicates are of course no erroneous.
+ */
+ private void verifyInvalidExports() {
+ Set<String> invalidExport = newSet(mexports.keySet());
+ invalidExport.removeAll(contained.keySet());
+
+ // We might have duplicate names that are marked for it. These
+ // should not be counted. Should we test them against the contained
+ // set? Hmm. If someone wants to hang himself by using duplicates than
+ // I guess he can go ahead ... This is not a recommended practice
+ for (Iterator<String> i = invalidExport.iterator(); i.hasNext();) {
+ String pack = i.next();
+ if (isDuplicate(pack))
+ i.remove();
+ }
+
+ if (!invalidExport.isEmpty())
+ error("Exporting packages that are not on the Bundle-Classpath"
+ + bundleClasspath + ": " + invalidExport);
+ }
+
+ /**
+ * Invalid imports are imports that we never refer to. They can be
+ * calculated by removing the refered packages from the imported packages.
+ * This leaves packages that the manifest imported but that we never use.
+ */
+ private void verifyInvalidImports() {
+ Set<String> invalidImport = newSet(mimports.keySet());
+ invalidImport.removeAll(referred.keySet());
+ // TODO Added this line but not sure why it worked before ...
+ invalidImport.removeAll(contained.keySet());
+ String bactivator = getHeader(Analyzer.BUNDLE_ACTIVATOR);
+ if (bactivator != null) {
+ int n = bactivator.lastIndexOf('.');
+ if (n > 0) {
+ invalidImport.remove(bactivator.substring(0, n));
+ }
+ }
+ if (isPedantic() && !invalidImport.isEmpty())
+ warning("Importing packages that are never refered to by any class on the Bundle-Classpath"
+ + bundleClasspath + ": " + invalidImport);
+ }
+
+ /**
+ * Check for unresolved imports. These are referals that are not imported by
+ * the manifest and that are not part of our bundle classpath. The are
+ * calculated by removing all the imported packages and contained from the
+ * refered packages.
+ */
+ private void verifyUnresolvedReferences() {
+ Set<String> unresolvedReferences = new TreeSet<String>(referred
+ .keySet());
+ unresolvedReferences.removeAll(mimports.keySet());
+ unresolvedReferences.removeAll(contained.keySet());
+
+ // Remove any java.** packages.
+ for (Iterator<String> p = unresolvedReferences.iterator(); p.hasNext();) {
+ String pack = p.next();
+ if (pack.startsWith("java.") || ignore.containsKey(pack))
+ p.remove();
+ else {
+ // Remove any dynamic imports
+ if (isDynamicImport(pack))
+ p.remove();
+ }
+ }
+
+ if (!unresolvedReferences.isEmpty()) {
+ // Now we want to know the
+ // classes that are the culprits
+ Set<String> culprits = new HashSet<String>();
+ for (Clazz clazz : classSpace.values()) {
+ if (hasOverlap(unresolvedReferences, clazz.imports.keySet()))
+ culprits.add(clazz.getPath());
+ }
+
+ error("Unresolved references to " + unresolvedReferences
+ + " by class(es) on the Bundle-Classpath" + bundleClasspath
+ + ": " + culprits);
+ }
+ }
+
+ /**
+ * @param p
+ * @param pack
+ */
+ private boolean isDynamicImport(String pack) {
+ for (String pattern : mdynimports.keySet()) {
+ // Wildcard?
+ if (pattern.equals("*"))
+ return true; // All packages can be dynamically imported
+
+ if (pattern.endsWith(".*")) {
+ pattern = pattern.substring(0, pattern.length() - 2);
+ if (pack.startsWith(pattern)
+ && (pack.length() == pattern.length() || pack
+ .charAt(pattern.length()) == '.'))
+ return true;
+ } else {
+ if (pack.equals(pattern))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasOverlap(Set<?> a, Set<?> b) {
+ for (Iterator<?> i = a.iterator(); i.hasNext();) {
+ if (b.contains(i.next()))
+ return true;
+ }
+ return false;
+ }
+
+ public void verify() throws IOException {
+ if (classSpace == null)
+ classSpace = analyzeBundleClasspath(dot,
+ parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH)),
+ contained, referred, uses);
+ verifyManifestFirst();
+ verifyActivator();
+ verifyComponent();
+ verifyNative();
+ verifyInvalidExports();
+ verifyInvalidImports();
+ verifyUnresolvedReferences();
+ verifySymbolicName();
+ verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
+ verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
+ verifyHeader("Bundle-Version", VERSION, true);
+ verifyListHeader("Bundle-Classpath", FILE, false);
+ verifyDynamicImportPackage();
+ verifyBundleClasspath();
+ if (usesRequire) {
+ if (!getErrors().isEmpty()) {
+ getWarnings()
+ .add(
+ 0,
+ "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
+ }
+ }
+ }
+
+ public void verifyBundleClasspath() {
+ Map<String, Map<String, String>> bcp = parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH));
+ if (bcp.isEmpty() || bcp.containsKey("."))
+ return;
+
+ for (String path : dot.getResources().keySet()) {
+ if (path.endsWith(".class")) {
+ warning("The Bundle-Classpath does not contain the actual bundle JAR (as specified with '.' in the Bundle-Classpath) but the JAR does contain classes. Is this intentional?");
+ return;
+ }
+ }
+ }
+
+ /**
+ * <pre>
+ * DynamicImport-Package ::= dynamic-description
+ * ( ',' dynamic-description )*
+ *
+ * dynamic-description::= wildcard-names ( ';' parameter )*
+ * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
+ * wildcard-name ::= package-name
+ * | ( package-name '.*' ) // See 1.4.2
+ * | '*'
+ * </pre>
+ */
+ private void verifyDynamicImportPackage() {
+ verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
+ String dynamicImportPackage = getHeader("DynamicImport-Package");
+ if (dynamicImportPackage == null)
+ return;
+
+ Map<String, Map<String, String>> map = parseHeader(dynamicImportPackage);
+ for (String name : map.keySet()) {
+ name = name.trim();
+ if (!verify(name, WILDCARDPACKAGE))
+ error("DynamicImport-Package header contains an invalid package name: "
+ + name);
+
+ Map<String, String> sub = map.get(name);
+ if (r3 && sub.size() != 0) {
+ error("DynamicPackage-Import has attributes on import: "
+ + name
+ + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
+ }
+ }
+ }
+
+ private void verifyManifestFirst() {
+ if (!dot.manifestFirst) {
+ error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
+ }
+ }
+
+ private void verifySymbolicName() {
+ Map<String, Map<String, String>> bsn = parseHeader(getHeader(Analyzer.BUNDLE_SYMBOLICNAME));
+ if (!bsn.isEmpty()) {
+ if (bsn.size() > 1)
+ error("More than one BSN specified " + bsn);
+
+ String name = (String) bsn.keySet().iterator().next();
+ if (!SYMBOLICNAME.matcher(name).matches()) {
+ error("Symbolic Name has invalid format: " + name);
+ }
+ }
+ }
+
+ /**
+ * <pre>
+ * filter ::= ’(’ filter-comp ’)’
+ * filter-comp ::= and | or | not | operation
+ * and ::= ’&’ filter-list
+ * or ::= ’|’ filter-list
+ * not ::= ’!’ filter
+ * filter-list ::= filter | filter filter-list
+ * operation ::= simple | present | substring
+ * simple ::= attr filter-type value
+ * filter-type ::= equal | approx | greater | less
+ * equal ::= ’=’
+ * approx ::= ’˜=’
+ * greater ::= ’>=’
+ * less ::= ’<=’
+ * present ::= attr ’=*’
+ * substring ::= attr ’=’ initial any final
+ * inital ::= () | value
+ * any ::= ’*’ star-value
+ * star-value ::= () | value ’*’ star-value
+ * final ::= () | value
+ * value ::= <see text>
+ * </pre>
+ *
+ * @param expr
+ * @param index
+ * @return
+ */
+
+ int verifyFilter(String expr, int index) {
+ try {
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ if (expr.charAt(index) != '(')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ( at position " + index
+ + " : " + expr);
+
+ index++;
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ switch (expr.charAt(index)) {
+ case '!':
+ case '&':
+ case '|':
+ return verifyFilterSubExpression(expr, index) + 1;
+
+ default:
+ return verifyFilterOperation(expr, index) + 1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException(
+ "Filter mismatch: early EOF from " + index);
+ }
+ }
+
+ private int verifyFilterOperation(String expr, int index) {
+ StringBuffer sb = new StringBuffer();
+ while ("=><~()".indexOf(expr.charAt(index)) < 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String attr = sb.toString().trim();
+ if (attr.length() == 0)
+ throw new IllegalArgumentException(
+ "Filter mismatch: attr at index " + index + " is 0");
+ sb = new StringBuffer();
+ while ("=><~".indexOf(expr.charAt(index)) >= 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String operator = sb.toString();
+ if (!verify(operator, FILTEROP))
+ throw new IllegalArgumentException(
+ "Filter error, illegal operator " + operator + " at index "
+ + index);
+
+ sb = new StringBuffer();
+ while (")".indexOf(expr.charAt(index)) < 0) {
+ switch (expr.charAt(index)) {
+ case '\\':
+ if (expr.charAt(index + 1) == '*'
+ || expr.charAt(index + 1) == ')')
+ index++;
+ else
+ throw new IllegalArgumentException(
+ "Filter error, illegal use of backslash at index "
+ + index
+ + ". Backslash may only be used before * or (");
+ }
+ sb.append(expr.charAt(index++));
+ }
+ return index;
+ }
+
+ private int verifyFilterSubExpression(String expr, int index) {
+ do {
+ index = verifyFilter(expr, index + 1);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ) at position " + index
+ + " : " + expr);
+ index++;
+ } while (expr.charAt(index) == '(');
+ return index;
+ }
+
+ private String getHeader(String string) {
+ return main.getValue(string);
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean verifyHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
+ for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
+ if (!verify((String) i.next(), regex)) {
+ String msg = "Invalid value for " + name + ", " + value
+ + " does not match " + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ private boolean verify(String value, Pattern regex) {
+ return regex.matcher(value).matches();
+ }
+
+ private boolean verifyListHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ Map<String, Map<String, String>> map = parseHeader(value);
+ for (String header : map.keySet()) {
+ if (!regex.matcher(header).matches()) {
+ String msg = "Invalid value for " + name + ", " + value
+ + " does not match " + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ public String getProperty(String key, String deflt) {
+ if (properties == null)
+ return deflt;
+ return properties.getProperty(key, deflt);
+ }
+
+ public void setClassSpace(Map<String, Clazz> classspace,
+ Map<String, Map<String, String>> contained,
+ Map<String, Map<String, String>> referred,
+ Map<String, Set<String>> uses) {
+ this.classSpace = classspace;
+ this.contained = contained;
+ this.referred = referred;
+ this.uses = uses;
+ }
+
+ public static boolean isVersion(String version) {
+ return VERSION.matcher(version).matches();
+ }
+
+ public static boolean isIdentifier(String value) {
+ if ( value.length() < 1 )
+ return false;
+
+ if ( !Character.isJavaIdentifierStart(value.charAt(0)))
+ return false;
+
+ for ( int i = 1; i<value.length(); i++ ) {
+ if ( !Character.isJavaIdentifierPart(value.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isMember(String value, String[] matches) {
+ for ( String match : matches ) {
+ if ( match.equals(value) )
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java b/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
new file mode 100644
index 0000000..890dbdc
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
@@ -0,0 +1,82 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+public class ZipResource implements Resource {
+ ZipFile zip;
+ ZipEntry entry;
+ long lastModified;
+ String extra;
+
+ ZipResource(ZipFile zip, ZipEntry entry, long lastModified) {
+ this.zip = zip;
+ this.entry = entry;
+ this.lastModified = lastModified;
+ byte[] data = entry.getExtra();
+ if (data != null)
+ this.extra = new String(data);
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return zip.getInputStream(entry);
+ }
+
+ public String toString() {
+ return ":" + entry.getName() + ":";
+ }
+
+ public static ZipFile build(Jar jar, File file) throws ZipException,
+ IOException {
+ return build(jar, file, null);
+ }
+
+ public static ZipFile build(Jar jar, File file, Pattern pattern)
+ throws ZipException, IOException {
+
+ try {
+ ZipFile zip = new ZipFile(file);
+ nextEntry: for (Enumeration<? extends ZipEntry> e = zip.entries(); e
+ .hasMoreElements();) {
+ ZipEntry entry = e.nextElement();
+ if (pattern != null) {
+ Matcher m = pattern.matcher(entry.getName());
+ if (!m.matches())
+ continue nextEntry;
+ }
+ if (!entry.isDirectory()) {
+ long time = entry.getTime();
+ if (time <= 0)
+ time = file.lastModified();
+ jar.putResource(entry.getName(), new ZipResource(zip,
+ entry, time), true);
+ }
+ }
+ return zip;
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("Problem opening JAR: "
+ + file.getAbsolutePath());
+ }
+ }
+
+ public void write(OutputStream out) throws IOException {
+ FileResource.copy(this, out);
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java b/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
new file mode 100644
index 0000000..7aa947f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
@@ -0,0 +1,250 @@
+/* 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.eclipse;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.parsers.*;
+
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import aQute.libg.reporter.*;
+
+/**
+ * Parse the Eclipse project information for the classpath. Unfortunately, it is
+ * impossible to read the variables. They are ignored but that can cause
+ * problems.
+ *
+ * @version $Revision: 1.1 $
+ */
+public class EclipseClasspath {
+ static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ DocumentBuilder db;
+ File project;
+ File workspace;
+ Set<File> sources = new LinkedHashSet<File>();
+ Set<File> allSources = new LinkedHashSet<File>();
+
+ Set<File> classpath = new LinkedHashSet<File>();
+ List<File> dependents = new ArrayList<File>();
+ File output;
+ boolean recurse = true;
+ Set<File> exports = new LinkedHashSet<File>();
+ Map<String, String> properties = new HashMap<String, String>();
+ Reporter reporter;
+ int options;
+ Set<File> bootclasspath = new LinkedHashSet<File>();
+
+ public final static int DO_VARIABLES = 1;
+
+ /**
+ * Parse an Eclipse project structure to discover the classpath.
+ *
+ * @param workspace
+ * Points to workspace
+ * @param project
+ * Points to project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project,
+ int options) throws Exception {
+ this.project = project.getCanonicalFile();
+ this.workspace = workspace.getCanonicalFile();
+ this.reporter = reporter;
+ db = documentBuilderFactory.newDocumentBuilder();
+ parse(this.project, true);
+ db = null;
+ }
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project)
+ throws Exception {
+ this(reporter, workspace, project, 0);
+ }
+
+ /**
+ * Recursive routine to parse the files. If a sub project is detected, it is
+ * parsed before the parsing continues. This should give the right order.
+ *
+ * @param project
+ * Project directory
+ * @param top
+ * If this is the top project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+ void parse(File project, boolean top) throws ParserConfigurationException,
+ SAXException, IOException {
+ File file = new File(project, ".classpath");
+ if (!file.exists())
+ throw new FileNotFoundException(".classpath file not found: "
+ + file.getAbsolutePath());
+
+ Document doc = db.parse(file);
+ NodeList nodelist = doc.getDocumentElement().getElementsByTagName(
+ "classpathentry");
+
+ if (nodelist == null)
+ throw new IllegalArgumentException(
+ "Can not find classpathentry in classpath file");
+
+ for (int i = 0; i < nodelist.getLength(); i++) {
+ Node node = nodelist.item(i);
+ NamedNodeMap attrs = node.getAttributes();
+ String kind = get(attrs, "kind");
+ if ("src".equals(kind)) {
+ String path = get(attrs, "path");
+ // TODO boolean exported = "true".equalsIgnoreCase(get(attrs,
+ // "exported"));
+ if (path.startsWith("/")) {
+ // We have another project
+ File subProject = getFile(workspace, project, path);
+ if (recurse)
+ parse(subProject, false);
+ dependents.add(subProject.getCanonicalFile());
+ } else {
+ File src = getFile(workspace, project, path);
+ allSources.add(src);
+ if (top) {
+ // We only want the sources for our own project
+ // or we'll compile all at once. Not a good idea
+ // because project settings can differ.
+ sources.add(src);
+ }
+ }
+ } else if ("lib".equals(kind)) {
+ String path = get(attrs, "path");
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ if (top || exported) {
+ File jar = getFile(workspace, project, path);
+ if (jar.getName().startsWith("ee."))
+ bootclasspath.add(jar);
+ else
+ classpath.add(jar);
+ if (exported)
+ exports.add(jar);
+ }
+ } else if ("output".equals(kind)) {
+ String path = get(attrs, "path");
+ path = path.replace('/', File.separatorChar);
+ output = getFile(workspace, project, path);
+ classpath.add(output);
+ exports.add(output);
+ } else if ("var".equals(kind)) {
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ File lib = replaceVar(get(attrs, "path"));
+ File slib = replaceVar(get(attrs, "sourcepath"));
+ if (lib != null) {
+ classpath.add(lib);
+ if (exported)
+ exports.add(lib);
+ }
+ if (slib != null)
+ sources.add(slib);
+ } else if ("con".equals(kind)) {
+ // Should do something useful ...
+ }
+ }
+ }
+
+ private File getFile(File abs, File relative, String opath) {
+ String path = opath.replace('/', File.separatorChar);
+ File result = new File(path);
+ if (result.isAbsolute() && result.isFile()) {
+ return result;
+ }
+ if (path.startsWith(File.separator)) {
+ result = abs;
+ path = path.substring(1);
+ } else
+ result = relative;
+
+ StringTokenizer st = new StringTokenizer(path, File.separator);
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ result = new File(result, token);
+ }
+
+ if (!result.exists())
+ System.err.println("File not found: project=" + project
+ + " workspace=" + workspace + " path=" + opath + " file="
+ + result);
+ return result;
+ }
+
+ static Pattern PATH = Pattern.compile("([A-Z_]+)/(.*)");
+
+ private File replaceVar(String path) {
+ if ((options & DO_VARIABLES) == 0)
+ return null;
+
+ Matcher m = PATH.matcher(path);
+ if (m.matches()) {
+ String var = m.group(1);
+ String remainder = m.group(2);
+ String base = (String) properties.get(var);
+ if (base != null) {
+ File b = new File(base);
+ File f = new File(b, remainder.replace('/', File.separatorChar));
+ return f;
+ } else
+ reporter.error("Can't find replacement variable for: " + path);
+ } else
+ reporter.error("Cant split variable path: " + path);
+ return null;
+ }
+
+ private String get(NamedNodeMap map, String name) {
+ Node node = map.getNamedItem(name);
+ if (node == null)
+ return null;
+
+ return node.getNodeValue();
+ }
+
+ public Set<File> getClasspath() {
+ return classpath;
+ }
+
+ public Set<File> getSourcepath() {
+ return sources;
+ }
+
+ public File getOutput() {
+ return output;
+ }
+
+ public List<File> getDependents() {
+ return dependents;
+ }
+
+ public void setRecurse(boolean recurse) {
+ this.recurse = recurse;
+ }
+
+ public Set<File> getExports() {
+ return exports;
+ }
+
+ public void setProperties(Map<String, String> map) {
+ this.properties = map;
+ }
+
+ public Set<File> getBootclasspath() {
+ return bootclasspath;
+ }
+
+ public Set<File> getAllSources() {
+ return allSources;
+ }
+
+}