Update to latest refactored bndlib

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1362033 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/About.java b/bundleplugin/src/main/java/aQute/bnd/osgi/About.java
new file mode 100755
index 0000000..f2b7240
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/About.java
@@ -0,0 +1,39 @@
+package aQute.bnd.osgi;
+
+import aQute.bnd.header.*;
+
+/**
+ * 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. Headers are translated to {@link Parameters} that
+ * contains all headers (the order is maintained). The attribute of each header
+ * are maintained in an {@link Attrs}. Each additional file in a header
+ * definition will have its own entry (only native code does not work this way).
+ * The ':' of directives is considered part of the name. This allows attributes
+ * and directives to be maintained in the Attributes map. An important aspect of
+ * the specification is to allow the use of wildcards. Wildcards select from a
+ * set and can decorate the entries with new attributes. This functionality is
+ * implemented in Instructions. Much of the information calculated is in
+ * packages. A package is identified by a PackageRef (and a type by a TypeRef).
+ * The namespace is maintained by {@link Descriptors}, which here is owned by
+ * {@link Analyzer}. A special class, {@link Packages} maintains the attributes
+ * that are found in the code.
+ * 
+ * @version $Revision$
+ */
+public class About {
+	// Empty
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/AbstractResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/AbstractResource.java
new file mode 100644
index 0000000..e023985
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/AbstractResource.java
@@ -0,0 +1,56 @@
+package aQute.bnd.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;
+
+	public long size() throws IOException {
+		return getLocalBytes().length;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java
new file mode 100755
index 0000000..1a5c61b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Analyzer.java
@@ -0,0 +1,2484 @@
+package aQute.bnd.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 static aQute.libg.generics.Create.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.jar.Attributes.Name;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.*;
+import aQute.bnd.header.*;
+import aQute.bnd.osgi.Descriptors.Descriptor;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.bnd.service.*;
+import aQute.lib.base64.*;
+import aQute.lib.collections.*;
+import aQute.lib.filter.*;
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.generics.*;
+import aQute.libg.reporter.*;
+
+public class Analyzer extends Processor {
+	private final SortedSet<Clazz.JAVA>				ees						= new TreeSet<Clazz.JAVA>();
+	static Properties								bndInfo;
+
+	// Bundle parameters
+	private Jar										dot;
+	private final Packages							contained				= new Packages();
+	private final Packages							referred				= new Packages();
+	private Packages								exports;
+	private Packages								imports;
+	private TypeRef									activator;
+
+	// Global parameters
+	private final MultiMap<PackageRef,PackageRef>	uses					= new MultiMap<PackageRef,PackageRef>(
+			PackageRef.class, PackageRef.class,
+			true);
+	private final MultiMap<PackageRef,PackageRef>	apiUses					= new MultiMap<PackageRef,PackageRef>(
+			PackageRef.class, PackageRef.class,
+			true);
+	private final Packages							classpathExports		= new Packages();
+	private final Descriptors						descriptors				= new Descriptors();
+	private final List<Jar>							classpath				= list();
+	private final Map<TypeRef,Clazz>				classspace				= map();
+	private final Map<TypeRef,Clazz>				importedClassesCache	= map();
+	private boolean									analyzed				= false;
+	private boolean									diagnostics				= false;
+	private boolean									inited					= false;
+	final protected AnalyzerMessages				msgs					= ReporterMessages.base(this,
+																					AnalyzerMessages.class);
+
+	public Analyzer(Processor parent) {
+		super(parent);
+	}
+
+	public Analyzer() {}
+
+	/**
+	 * Specifically for Maven
+	 * 
+	 * @param properties
+	 *            the properties
+	 */
+
+	public static Properties getManifest(File dirOrJar) throws Exception {
+		Analyzer analyzer = new Analyzer();
+		try {
+			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;
+		}
+		finally {
+			analyzer.close();
+		}
+	}
+
+	/**
+	 * Calculates the data structures for generating a manifest.
+	 * 
+	 * @throws IOException
+	 */
+	public void analyze() throws Exception {
+		if (!analyzed) {
+			analyzed = true;
+			uses.clear();
+			apiUses.clear();
+			classspace.clear();
+			classpathExports.clear();
+
+			// Parse all the class in the
+			// the jar according to the OSGi bcp
+			analyzeBundleClasspath();
+
+			//
+			// calculate class versions in use
+			//
+			for (Clazz c : classspace.values()) {
+				ees.add(c.getFormat());
+			}
+
+			//
+			// Get exported packages from the
+			// entries on the classpath
+			//
+
+			for (Jar current : getClasspath()) {
+				getExternalExports(current, classpathExports);
+				for (String dir : current.getDirectories().keySet()) {
+					PackageRef packageRef = getPackageRef(dir);
+					Resource resource = current.getResource(dir + "/packageinfo");
+					getExportVersionsFromPackageInfo(packageRef, resource, classpathExports);
+				}
+			}
+
+			// Handle the bundle activator
+
+			String s = getProperty(BUNDLE_ACTIVATOR);
+			if (s != null) {
+				activator = getTypeRefFromFQN(s);
+				referTo(activator);
+				trace("activator %s %s", s, activator);
+			}
+
+			// Execute any plugins
+			// TODO handle better reanalyze
+			doPlugins();
+
+			Jar extra = getExtra();
+			while (extra != null) {
+				dot.addAll(extra);
+				analyzeJar(extra, "", true);
+				extra = getExtra();
+			}
+
+			referred.keySet().removeAll(contained.keySet());
+
+			//
+			// EXPORTS
+			//
+			{
+				Set<Instruction> unused = Create.set();
+
+				Instructions filter = new Instructions(getExportPackage());
+				filter.append(getExportContents());
+
+				exports = filter(filter, contained, unused);
+
+				if (!unused.isEmpty()) {
+					warning("Unused Export-Package instructions: %s ", unused);
+				}
+
+				// See what information we can find to augment the
+				// exports. I.e. look on the classpath
+				augmentExports(exports);
+			}
+
+			//
+			// IMPORTS
+			// Imports MUST come after exports because we use information from
+			// the exports
+			//
+			{
+				// Add all exports that do not have an -noimport: directive
+				// to the imports.
+				Packages referredAndExported = new Packages(referred);
+				referredAndExported.putAll(doExportsToImports(exports));
+
+				removeDynamicImports(referredAndExported);
+
+				// Remove any Java references ... where are the closures???
+				for (Iterator<PackageRef> i = referredAndExported.keySet().iterator(); i.hasNext();) {
+					if (i.next().isJava())
+						i.remove();
+				}
+
+				Set<Instruction> unused = Create.set();
+				String h = getProperty(IMPORT_PACKAGE);
+				if (h == null) // If not set use a default
+					h = "*";
+
+				if (isPedantic() && h.trim().length() == 0)
+					warning("Empty Import-Package header");
+
+				Instructions filter = new Instructions(h);
+				imports = filter(filter, referredAndExported, unused);
+				if (!unused.isEmpty()) {
+					// We ignore the end wildcard catch
+					if (!(unused.size() == 1 && unused.iterator().next().toString().equals("*")))
+						warning("Unused Import-Package instructions: %s ", unused);
+				}
+
+				// See what information we can find to augment the
+				// imports. I.e. look in the exports
+				augmentImports(imports, exports);
+			}
+
+			//
+			// USES
+			//
+			// Add the uses clause to the exports
+			
+			boolean api = isTrue(getProperty(EXPERIMENTS)) || true; // brave, lets see
+			
+			doUses(exports, api ? apiUses : uses, imports);
+			
+			//
+			// Verify that no exported package has a reference to a private
+			// package
+			// This can cause a lot of harm.
+			// TODO restrict the check to public API only, but even then
+			// exported packages
+			// should preferably not refer to private packages.
+			//
+			Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
+			privatePackages.removeAll(exports.keySet());
+			privatePackages.removeAll(imports.keySet());
+
+			// References to java are not imported so they would show up as
+			// private
+			// packages, lets kill them as well.
+
+			for (Iterator<PackageRef> p = privatePackages.iterator(); p.hasNext();)
+				if (p.next().isJava())
+					p.remove();
+
+			for (PackageRef exported : exports.keySet()) {
+				List<PackageRef> used = uses.get(exported);
+				if (used != null) {
+					Set<PackageRef> privateReferences = new HashSet<PackageRef>(apiUses.get(exported));
+					privateReferences.retainAll(privatePackages);
+					if (!privateReferences.isEmpty())
+						msgs.Export_Has_PrivateReferences_(exported, privateReferences.size(), privateReferences);
+				}
+			}
+
+			//
+			// Checks
+			//
+			if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
+				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 "
+						+ uses.transpose().get(Descriptors.DEFAULT_PACKAGE));
+			}
+
+		}
+	}
+
+	/**
+	 * Discussed with BJ and decided to kill the .
+	 * 
+	 * @param referredAndExported
+	 */
+	void removeDynamicImports(Packages referredAndExported) {
+
+		// // Remove any matching a dynamic import package instruction
+		// Instructions dynamicImports = new
+		// Instructions(getDynamicImportPackage());
+		// Collection<PackageRef> dynamic = dynamicImports.select(
+		// referredAndExported.keySet(), false);
+		// referredAndExported.keySet().removeAll(dynamic);
+	}
+
+	protected Jar getExtra() throws Exception {
+		return null;
+	}
+
+	/**
+	 * 
+	 */
+	void doPlugins() {
+		for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
+			try {
+				boolean reanalyze = plugin.analyzeJar(this);
+				if (reanalyze) {
+					classspace.clear();
+					analyzeBundleClasspath();
+				}
+			}
+			catch (Exception e) {
+				error("Analyzer Plugin %s failed %s", plugin, e);
+			}
+		}
+	}
+
+	/**
+	 * @return
+	 */
+	boolean isResourceOnly() {
+		return isTrue(getProperty(RESOURCEONLY));
+	}
+
+	/**
+	 * 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 Exception {
+		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-" + getBndVersion());
+			main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+		}
+
+		String exportHeader = printClauses(exports, true);
+
+		if (exportHeader.length() > 0)
+			main.putValue(EXPORT_PACKAGE, exportHeader);
+		else
+			main.remove(EXPORT_PACKAGE);
+
+		// Remove all the Java packages from the imports
+		if (!imports.isEmpty()) {
+			main.putValue(IMPORT_PACKAGE, printClauses(imports));
+		} else {
+			main.remove(IMPORT_PACKAGE);
+		}
+
+		Packages temp = new Packages(contained);
+		temp.keySet().removeAll(exports.keySet());
+
+		if (!temp.isEmpty())
+			main.putValue(PRIVATE_PACKAGE, printClauses(temp));
+		else
+			main.remove(PRIVATE_PACKAGE);
+
+		Parameters bcp = getBundleClasspath();
+		if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
+			main.remove(BUNDLE_CLASSPATH);
+		else
+			main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
+
+		doNamesection(dot, manifest);
+
+		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 (isMissingPlugin(header.trim())) {
+				error("Missing plugin for command %s", header);
+			}
+			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_HEADER))
+						main.putValue(header, "");
+					else
+						main.putValue(header, value);
+				}
+			} else {
+				// TODO should we report?
+			}
+		}
+
+		// Copy old values into new manifest, when they
+		// exist in the old one, but not in the new one
+		merge(manifest, dot.getManifest());
+
+		//
+		// 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");
+
+		// Remove all the headers mentioned in -removeheaders
+		Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
+		Collection<Object> result = instructions.select(main.keySet(), false);
+		main.keySet().removeAll(result);
+
+		// We should not set the manifest here, this is in general done
+		// by the caller.
+		// dot.setManifest(manifest);
+		return manifest;
+	}
+
+	/**
+	 * Parse the namesection as instructions and then match them against the
+	 * current set of resources For example:
+	 * 
+	 * <pre>
+	 * 	-namesection: *;baz=true, abc/def/bar/X.class=3
+	 * </pre>
+	 * 
+	 * The raw value of {@link Constants#NAMESECTION} is used but the values of
+	 * the attributes are replaced where @ is set to the resource name. This
+	 * allows macro to operate on the resource
+	 */
+
+	private void doNamesection(Jar dot, Manifest manifest) {
+
+		Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
+		Instructions instructions = new Instructions(namesection);
+		Set<String> resources = new HashSet<String>(dot.getResources().keySet());
+
+		//
+		// For each instruction, iterator over the resources and filter
+		// them. If a resource matches, it must be removed even if the
+		// instruction is negative. If positive, add a name section
+		// to the manifest for the given resource name. Then add all
+		// attributes from the instruction to that name section.
+		//
+		for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
+			boolean matched = false;
+
+			// For each instruction
+
+			for (Iterator<String> i = resources.iterator(); i.hasNext();) {
+				String path = i.next();
+				// For each resource
+
+				if (instr.getKey().matches(path)) {
+
+					// Instruction matches the resource
+
+					matched = true;
+					if (!instr.getKey().isNegated()) {
+
+						// Positive match, add the attributes
+
+						Attributes attrs = manifest.getAttributes(path);
+						if (attrs == null) {
+							attrs = new Attributes();
+							manifest.getEntries().put(path, attrs);
+						}
+
+						//
+						// Add all the properties from the instruction to the
+						// name section
+						//
+
+						for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
+							setProperty("@", path);
+							try {
+								String processed = getReplacer().process(property.getValue());
+								attrs.putValue(property.getKey(), processed);
+							}
+							finally {
+								unsetProperty("@");
+							}
+						}
+					}
+					i.remove();
+				}
+			}
+
+			if (!matched && resources.size() > 0)
+				warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
+		}
+
+	}
+
+	/**
+	 * 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>
+	 * &#064;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();
+
+			String projectName = getBase().getName();
+			if (value == null || value.equals("bnd.bnd")) {
+				value = projectName;
+			} else if (value.endsWith(".bnd")) {
+				value = value.substring(0, value.length() - 4);
+				if (!value.startsWith(getBase().getName()))
+					value = projectName + "." + value;
+			}
+		}
+
+		if (value == null)
+			return "untitled";
+
+		int n = value.indexOf(';');
+		if (n > 0)
+			value = value.substring(0, n);
+		return value.trim();
+	}
+
+	public String _bsn(@SuppressWarnings("unused")
+	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 = "";
+		StringBuilder sb = new StringBuilder();
+		Map<String,Map<String,Resource>> map = bundle.getDirectories();
+		for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+			String directory = 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 Packages getContained() {
+		return contained;
+	}
+
+	public Packages getExports() {
+		return exports;
+	}
+
+	public Packages getImports() {
+		return imports;
+	}
+
+	public Jar getJar() {
+		return dot;
+	}
+
+	public Packages getReferred() {
+		return referred;
+	}
+
+	/**
+	 * Return the set of unreachable code depending on exports and the bundle
+	 * activator.
+	 * 
+	 * @return
+	 */
+	public Set<PackageRef> getUnreachable() {
+		Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
+		for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
+			PackageRef packageRef = r.next();
+			removeTransitive(packageRef, unreachable);
+		}
+		if (activator != null) {
+			removeTransitive(activator.getPackageRef(), unreachable);
+		}
+		return unreachable;
+	}
+
+	public MultiMap<PackageRef,PackageRef> getUses() {
+		return uses;
+	}
+
+	public MultiMap<PackageRef,PackageRef> getAPIUses() {
+		return apiUses;
+	}
+
+	/**
+	 * Get the version for this bnd
+	 * 
+	 * @return version or unknown.
+	 */
+	public String getBndVersion() {
+		return getBndInfo("version", "<unknown>");
+	}
+
+	public long getBndLastModified() {
+		String time = getBndInfo("lastmodified", "0");
+		try {
+			return Long.parseLong(time);
+		}
+		catch (Exception e) {
+			// Ignore
+		}
+		return 0;
+	}
+
+	public String getBndInfo(String key, String defaultValue) {
+		if (bndInfo == null) {
+			try {
+				Properties bndInfoLocal = new Properties();
+				URL url = Analyzer.class.getResource("bnd.info");
+				if (url != null) {
+					InputStream in = url.openStream();
+					try {
+						bndInfoLocal.load(in);
+					}
+					finally {
+						in.close();
+					}
+				}
+				bndInfo = bndInfoLocal;
+			}
+			catch (Exception e) {
+				e.printStackTrace();
+				return defaultValue;
+			}
+		}
+		String value = bndInfo.getProperty(key);
+		if (value == null)
+			return defaultValue;
+		return value;
+	}
+
+	/**
+	 * Merge the existing manifest with the instructions but do not override
+	 * existing properties.
+	 * 
+	 * @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, attributes.getValue(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: %s", 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) {
+		if (dot != null)
+			removeClose(dot);
+
+		this.dot = jar;
+		if (dot != null)
+			addClose(dot);
+
+		return jar;
+	}
+
+	protected void begin() {
+		if (inited == false) {
+			inited = true;
+			super.begin();
+
+			updateModified(getBndLastModified(), "bnd last modified");
+			verifyManifestHeadersCase(getProperties());
+
+		}
+	}
+
+	/**
+	 * 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.getSource() != null && entry.getSource().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 manifests
+	 * @throws Exception
+	 */
+	private void merge(Manifest result, Manifest old) {
+		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());
+				}
+			}
+		}
+	}
+
+	/**
+	 * 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. The following method is really tricky and evolved
+	 * over time. Coming from the original background of OSGi, it was a weird
+	 * idea for me to have a public package that should not be substitutable. I
+	 * was so much convinced that this was the right rule that I rücksichtlos
+	 * imported them all. Alas, the real world was more subtle than that. It
+	 * turns out that it is not a good idea to always import. First, there must
+	 * be a need to import, i.e. there must be a contained package that refers
+	 * to the exported package for it to make use importing that package.
+	 * Second, if an exported package refers to an internal package than it
+	 * should not be imported. Additionally, it is necessary to treat the
+	 * exports in groups. If an exported package refers to another exported
+	 * packages than it must be in the same group. A framework can only
+	 * substitute exports for imports for the whole of such a group. WHY?????
+	 * Not clear anymore ...
+	 */
+	Packages doExportsToImports(Packages exports) {
+
+		// private packages = contained - exported.
+		Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
+		privatePackages.removeAll(exports.keySet());
+
+		// private references = ∀ p : private packages | uses(p)
+		Set<PackageRef> privateReferences = newSet();
+		for (PackageRef p : privatePackages) {
+			Collection<PackageRef> uses = this.uses.get(p);
+			if (uses != null)
+				privateReferences.addAll(uses);
+		}
+
+		// Assume we are going to export all exported packages
+		Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
+
+		// Remove packages that are not referenced privately
+		toBeImported.retainAll(privateReferences);
+
+		// Not necessary to import anything that is already
+		// imported in the Import-Package statement.
+		// TODO toBeImported.removeAll(imports.keySet());
+
+		// Remove exported packages that are referring to
+		// private packages.
+		// Each exported package has a uses clause. We just use
+		// the used packages for each exported package to find out
+		// if it refers to an internal package.
+		//
+
+		for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
+			PackageRef next = i.next();
+			Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
+
+			for (PackageRef privatePackage : privatePackages) {
+				if (usedByExportedPackage.contains(privatePackage)) {
+					i.remove();
+					break;
+				}
+			}
+		}
+
+		// Clean up attributes and generate result map
+		Packages result = new Packages();
+		for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
+			PackageRef ep = i.next();
+			Attrs parameters = exports.get(ep);
+
+			String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
+			if (noimport != null && noimport.equalsIgnoreCase("true"))
+				continue;
+
+			// // we can't substitute when there is no version
+			// String version = parameters.get(VERSION_ATTRIBUTE);
+			// if (version == null) {
+			// if (isPedantic())
+			// warning(
+			// "Cannot automatically import exported package %s because it has no version defined",
+			// ep);
+			// continue;
+			// }
+
+			parameters = new Attrs();
+			parameters.remove(VERSION_ATTRIBUTE);
+			result.put(ep, parameters);
+		}
+		return result;
+	}
+
+	public boolean referred(PackageRef packageName) {
+		// return true;
+		for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
+			if (!contained.getKey().equals(packageName)) {
+				if (contained.getValue().contains(packageName))
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @param jar
+	 */
+	private void getExternalExports(Jar jar, Packages classpathExports) {
+		try {
+			Manifest m = jar.getManifest();
+			if (m != null) {
+				Domain domain = Domain.domain(m);
+				Parameters exported = domain.getExportPackage();
+				for (Entry<String,Attrs> e : exported.entrySet()) {
+					PackageRef ref = getPackageRef(e.getKey());
+					if (!classpathExports.containsKey(ref)) {
+						// TODO e.getValue().put(SOURCE_DIRECTIVE,
+						// jar.getBsn()+"-"+jar.getVersion());
+
+						classpathExports.put(ref, e.getValue());
+					}
+				}
+			}
+		}
+		catch (Exception e) {
+			warning("Erroneous Manifest for " + jar + " " + e);
+		}
+	}
+
+	/**
+	 * Find some more information about imports in manifest and other places. It
+	 * is assumed that the augmentsExports has already copied external attrs
+	 * from the classpathExports.
+	 * 
+	 * @throws Exception
+	 */
+	void augmentImports(Packages imports, Packages exports) throws Exception {
+		List<PackageRef> noimports = Create.list();
+		Set<PackageRef> provided = findProvidedPackages();
+
+		for (PackageRef packageRef : imports.keySet()) {
+			String packageName = packageRef.getFQN();
+
+			setProperty(CURRENT_PACKAGE, packageName);
+			try {
+				Attrs importAttributes = imports.get(packageRef);
+				Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
+
+				String exportVersion = exportAttributes.getVersion();
+				String importRange = importAttributes.getVersion();
+
+				if (exportVersion == null) {
+					// TODO Should check if the source is from a bundle.
+
+				} else {
+
+					//
+					// Version Policy - Import version substitution. We
+					// calculate the export version and then allow the
+					// import version attribute to use it in a substitution
+					// by using a ${@} macro. The export version can
+					// be defined externally or locally
+					//
+
+					boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
+							|| isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
+
+					exportVersion = cleanupVersion(exportVersion);
+
+					try {
+						setProperty("@", exportVersion);
+
+						if (importRange != null) {
+							importRange = cleanupVersion(importRange);
+							importRange = getReplacer().process(importRange);
+						} else
+							importRange = getVersionPolicy(provider);
+
+					}
+					finally {
+						unsetProperty("@");
+					}
+					importAttributes.put(VERSION_ATTRIBUTE, importRange);
+				}
+
+				//
+				// Check if exporter has mandatory attributes
+				//
+				String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
+				if (mandatory != null) {
+					String[] attrs = mandatory.split("\\s*,\\s*");
+					for (int i = 0; i < attrs.length; i++) {
+						if (!importAttributes.containsKey(attrs[i]))
+							importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
+					}
+				}
+
+				if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
+					importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
+
+				fixupAttributes(importAttributes);
+				removeAttributes(importAttributes);
+
+				String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
+				if (result == null)
+					noimports.add(packageRef);
+			}
+			finally {
+				unsetProperty(CURRENT_PACKAGE);
+			}
+		}
+
+		if (isPedantic() && noimports.size() != 0) {
+			warning("Imports that lack version ranges: %s", noimports);
+		}
+	}
+
+	/**
+	 * Find the packages we depend on, where we implement an interface that is a
+	 * Provider Type. These packages, when we import them, must use the provider
+	 * policy.
+	 * 
+	 * @throws Exception
+	 */
+	Set<PackageRef> findProvidedPackages() throws Exception {
+		Set<PackageRef> providers = Create.set();
+		Set<TypeRef> cached = Create.set();
+
+		for (Clazz c : classspace.values()) {
+			TypeRef[] interfaces = c.getInterfaces();
+			if (interfaces != null)
+				for (TypeRef t : interfaces)
+					if (cached.contains(t) || isProvider(t)) {
+						cached.add(t);
+						providers.add(t.getPackageRef());
+					}
+		}
+		return providers;
+	}
+
+	private boolean isProvider(TypeRef t) throws Exception {
+		Clazz c = findClass(t);
+		if (c == null)
+			return false;
+
+		if (c.annotations == null)
+			return false;
+
+		TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
+		boolean result = c.annotations.contains(pt);
+		return result;
+	}
+
+	/**
+	 * Provide any macro substitutions and versions for exported packages.
+	 */
+
+	void augmentExports(Packages exports) {
+		for (PackageRef packageRef : exports.keySet()) {
+			String packageName = packageRef.getFQN();
+			setProperty(CURRENT_PACKAGE, packageName);
+			try {
+				Attrs attributes = exports.get(packageRef);
+				Attrs exporterAttributes = classpathExports.get(packageRef);
+				if (exporterAttributes == null)
+					continue;
+
+				for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
+					String key = entry.getKey();
+					if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
+						key = VERSION_ATTRIBUTE;
+
+					// dont overwrite and no directives
+					if (!key.endsWith(":") && !attributes.containsKey(key)) {
+						attributes.put(key, entry.getValue());
+					}
+				}
+
+				fixupAttributes(attributes);
+				removeAttributes(attributes);
+
+			}
+			finally {
+				unsetProperty(CURRENT_PACKAGE);
+			}
+		}
+	}
+
+	/**
+	 * Fixup Attributes Execute any macros on an export and
+	 */
+
+	void fixupAttributes(Attrs attributes) {
+		// Convert any attribute values that have macros.
+		for (String key : attributes.keySet()) {
+			String value = attributes.get(key);
+			if (value.indexOf('$') >= 0) {
+				value = getReplacer().process(value);
+				attributes.put(key, value);
+			}
+		}
+
+	}
+
+	/**
+	 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. 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.
+	 */
+
+	void removeAttributes(Attrs attributes) {
+		String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
+
+		if (remove != null) {
+			Instructions removeInstr = new Instructions(remove);
+			attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
+		}
+
+		// Remove any ! valued attributes
+		for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
+			String v = i.next().getValue();
+			if (v.equals("!"))
+				i.remove();
+		}
+	}
+
+	/**
+	 * Calculate a version from a version policy.
+	 * 
+	 * @param version
+	 *            The actual exported version
+	 * @param impl
+	 *            true for implementations and false for clients
+	 */
+
+	String calculateVersionRange(String version, boolean impl) {
+		setProperty("@", version);
+		try {
+			return getVersionPolicy(impl);
+		}
+		finally {
+			unsetProperty("@");
+		}
+	}
+
+	/**
+	 * Add the uses clauses. This method iterates over the exports and cal
+	 * 
+	 * @param exports
+	 * @param uses
+	 * @throws MojoExecutionException
+	 */
+	void doUses(Packages exports, MultiMap<PackageRef,PackageRef> uses, Packages imports) {
+		if ("true".equalsIgnoreCase(getProperty(NOUSES)))
+			return;
+
+		for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
+			PackageRef packageRef = i.next();
+			String packageName = packageRef.getFQN();
+			setProperty(CURRENT_PACKAGE, packageName);
+			try {
+				doUses(packageRef, exports, uses, imports);
+			}
+			finally {
+				unsetProperty(CURRENT_PACKAGE);
+			}
+
+		}
+	}
+
+	/**
+	 * @param packageName
+	 * @param exports
+	 * @param uses
+	 * @param imports
+	 */
+	protected void doUses(PackageRef packageRef, Packages exports, MultiMap<PackageRef,PackageRef> uses,
+			Packages imports) {
+		Attrs clause = exports.get(packageRef);
+
+		// Check if someone already set the uses: directive
+		String override = clause.get(USES_DIRECTIVE);
+		if (override == null)
+			override = USES_USES;
+
+		// Get the used packages
+		Collection<PackageRef> usedPackages = uses.get(packageRef);
+
+		if (usedPackages != null) {
+
+			// Only do a uses on exported or imported packages
+			// and uses should also not contain our own package
+			// name
+			Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
+			sharedPackages.addAll(imports.keySet());
+			sharedPackages.addAll(exports.keySet());
+			sharedPackages.retainAll(usedPackages);
+			sharedPackages.remove(packageRef);
+
+			StringBuilder sb = new StringBuilder();
+			String del = "";
+			for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
+				PackageRef usedPackage = u.next();
+				if (!usedPackage.isJava()) {
+					sb.append(del);
+					sb.append(usedPackage.getFQN());
+					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, Matcher.quoteReplacement(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_DIRECTIVE, override);
+			}
+		}
+	}
+
+	/**
+	 * Transitively remove all elemens from unreachable through the uses link.
+	 * 
+	 * @param name
+	 * @param unreachable
+	 */
+	void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
+		if (!unreachable.contains(name))
+			return;
+
+		unreachable.remove(name);
+
+		List<PackageRef> ref = uses.get(name);
+		if (ref != null) {
+			for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
+				PackageRef element = r.next();
+				removeTransitive(element, unreachable);
+			}
+		}
+	}
+
+	/**
+	 * Helper method to set the package info resource
+	 * 
+	 * @param dir
+	 * @param key
+	 * @param value
+	 * @throws Exception
+	 */
+	void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
+			throws Exception {
+		if (r == null)
+			return;
+
+		Properties p = new Properties();
+		try {
+			InputStream in = r.openInputStream();
+			try {
+				p.load(in);
+			}
+			finally {
+				in.close();
+			}
+			Attrs map = classpathExports.get(packageRef);
+			if (map == null) {
+				classpathExports.put(packageRef, map = new Attrs());
+			}
+			for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
+				String key = t.nextElement();
+				String value = map.get(key);
+				if (value == null) {
+					value = p.getProperty(key);
+
+					// Messy, to allow directives we need to
+					// allow the value to start with a ':' since we cannot
+					// encode this in a property name
+
+					if (value.startsWith(":")) {
+						key = key + ":";
+						value = value.substring(1);
+					}
+					map.put(key, value);
+				}
+			}
+		}
+		catch (Exception e) {
+			msgs.NoSuchFile_(r);
+		}
+	}
+
+	public void close() {
+		if (diagnostics) {
+			PrintStream out = System.err;
+			out.printf("Current directory            : %s%n", new File("").getAbsolutePath());
+			out.println("Classpath used");
+			for (Jar jar : getClasspath()) {
+				out.printf("File                                : %s%n", jar.getSource());
+				out.printf("File abs path                       : %s%n", jar.getSource().getAbsolutePath());
+				out.printf("Name                                : %s%n", jar.getName());
+				Map<String,Map<String,Resource>> dirs = jar.getDirectories();
+				for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
+					Map<String,Resource> dir = entry.getValue();
+					String name = entry.getKey().replace('/', '.');
+					if (dir != null) {
+						out.printf("                                      %-30s %d%n", name, dir.size());
+					} else {
+						out.printf("                                      %-30s <<empty>>%n", name);
+					}
+				}
+			}
+		}
+
+		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];
+				//$FALL-THROUGH$
+			case 2 :
+				regexp = args[1];
+		}
+		StringBuilder sb = new StringBuilder();
+		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(entry.getKey(), 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);
+					else
+						warning("Cannot find entry on -classpath: %s", s);
+				}
+		}
+		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(Collection< ? > jars) throws IOException {
+		for (Object jar : jars) {
+			if (jar instanceof Jar)
+				addClasspath((Jar) jar);
+			else if (jar instanceof File)
+				addClasspath((File) jar);
+			else if (jar instanceof String)
+				addClasspath(getFile((String) jar));
+			else
+				error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", 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;
+	}
+
+	private void analyzeBundleClasspath() throws Exception {
+		Parameters bcp = getBundleClasspath();
+
+		if (bcp.isEmpty()) {
+			analyzeJar(dot, "", true);
+		} else {
+			boolean okToIncludeDirs = true;
+
+			for (String path : bcp.keySet()) {
+				if (dot.getDirectories().containsKey(path)) {
+					okToIncludeDirs = false;
+					break;
+				}
+			}
+
+			for (String path : bcp.keySet()) {
+				Attrs info = bcp.get(path);
+
+				if (path.equals(".")) {
+					analyzeJar(dot, "", okToIncludeDirs);
+					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, "", true);
+					}
+					catch (Exception e) {
+						warning("Invalid bundle classpath entry: " + path + " " + e);
+					}
+				} else {
+					if (dot.getDirectories().containsKey(path)) {
+						// if directories are used, we should not have dot as we
+						// would have the classes in these directories on the
+						// class path twice.
+						if (bcp.containsKey("."))
+							warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
+									path, path);
+						analyzeJar(dot, Processor.appendPath(path) + "/", true);
+					} else {
+						if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
+							warning("No sub JAR or directory " + path);
+					}
+				}
+			}
+
+		}
+	}
+
+	/**
+	 * 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 boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
+		Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
+
+		next: for (String path : jar.getResources().keySet()) {
+			if (path.startsWith(prefix)) {
+
+				String relativePath = path.substring(prefix.length());
+
+				if (okToIncludeDirs) {
+					int n = relativePath.lastIndexOf('/');
+					if (n < 0)
+						n = relativePath.length();
+					String relativeDir = relativePath.substring(0, n);
+
+					PackageRef packageRef = getPackageRef(relativeDir);
+					if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
+						contained.put(packageRef);
+
+						// For each package we encounter for the first
+						// time. Unfortunately we can only do this once
+						// we found a class since the bcp has a tendency
+						// to overlap
+						if (!packageRef.isMetaData()) {
+							Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
+							getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
+						}
+					}
+				}
+
+				// Check class resources, we need to analyze them
+				if (path.endsWith(".class")) {
+					Resource resource = jar.getResource(path);
+					Clazz clazz;
+					Attrs info = null;
+
+					try {
+						InputStream in = resource.openInputStream();
+						clazz = new Clazz(this, path, resource);
+						try {
+							// Check if we have a package-info
+							if (relativePath.endsWith("/package-info.class")) {
+								// package-info can contain an Export annotation
+								info = new Attrs();
+								parsePackageInfoClass(clazz, info);
+							} else {
+								// Otherwise we just parse it simply
+								clazz.parseClassFile();
+							}
+						}
+						finally {
+							in.close();
+						}
+					}
+					catch (Throwable e) {
+						error("Invalid class file %s (%s)", e, relativePath, e);
+						e.printStackTrace();
+						continue next;
+					}
+
+					String calculatedPath = clazz.getClassName().getPath();
+					if (!calculatedPath.equals(relativePath)) {
+						// If there is a mismatch we
+						// warning
+						if (okToIncludeDirs) // assume already reported
+							mismatched.put(clazz.getAbsolutePath(), clazz);
+					} else {
+						classspace.put(clazz.getClassName(), clazz);
+						PackageRef packageRef = clazz.getClassName().getPackageRef();
+
+						if (!contained.containsKey(packageRef)) {
+							contained.put(packageRef);
+							if (!packageRef.isMetaData()) {
+								Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
+								getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
+							}
+						}
+						if (info != null)
+							contained.merge(packageRef, false, info);
+
+
+						// Look at the referred packages
+						// and copy them to our baseline
+						Set<PackageRef> refs = Create.set();
+						for (PackageRef p : clazz.getReferred()) {
+							referred.put(p);
+							refs.add(p);
+						}
+						refs.remove(packageRef);
+						uses.addAll(packageRef, refs);
+	
+						// Collect the API
+						apiUses.addAll(packageRef, clazz.getAPIUses());
+					}
+				}
+			}
+		}
+
+		if (mismatched.size() > 0) {
+			error("Classes found in the wrong directory: %s", mismatched);
+			return false;
+		}
+		return true;
+	}
+
+	static Pattern	OBJECT_REFERENCE	= Pattern.compile("L([^/]+/)*([^;]+);");
+
+	private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
+		clazz.parseClassFileWithCollector(new ClassDataCollector() {
+			@Override
+			public void annotation(Annotation a) {
+				String name = a.name.getFQN();
+				if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
+
+					// Check version
+					String version = a.get("value");
+					if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
+						if (version != null) {
+							version = getReplacer().process(version);
+							if (Verifier.VERSION.matcher(version).matches())
+								info.put(VERSION_ATTRIBUTE, version);
+							else
+								error("Export annotation in %s has invalid version info: %s", clazz, version);
+						}
+					} else {
+						// Verify this matches with packageinfo
+						String presentVersion = info.get(VERSION_ATTRIBUTE);
+						try {
+							Version av = new Version(presentVersion);
+							Version bv = new Version(version);
+							if (!av.equals(bv)) {
+								error("Version from annotation for %s differs with packageinfo or Manifest", clazz
+										.getClassName().getFQN());
+							}
+						}
+						catch (Exception e) {
+							// Ignore
+						}
+					}
+				} else if (name.equals(Export.class.getName())) {
+
+					// Check mandatory attributes
+					Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
+					if (!attrs.isEmpty()) {
+						info.putAll(attrs);
+						info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
+					}
+
+					// Check optional attributes
+					attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
+					if (!attrs.isEmpty()) {
+						info.putAll(attrs);
+					}
+
+					// Check Included classes
+					Object[] included = a.get(Export.INCLUDE);
+					if (included != null && included.length > 0) {
+						StringBuilder sb = new StringBuilder();
+						String del = "";
+						for (Object i : included) {
+							Matcher m = OBJECT_REFERENCE.matcher((String) i);
+							if (m.matches()) {
+								sb.append(del);
+								sb.append(m.group(2));
+								del = ",";
+							}
+						}
+						info.put(INCLUDE_DIRECTIVE, sb.toString());
+					}
+
+					// Check Excluded classes
+					Object[] excluded = a.get(Export.EXCLUDE);
+					if (excluded != null && excluded.length > 0) {
+						StringBuilder sb = new StringBuilder();
+						String del = "";
+						for (Object i : excluded) {
+							Matcher m = OBJECT_REFERENCE.matcher((String) i);
+							if (m.matches()) {
+								sb.append(del);
+								sb.append(m.group(2));
+								del = ",";
+							}
+						}
+						info.put(EXCLUDE_DIRECTIVE, sb.toString());
+					}
+
+					// Check Uses
+					Object[] uses = a.get(Export.USES);
+					if (uses != null && uses.length > 0) {
+						String old = info.get(USES_DIRECTIVE);
+						if (old == null)
+							old = "";
+						StringBuilder sb = new StringBuilder(old);
+						String del = sb.length() == 0 ? "" : ",";
+
+						for (Object use : uses) {
+							sb.append(del);
+							sb.append(use);
+							del = ",";
+						}
+						info.put(USES_DIRECTIVE, sb.toString());
+					}
+				}
+			}
+
+		});
+	}
+
+	/**
+	 * 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) {
+		Matcher m = Verifier.VERSIONRANGE.matcher(version);
+
+		if (m.matches()) {
+			return version;
+		}
+
+		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;
+		}
+
+		m = fuzzyVersion.matcher(version);
+		if (m.matches()) {
+			StringBuilder result = new StringBuilder();
+			String major = removeLeadingZeroes(m.group(1));
+			String minor = removeLeadingZeroes(m.group(3));
+			String micro = removeLeadingZeroes(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;
+	}
+
+	private static String removeLeadingZeroes(String group) {
+		if (group == null)
+			return null;
+
+		int n = 0;
+		while (n < group.length() - 1 && group.charAt(n) == '0')
+			n++;
+		if (n == 0)
+			return group;
+
+		return group.substring(n);
+	}
+
+	static void cleanupModifier(StringBuilder 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);
+		}
+	}
+
+	final static String	DEFAULT_PROVIDER_POLICY	= "${range;[==,=+)}";
+	final static String	DEFAULT_CONSUMER_POLICY	= "${range;[==,+)}";
+
+	public String getVersionPolicy(boolean implemented) {
+		if (implemented) {
+			return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
+		}
+
+		return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
+	}
+
+	/**
+	 * 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) throws Exception {
+		// Macro.verifyCommand(args, _classesHelp, new
+		// Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
+		// null}, 3,3);
+
+		Collection<Clazz> matched = getClasses(args);
+		if (matched.isEmpty())
+			return "";
+
+		return join(matched);
+	}
+
+	public Collection<Clazz> getClasses(String... args) throws Exception {
+
+		Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
+		for (int i = 1; i < args.length; i++) {
+			if (args.length < i + 1)
+				throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
+						+ _classesHelp);
+
+			String typeName = args[i];
+			if (typeName.equalsIgnoreCase("extending"))
+				typeName = "extends";
+			else if (typeName.equalsIgnoreCase("importing"))
+				typeName = "imports";
+			else if (typeName.equalsIgnoreCase("implementing"))
+				typeName = "implements";
+
+			Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
+
+			if (type == null)
+				throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
+
+			Instruction instr = null;
+			if (Clazz.HAS_ARGUMENT.contains(type)) {
+				String s = args[++i];
+				instr = new Instruction(s);
+			}
+			for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
+				Clazz clazz = c.next();
+				if (!clazz.is(type, instr, this)) {
+					c.remove();
+				}
+			}
+		}
+		return 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<TypeRef,Clazz> getClassspace() {
+		return classspace;
+	}
+
+	/**
+	 * Locate a resource on the class path.
+	 * 
+	 * @param path
+	 *            Path of the reosurce
+	 * @return A resource or <code>null</code>
+	 */
+	public Resource findResource(String path) {
+		for (Jar entry : getClasspath()) {
+			Resource r = entry.getResource(path);
+			if (r != null)
+				return r;
+		}
+		return null;
+	}
+
+	/**
+	 * Find a clazz on the class path. This class has been parsed.
+	 * 
+	 * @param path
+	 * @return
+	 */
+	public Clazz findClass(TypeRef typeRef) throws Exception {
+		Clazz c = classspace.get(typeRef);
+		if (c != null)
+			return c;
+
+		c = importedClassesCache.get(typeRef);
+		if (c != null)
+			return c;
+
+		Resource r = findResource(typeRef.getPath());
+		if (r == null) {
+			getClass().getClassLoader();
+			URL url = ClassLoader.getSystemResource(typeRef.getPath());
+			if (url != null)
+				r = new URLResource(url);
+		}
+		if (r != null) {
+			c = new Clazz(this, typeRef.getPath(), r);
+			c.parseClassFile();
+			importedClassesCache.put(typeRef, c);
+		}
+		return c;
+	}
+
+	/**
+	 * Answer the bundle version.
+	 * 
+	 * @return
+	 */
+	public String getVersion() {
+		String version = getProperty(BUNDLE_VERSION);
+		if (version == null)
+			version = "0.0.0";
+		return version;
+	}
+
+	public boolean isNoBundle() {
+		return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
+	}
+
+	public void referTo(TypeRef ref) {
+		PackageRef pack = ref.getPackageRef();
+		if (!referred.containsKey(pack))
+			referred.put(pack, new Attrs());
+	}
+
+	public void referToByBinaryName(String binaryClassName) {
+		TypeRef ref = descriptors.getTypeRef(binaryClassName);
+		referTo(ref);
+	}
+
+	/**
+	 * Ensure that we are running on the correct bnd.
+	 */
+	void doRequireBnd() {
+		Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
+		if (require == null || require.isEmpty())
+			return;
+
+		Hashtable<String,String> map = new Hashtable<String,String>();
+		map.put(Constants.VERSION_FILTER, getBndVersion());
+
+		for (String filter : require.keySet()) {
+			try {
+				Filter f = new Filter(filter);
+				if (f.match(map))
+					continue;
+				error("%s fails %s", REQUIRE_BND, require.get(filter));
+			}
+			catch (Exception t) {
+				error("%s with value %s throws exception", t, REQUIRE_BND, require);
+			}
+		}
+	}
+
+	/**
+	 * md5 macro
+	 */
+
+	static String	_md5Help	= "${md5;path}";
+
+	public String _md5(String args[]) throws Exception {
+		Macro.verifyCommand(args, _md5Help, new Pattern[] {
+				null, null, Pattern.compile("base64|hex")
+		}, 2, 3);
+
+		Digester<MD5> digester = MD5.getDigester();
+		Resource r = dot.getResource(args[1]);
+		if (r == null)
+			throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
+
+		IO.copy(r.openInputStream(), digester);
+		boolean hex = args.length > 2 && args[2].equals("hex");
+		if (hex)
+			return Hex.toHexString(digester.digest().digest());
+
+		return Base64.encodeBase64(digester.digest().digest());
+	}
+
+	/**
+	 * SHA1 macro
+	 */
+
+	static String	_sha1Help	= "${sha1;path}";
+
+	public String _sha1(String args[]) throws Exception {
+		Macro.verifyCommand(args, _sha1Help, new Pattern[] {
+				null, null, Pattern.compile("base64|hex")
+		}, 2, 3);
+		Digester<SHA1> digester = SHA1.getDigester();
+		Resource r = dot.getResource(args[1]);
+		if (r == null)
+			throw new FileNotFoundException("From sha1, not found " + args[1]);
+
+		IO.copy(r.openInputStream(), digester);
+		return Base64.encodeBase64(digester.digest().digest());
+	}
+
+	public Descriptor getDescriptor(String descriptor) {
+		return descriptors.getDescriptor(descriptor);
+	}
+
+	public TypeRef getTypeRef(String binaryClassName) {
+		return descriptors.getTypeRef(binaryClassName);
+	}
+
+	public PackageRef getPackageRef(String binaryName) {
+		return descriptors.getPackageRef(binaryName);
+	}
+
+	public TypeRef getTypeRefFromFQN(String fqn) {
+		return descriptors.getTypeRefFromFQN(fqn);
+	}
+
+	public TypeRef getTypeRefFromPath(String path) {
+		return descriptors.getTypeRefFromPath(path);
+	}
+
+	public boolean isImported(PackageRef packageRef) {
+		return imports.containsKey(packageRef);
+	}
+
+	/**
+	 * Merge the attributes of two maps, where the first map can contain
+	 * wildcarded names. The idea is that the first map contains instructions
+	 * (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.
+	 * @param source
+	 *            the actual found packages, contains no duplicates
+	 * @return Only the packages that were filtered by the given instructions
+	 */
+
+	Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
+		Packages result = new Packages();
+		List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
+		Collections.sort(refs);
+
+		List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
+		if (nomatch == null)
+			nomatch = Create.set();
+
+		for (Instruction instruction : filters) {
+			boolean match = false;
+
+			for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
+				PackageRef packageRef = i.next();
+
+				if (packageRef.isMetaData()) {
+					i.remove(); // no use checking it again
+					continue;
+				}
+
+				String packageName = packageRef.getFQN();
+
+				if (instruction.matches(packageName)) {
+					match = true;
+					if (!instruction.isNegated()) {
+						result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
+								instructions.get(instruction));
+					}
+					i.remove(); // Can never match again for another pattern
+				}
+			}
+			if (!match && !instruction.isAny())
+				nomatch.add(instruction);
+		}
+
+		/*
+		 * Tricky. If we have umatched instructions they might indicate that we
+		 * want to have multiple decorators for the same package. So we check
+		 * the unmatched against the result list. If then then match and have
+		 * actually interesting properties then we merge them
+		 */
+
+		for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
+			Instruction instruction = i.next();
+
+			// We assume the user knows what he is
+			// doing and inserted a literal. So
+			// we ignore any not matched literals
+			if (instruction.isLiteral()) {
+				result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
+				i.remove();
+				continue;
+			}
+
+			// Not matching a negated instruction looks
+			// like an error ...
+			if (instruction.isNegated()) {
+				continue;
+			}
+
+			// An optional instruction should not generate
+			// an error
+			if (instruction.isOptional()) {
+				i.remove();
+				continue;
+			}
+
+			// boolean matched = false;
+			// Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
+			// for (PackageRef ref : prefs) {
+			// if (instruction.matches(ref.getFQN())) {
+			// result.merge(ref, true, source.get(ref),
+			// instructions.get(instruction));
+			// matched = true;
+			// }
+			// }
+			// if (matched)
+			// i.remove();
+		}
+		return result;
+	}
+
+	public void setDiagnostics(boolean b) {
+		diagnostics = b;
+	}
+
+	public Clazz.JAVA getLowestEE() {
+		if (ees.isEmpty())
+			return Clazz.JAVA.JDK1_4;
+
+		return ees.first();
+	}
+
+	public String _ee(@SuppressWarnings("unused")
+	String args[]) {
+		return getLowestEE().getEE();
+	}
+
+	/**
+	 * Calculate the output file for the given target. The strategy is:
+	 * 
+	 * <pre>
+	 * parameter given if not null and not directory
+	 * if directory, this will be the output directory
+	 * based on bsn-version.jar
+	 * name of the source file if exists
+	 * Untitled-[n]
+	 * </pre>
+	 * 
+	 * @param output
+	 *            may be null, otherwise a file path relative to base
+	 */
+	public File getOutputFile(String output) {
+
+		if (output == null)
+			output = get(Constants.OUTPUT);
+
+		File outputDir;
+
+		if (output != null) {
+			File outputFile = getFile(output);
+			if (outputFile.isDirectory())
+				outputDir = outputFile;
+			else
+				return outputFile;
+		} else
+			outputDir = getBase();
+
+		Entry<String,Attrs> name = getBundleSymbolicName();
+		if (name != null) {
+			String bsn = name.getKey();
+			String version = getBundleVersion();
+			Version v = Version.parseVersion(version);
+			String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
+			return new File(outputDir, outputName);
+		}
+
+		File source = getJar().getSource();
+		if (source != null) {
+			String outputName = source.getName();
+			return new File(outputDir, outputName);
+		}
+
+		error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
+		int n = 0;
+		File f = getFile(outputDir, "Untitled");
+		while (f.isFile()) {
+			f = getFile(outputDir, "Untitled-" + n++);
+		}
+		return f;
+	}
+
+	/**
+	 * Utility function to carefully save the file. Will create a backup if the
+	 * source file has the same path as the output. It will also only save if
+	 * the file was modified or the force flag is true
+	 * 
+	 * @param output
+	 *            the output file, if null {@link #getOutputFile(String)} is
+	 *            used.
+	 * @param force
+	 *            if it needs to be overwritten
+	 * @throws Exception
+	 */
+
+	public boolean save(File output, boolean force) throws Exception {
+		if (output == null)
+			output = getOutputFile(null);
+
+		Jar jar = getJar();
+		File source = jar.getSource();
+
+		trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
+				jar.lastModified() - output.lastModified());
+
+		if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
+			output.getParentFile().mkdirs();
+			if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
+				File bak = new File(source.getParentFile(), source.getName() + ".bak");
+				if (!source.renameTo(bak)) {
+					error("Could not create backup file %s", bak);
+				} else
+					source.delete();
+			}
+			try {
+				trace("Saving jar to %s", output);
+				getJar().write(output);
+			}
+			catch (Exception e) {
+				output.delete();
+				error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
+			}
+			return true;
+		}
+		trace("Not modified %s", output);
+		return false;
+
+	}
+
+	/**
+	 * Set default import and export instructions if none are set
+	 */
+	public void setDefaults(String bsn, Version version) {
+		if (getExportPackage() == null)
+			setExportPackage("*");
+		if (getImportPackage() == null)
+			setExportPackage("*");
+		if (bsn != null && getBundleSymbolicName() == null)
+			setBundleSymbolicName(bsn);
+		if (version != null && getBundleVersion() == null)
+			setBundleVersion(version);
+	}
+
+	/**
+	 * Remove the own references and optionall java references from the uses lib
+	 * @param apiUses
+	 * @param removeJava
+	 * @return
+	 */
+	public MultiMap<PackageRef,PackageRef> cleanupUses(MultiMap<PackageRef,PackageRef> apiUses, boolean removeJava) {
+		MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
+		for ( Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
+			e.getValue().remove(e.getKey());
+			if (!removeJava)
+				continue;
+			
+			for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext(); ) {
+				if ( i.next().isJava())
+					i.remove();
+			}
+		}		
+		return map;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/AnalyzerMessages.java b/bundleplugin/src/main/java/aQute/bnd/osgi/AnalyzerMessages.java
new file mode 100644
index 0000000..aac1a52
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/AnalyzerMessages.java
@@ -0,0 +1,13 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.libg.reporter.*;
+
+public interface AnalyzerMessages extends Messages {
+
+	WARNING Export_Has_PrivateReferences_(PackageRef exported, int count, Collection<PackageRef> local);
+/**/
+}
+
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java
new file mode 100644
index 0000000..37c4cad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Annotation.java
@@ -0,0 +1,74 @@
+package aQute.bnd.osgi;
+
+import java.lang.annotation.*;
+import java.util.*;
+
+import aQute.bnd.annotation.metatype.*;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+
+public class Annotation {
+	TypeRef				name;
+	Map<String,Object>	elements;
+	ElementType			member;
+	RetentionPolicy		policy;
+
+	public Annotation(TypeRef name, Map<String,Object> elements, ElementType member, RetentionPolicy policy) {
+		this.name = name;
+		if (elements == null)
+			this.elements = Collections.emptyMap();
+		else
+			this.elements = elements;
+		this.member = member;
+		this.policy = policy;
+	}
+
+	public TypeRef getName() {
+		return name;
+	}
+
+	public ElementType getElementType() {
+		return member;
+	}
+
+	public RetentionPolicy getRetentionPolicy() {
+		return policy;
+	}
+
+	public String toString() {
+		return name + ":" + member + ":" + policy + ":" + elements;
+	}
+
+	public <T> T get(String string) {
+		if (elements == null)
+			return null;
+
+		return (T) elements.get(string);
+	}
+
+	public <T> void put(String string, Object v) {
+		if (elements == null)
+			return;
+
+		elements.put(string, v);
+	}
+
+	public Set<String> keySet() {
+		if (elements == null)
+			return Collections.emptySet();
+
+		return elements.keySet();
+	}
+
+	public <T extends java.lang.annotation.Annotation> T getAnnotation() throws Exception {
+		String cname = name.getFQN();
+		Class<T> c = (Class<T>) getClass().getClassLoader().loadClass(cname);
+		return getAnnotation(c);
+	}
+
+	public <T extends java.lang.annotation.Annotation> T getAnnotation(Class<T> c) throws Exception {
+		String cname = name.getFQN();
+		if (!c.getName().equals(cname))
+			return null;
+		return Configurable.createConfigurable(c, elements);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java
new file mode 100755
index 0000000..a5bb7f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Builder.java
@@ -0,0 +1,1575 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.bnd.component.*;
+import aQute.bnd.differ.*;
+import aQute.bnd.differ.Baseline.Info;
+import aQute.bnd.header.*;
+import aQute.bnd.make.*;
+import aQute.bnd.make.component.*;
+import aQute.bnd.make.metatype.*;
+import aQute.bnd.maven.*;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.bnd.service.*;
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.diff.*;
+import aQute.lib.collections.*;
+import aQute.libg.generics.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+ Private-Package: package-decl ( ','
+ * package-decl )* Export-Package: package-decl ( ',' package-decl )*
+ * Import-Package: package-decl ( ',' package-decl )*
+ * 
+ * @version $Revision$
+ */
+public class Builder extends Analyzer {
+	static Pattern					IR_PATTERN			= Pattern.compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
+	private final DiffPluginImpl	differ				= new DiffPluginImpl();
+	private Pattern					xdoNotCopy			= null;
+	private static final int		SPLIT_MERGE_LAST	= 1;
+	private static final int		SPLIT_MERGE_FIRST	= 2;
+	private static final int		SPLIT_ERROR			= 3;
+	private static final int		SPLIT_FIRST			= 4;
+	private static final int		SPLIT_DEFAULT		= 0;
+	private final List<File>		sourcePath			= new ArrayList<File>();
+	private final Make				make				= new Make(this);
+
+	public Builder(Processor parent) {
+		super(parent);
+	}
+
+	public Builder() {}
+
+	public Jar build() throws Exception {
+		trace("build");
+		init();
+		if (isTrue(getProperty(NOBUNDLES)))
+			return null;
+
+		if (getProperty(CONDUIT) != null)
+			error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
+
+		Jar dot = new Jar("dot");
+		try {
+			long modified = Long.parseLong(getProperty("base.modified"));
+			dot.updateModified(modified, "Base modified");
+		}
+		catch (Exception e) {
+			// Ignore
+		}
+		setJar(dot);
+
+		doExpand(dot);
+		doIncludeResources(dot);
+		doWab(dot);
+
+
+		// Check if we override the calculation of the
+		// manifest. We still need to calculated it because
+		// we need to have analyzed the classpath.
+
+		Manifest manifest = calcManifest();
+
+		String mf = getProperty(MANIFEST);
+		if (mf != null) {
+			File mff = getFile(mf);
+			if (mff.isFile()) {
+				try {
+					InputStream in = new FileInputStream(mff);
+					manifest = new Manifest(in);
+					in.close();
+				}
+				catch (Exception e) {
+					error(MANIFEST + " while reading manifest file", e);
+				}
+			} else {
+				error(MANIFEST + ", no such file " + mf);
+			}
+		}
+
+		if (getProperty(NOMANIFEST) == null)
+			dot.setManifest(manifest);
+		else
+			dot.setDoNotTouchManifest();
+
+		// This must happen after we analyzed so
+		// we know what it is on the classpath
+		addSources(dot);
+
+		if (getProperty(POM) != null)
+			dot.putResource("pom.xml", new PomResource(dot.getManifest()));
+		
+		if (!isNoBundle())
+			doVerify(dot);
+
+		if (dot.getResources().isEmpty())
+			warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
+					getBsn());
+
+		dot.updateModified(lastModified(), "Last Modified Processor");
+		dot.setName(getBsn());
+
+		sign(dot);
+		doSaveManifest(dot);
+
+		doDiff(dot); // check if need to diff this bundle
+		doBaseline(dot); // check for a baseline
+		return dot;
+	}
+
+
+	/**
+	 * Check if we need to calculate any checksums.
+	 * 
+	 * @param dot
+	 * @throws Exception
+	 */
+	private void doDigests(Jar dot) throws Exception {
+		Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
+		if (ps.isEmpty())
+			return;
+		trace("digests %s", ps);
+		String[] digests = ps.keySet().toArray(new String[ps.size()]);
+		dot.calcChecksums(digests);
+	}
+
+	/**
+	 * Allow any local initialization by subclasses before we build.
+	 */
+	public void init() throws Exception {
+		begin();
+		doRequireBnd();
+
+		// Check if we have sensible setup
+
+		if (getClasspath().size() == 0
+				&& (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+			warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+	}
+
+	/**
+	 * Turn this normal bundle in a web and add any resources.
+	 * 
+	 * @throws Exception
+	 */
+	private Jar doWab(Jar dot) throws Exception {
+		String wab = getProperty(WAB);
+		String wablib = getProperty(WABLIB);
+		if (wab == null && wablib == null)
+			return dot;
+
+		trace("wab %s %s", wab, wablib);
+		setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
+
+		Set<String> paths = new HashSet<String>(dot.getResources().keySet());
+
+		for (String path : paths) {
+			if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+				trace("wab: moving: %s", path);
+				dot.rename(path, "WEB-INF/classes/" + path);
+			}
+		}
+
+		Parameters clauses = parseHeader(getProperty(WABLIB));
+		for (String key : clauses.keySet()) {
+			File f = getFile(key);
+			addWabLib(dot, f);
+		}
+		doIncludeResource(dot, wab);
+		return dot;
+	}
+
+	/**
+	 * Add a wab lib to the jar.
+	 * 
+	 * @param f
+	 */
+	private void addWabLib(Jar dot, File f) throws Exception {
+		if (f.exists()) {
+			Jar jar = new Jar(f);
+			jar.setDoNotTouchManifest();
+			addClose(jar);
+			String path = "WEB-INF/lib/" + f.getName();
+			dot.putResource(path, new JarResource(jar));
+			setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
+
+			Manifest m = jar.getManifest();
+			String cp = m.getMainAttributes().getValue("Class-Path");
+			if (cp != null) {
+				Collection<String> parts = split(cp, ",");
+				for (String part : parts) {
+					File sub = getFile(f.getParentFile(), part);
+					if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
+						warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
+								f);
+					} else {
+						addWabLib(dot, sub);
+					}
+				}
+			}
+		} else {
+			error("WAB lib does not exist %s", f);
+		}
+	}
+
+	/**
+	 * Get the manifest and write it out separately if -savemanifest is set
+	 * 
+	 * @param dot
+	 */
+	private void doSaveManifest(Jar dot) throws Exception {
+		String output = getProperty(SAVEMANIFEST);
+		if (output == null)
+			return;
+
+		File f = getFile(output);
+		if (f.isDirectory()) {
+			f = new File(f, "MANIFEST.MF");
+		}
+		f.delete();
+		f.getParentFile().mkdirs();
+		OutputStream out = new FileOutputStream(f);
+		try {
+			Jar.writeManifest(dot.getManifest(), out);
+		}
+		finally {
+			out.close();
+		}
+		changedFile(f);
+	}
+
+	protected void changedFile(@SuppressWarnings("unused") File f) {}
+
+	/**
+	 * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
+	 * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
+	 * 
+	 * @return
+	 */
+
+	void sign(@SuppressWarnings("unused") Jar jar) throws Exception {
+		String signing = getProperty("-sign");
+		if (signing == null)
+			return;
+
+		trace("Signing %s, with %s", getBsn(), signing);
+		List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
+
+		Parameters infos = parseHeader(signing);
+		for (Entry<String,Attrs> entry : infos.entrySet()) {
+			for (SignerPlugin signer : signers) {
+				signer.sign(this, entry.getKey());
+			}
+		}
+	}
+
+	public boolean hasSources() {
+		return isTrue(getProperty(SOURCES));
+	}
+
+	/**
+	 * Answer extra packages. In this case we implement conditional package. Any
+	 */
+	protected Jar getExtra() throws Exception {
+		Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
+		if (conditionals.isEmpty())
+			return null;
+		trace("do Conditional Package %s", conditionals);
+		Instructions instructions = new Instructions(conditionals);
+
+		Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
+		referred.removeAll(getContained().keySet());
+
+		Jar jar = new Jar("conditional-import");
+		addClose(jar);
+		for (PackageRef pref : referred) {
+			for (Jar cpe : getClasspath()) {
+				Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
+				if (map != null) {
+					copy(jar, cpe, pref.getPath(), false);
+// Now use copy so that bnd.info is processed, next line should be 
+// removed in the future TODO
+//					jar.addDirectory(map, false);
+					break;
+				}
+			}
+		}
+		if (jar.getDirectories().size() == 0)
+			return null;
+		return jar;
+	}
+
+	/**
+	 * Intercept the call to analyze and cleanup versions after we have analyzed
+	 * the setup. We do not want to cleanup if we are going to verify.
+	 */
+
+	public void analyze() throws Exception {
+		super.analyze();
+		cleanupVersion(getImports(), null);
+		cleanupVersion(getExports(), getVersion());
+		String version = getProperty(BUNDLE_VERSION);
+		if (version != null) {
+			version = cleanupVersion(version);
+			if (version.endsWith(".SNAPSHOT")) {
+				version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
+			}
+			setProperty(BUNDLE_VERSION, version);
+		}
+	}
+
+	public void cleanupVersion(Packages packages, String defaultVersion) {
+		for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
+			Attrs attributes = entry.getValue();
+			String v = attributes.get(Constants.VERSION_ATTRIBUTE);
+			if (v == null && defaultVersion != null) {
+				if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
+					v = defaultVersion;
+					if (isPedantic())
+						warning("Used bundle version %s for exported package %s", v, entry.getKey());
+				} else {
+					if (isPedantic())
+						warning("No export version for exported package %s", entry.getKey());
+				}
+			}
+			if (v != null)
+				attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
+		}
+	}
+
+	/**
+     * 
+     */
+	private void addSources(Jar dot) {
+		if (!hasSources())
+			return;
+
+		Set<PackageRef> packages = Create.set();
+
+		for (TypeRef typeRef : getClassspace().keySet()) {
+			PackageRef packageRef = typeRef.getPackageRef();
+			String sourcePath = typeRef.getSourcePath();
+			String packagePath = packageRef.getPath();
+
+			boolean found = false;
+			String[] fixed = {
+					"packageinfo", "package.html", "module-info.java", "package-info.java"
+			};
+
+			for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
+				File root = i.next();
+
+				// TODO should use bcp?
+
+				File f = getFile(root, sourcePath);
+				if (f.exists()) {
+					found = true;
+					if (!packages.contains(packageRef)) {
+						packages.add(packageRef);
+						File bdir = getFile(root, packagePath);
+						for (int j = 0; j < fixed.length; j++) {
+							File ff = getFile(bdir, fixed[j]);
+							if (ff.isFile()) {
+								String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
+								dot.putResource(name, new FileResource(ff));
+							}
+						}
+					}
+					if (packageRef.isDefaultPackage())
+						System.err.println("Duh?");
+					dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
+				}
+			}
+			if (!found) {
+				for (Jar jar : getClasspath()) {
+					Resource resource = jar.getResource(sourcePath);
+					if (resource != null) {
+						dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+					} else {
+						resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
+						if (resource != null) {
+							dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+						}
+					}
+				}
+			}
+			if (getSourcePath().isEmpty())
+				warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
+			// TODO copy from the jars where they came from
+		}
+	}
+
+	boolean			firstUse	= true;
+	private Tree	tree;
+
+	public Collection<File> getSourcePath() {
+		if (firstUse) {
+			firstUse = false;
+			String sp = getProperty(SOURCEPATH);
+			if (sp != null) {
+				Parameters map = parseHeader(sp);
+				for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+					String file = i.next();
+					if (!isDuplicate(file)) {
+						File f = getFile(file);
+						if (!f.isDirectory()) {
+							error("Adding a sourcepath that is not a directory: " + f);
+						} else {
+							sourcePath.add(f);
+						}
+					}
+				}
+			}
+		}
+		return sourcePath;
+	}
+
+	private void doVerify(@SuppressWarnings("unused") Jar dot) throws Exception {
+		Verifier verifier = new Verifier(this);
+		// Give the verifier the benefit of our analysis
+		// prevents parsing the files twice
+		verifier.verify();
+		getInfo(verifier);
+	}
+
+	private void doExpand(Jar dot) {
+
+		// Build an index of the class path that we can then
+		// use destructively
+		MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
+		for (Jar srce : getClasspath()) {
+			for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
+				if (e.getValue() != null)
+					packages.add(e.getKey(), srce);
+			}
+		}
+
+		Parameters privatePackages = getPrivatePackage();
+		if (isTrue(getProperty(Constants.UNDERTEST))) {
+			String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
+			privatePackages.putAll(parseHeader(h));
+		}
+
+		if (!privatePackages.isEmpty()) {
+			Instructions privateFilter = new Instructions(privatePackages);
+			Set<Instruction> unused = doExpand(dot, packages, privateFilter);
+
+			if (!unused.isEmpty()) {
+				warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
+			}
+		}
+
+		Parameters exportedPackage = getExportPackage();
+		if (!exportedPackage.isEmpty()) {
+			Instructions exportedFilter = new Instructions(exportedPackage);
+
+			// We ignore unused instructions for exports, they should show
+			// up as errors during analysis. Otherwise any overlapping
+			// packages with the private packages should show up as
+			// unused
+
+			doExpand(dot, packages, exportedFilter);
+		}
+	}
+
+	/**
+	 * Destructively filter the packages from the build up index. This index is
+	 * used by the Export Package as well as the Private Package
+	 * 
+	 * @param jar
+	 * @param name
+	 * @param instructions
+	 */
+	private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
+		Set<Instruction> unused = Create.set();
+
+		for (Entry<Instruction,Attrs> e : filter.entrySet()) {
+			Instruction instruction = e.getKey();
+			if (instruction.isDuplicate())
+				continue;
+
+			Attrs directives = e.getValue();
+
+			// We can optionally filter on the
+			// source of the package. We assume
+			// they all match but this can be overridden
+			// on the instruction
+			Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
+
+			boolean used = false;
+
+			for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
+				Entry<String,List<Jar>> p = entry.next();
+
+				String directory = p.getKey();
+				PackageRef packageRef = getPackageRef(directory);
+
+				// Skip * and meta data, we're talking packages!
+				if (packageRef.isMetaData() && instruction.isAny())
+					continue;
+
+				if (!instruction.matches(packageRef.getFQN()))
+					continue;
+
+				// Ensure it is never matched again
+				entry.remove();
+
+				// ! effectively removes it from consideration by others (this
+				// includes exports)
+				if (instruction.isNegated())
+					continue;
+
+				// Do the from: directive, filters on the JAR type
+				List<Jar> providers = filterFrom(from, p.getValue());
+				if (providers.isEmpty())
+					continue;
+
+				int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
+				copyPackage(jar, providers, directory, splitStrategy);
+
+				used = true;
+			}
+
+			if (!used && !isTrue(directives.get("optional:")))
+				unused.add(instruction);
+		}
+		return unused;
+	}
+
+	/**
+	 * @param from
+	 * @return
+	 */
+	private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
+		if (from.isAny())
+			return providers;
+
+		List<Jar> np = new ArrayList<Jar>();
+		for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
+			Jar j = i.next();
+			if (from.matches(j.getName())) {
+				np.add(j);
+			}
+		}
+		return np;
+	}
+
+	/**
+	 * Copy the package from the providers based on the split package strategy.
+	 * 
+	 * @param dest
+	 * @param providers
+	 * @param directory
+	 * @param splitStrategy
+	 */
+	private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
+		switch (splitStrategy) {
+			case SPLIT_MERGE_LAST :
+				for (Jar srce : providers) {
+					copy(dest, srce, path, true);
+				}
+				break;
+
+			case SPLIT_MERGE_FIRST :
+				for (Jar srce : providers) {
+					copy(dest, srce, path, false);
+				}
+				break;
+
+			case SPLIT_ERROR :
+				error(diagnostic(path, providers));
+				break;
+
+			case SPLIT_FIRST :
+				copy(dest, providers.get(0), path, false);
+				break;
+
+			default :
+				if (providers.size() > 1)
+					warning("%s", diagnostic(path, providers));
+				for (Jar srce : providers) {
+					copy(dest, srce, path, false);
+				}
+				break;
+		}
+	}
+
+	/**
+	 * Cop
+	 * 
+	 * @param dest
+	 * @param srce
+	 * @param path
+	 * @param overwriteResource
+	 */
+	private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
+		trace("copy d=" + dest + " s=" + srce +" p="+ path);
+		dest.copy(srce, path, overwrite);
+		
+		// bnd.info sources must be preprocessed
+		String bndInfoPath = path + "/bnd.info";
+		Resource r = dest.getResource(bndInfoPath);
+		if ( r != null && !(r instanceof PreprocessResource)) {
+			trace("preprocessing bnd.info");
+			PreprocessResource pp = new PreprocessResource(this, r);
+			dest.putResource(bndInfoPath, pp);
+		}
+		
+		if (hasSources()) {
+			String srcPath = "OSGI-OPT/src/" + path;
+			Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
+			if (srcContents != null) {
+				dest.addDirectory(srcContents, overwrite);
+			}
+		}
+	}
+
+	/**
+	 * Analyze the classpath for a split package
+	 * 
+	 * @param pack
+	 * @param classpath
+	 * @param source
+	 * @return
+	 */
+	private String diagnostic(String pack, List<Jar> culprits) {
+		// Default is like merge-first, but with a warning
+		return "Split package, multiple jars provide the same package:"
+				+ pack
+				+ "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
+				+ "Package found in   " + culprits + "\n" //
+				+ "Class path         " + getClasspath();
+	}
+
+	private int getSplitStrategy(String type) {
+		if (type == null)
+			return SPLIT_DEFAULT;
+
+		if (type.equals("merge-last"))
+			return SPLIT_MERGE_LAST;
+
+		if (type.equals("merge-first"))
+			return SPLIT_MERGE_FIRST;
+
+		if (type.equals("error"))
+			return SPLIT_ERROR;
+
+		if (type.equals("first"))
+			return SPLIT_FIRST;
+
+		error("Invalid strategy for split-package: " + type);
+		return SPLIT_DEFAULT;
+	}
+
+	/**
+	 * Matches the instructions against a package.
+	 * 
+	 * @param instructions
+	 *            The list of instructions
+	 * @param pack
+	 *            The name of the package
+	 * @param unused
+	 *            The total list of patterns, matched patterns are removed
+	 * @param source
+	 *            The name of the source container, can be filtered upon with
+	 *            the from: directive.
+	 * @return
+	 */
+	private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
+		for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
+			Instruction pattern = entry.getKey();
+
+			// It is possible to filter on the source of the
+			// package with the from: directive. This is an
+			// instruction that must match the name of the
+			// source class path entry.
+
+			String from = entry.getValue().get(FROM_DIRECTIVE);
+			if (from != null) {
+				Instruction f = new Instruction(from);
+				if (!f.matches(source) || f.isNegated())
+					continue;
+			}
+
+			// Now do the normal
+			// matching
+			if (pattern.matches(pack)) {
+				if (unused != null)
+					unused.remove(pattern);
+				return pattern;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Parse the Bundle-Includes header. Files in the bundles Include header are
+	 * included in the jar. The source can be a directory or a file.
+	 * 
+	 * @throws IOException
+	 * @throws FileNotFoundException
+	 */
+	private void doIncludeResources(Jar jar) throws Exception {
+		String includes = getProperty("Bundle-Includes");
+		if (includes == null) {
+			includes = getProperty(INCLUDERESOURCE);
+			if (includes == null || includes.length() == 0)
+				includes = getProperty("Include-Resource");
+		} else
+			warning("Please use -includeresource instead of Bundle-Includes");
+
+		doIncludeResource(jar, includes);
+
+	}
+
+	private void doIncludeResource(Jar jar, String includes) throws Exception {
+		Parameters clauses = parseHeader(includes);
+		doIncludeResource(jar, clauses);
+	}
+
+	private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
+		for (Entry<String,Attrs> entry : clauses.entrySet()) {
+			doIncludeResource(jar, entry.getKey(), entry.getValue());
+		}
+	}
+
+	private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
+			Exception {
+
+		boolean preprocess = false;
+		boolean absentIsOk = false;
+
+		if (name.startsWith("{") && name.endsWith("}")) {
+			preprocess = true;
+			name = name.substring(1, name.length() - 1).trim();
+		}
+
+		String parts[] = name.split("\\s*=\\s*");
+		String source = parts[0];
+		String destination = parts[0];
+		if (parts.length == 2)
+			source = parts[1];
+
+		if (source.startsWith("-")) {
+			source = source.substring(1);
+			absentIsOk = true;
+		}
+
+		if (source.startsWith("@")) {
+			extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
+		} else if (extra.containsKey("cmd")) {
+			doCommand(jar, source, destination, extra, preprocess, absentIsOk);
+		} else if (extra.containsKey("literal")) {
+			String literal = extra.get("literal");
+			Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
+			String x = extra.get("extra");
+			if (x != null)
+				r.setExtra(x);
+			jar.putResource(name, r);
+		} else {
+			File sourceFile;
+			String destinationPath;
+
+			sourceFile = getFile(source);
+			if (parts.length == 1) {
+				// Directories should be copied to the root
+				// but files to their file name ...
+				if (sourceFile.isDirectory())
+					destinationPath = "";
+				else
+					destinationPath = sourceFile.getName();
+			} else {
+				destinationPath = parts[0];
+			}
+			// Handle directories
+			if (sourceFile.isDirectory()) {
+				destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
+				return;
+			}
+
+			// destinationPath = checkDestinationPath(destinationPath);
+
+			if (!sourceFile.exists()) {
+				if (absentIsOk)
+					return;
+
+				noSuchFile(jar, name, extra, source, destinationPath);
+			} else
+				copy(jar, destinationPath, sourceFile, preprocess, extra);
+		}
+	}
+
+	/**
+	 * It is possible in Include-Resource to use a system command that generates
+	 * the contents, this is indicated with {@code cmd} attribute. The command
+	 * can be repeated for a number of source files with the {@code for}
+	 * attribute which indicates a list of repetitions, often down with the
+	 * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
+	 * repetition will repeat the given command for each item. The @} macro can
+	 * be used to replace the current item. If no {@code for} is given, the
+	 * source is used as the only item. If the destination contains a macro,
+	 * each iteration will create a new file, otherwise the destination name is
+	 * used. The execution of the command is delayed until the JAR is actually
+	 * written to the file system for performance reasons.
+	 * 
+	 * @param jar
+	 * @param source
+	 * @param destination
+	 * @param extra
+	 * @param preprocess
+	 * @param absentIsOk
+	 */
+	private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
+			boolean absentIsOk) {
+		String repeat = extra.get("for"); // TODO constant
+		if (repeat == null)
+			repeat = source;
+
+		Collection<String> requires = split(extra.get("requires"));
+		long lastModified = 0;
+		for (String required : requires) {
+			File file = getFile(required);
+			if (!file.isFile()) {
+				error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
+						file.getAbsoluteFile());
+			} else
+				lastModified = Math.max(lastModified, file.lastModified());
+		}
+
+		String cmd = extra.get("cmd");
+
+		Collection<String> items = Processor.split(repeat);
+
+		CombinedResource cr = null;
+
+		if (!destination.contains("${@}")) {
+			cr = new CombinedResource();
+		}
+		trace("last modified requires %s", lastModified);
+
+		for (String item : items) {
+			setProperty("@", item);
+			try {
+				String path = getReplacer().process(destination);
+				String command = getReplacer().process(cmd);
+				File file = getFile(item);
+
+				Resource r = new CommandResource(command, this, Math.max(lastModified,
+						file.exists() ? file.lastModified() : 0L));
+
+				if (preprocess)
+					r = new PreprocessResource(this, r);
+
+				if (cr == null)
+					jar.putResource(path, r);
+				else
+					cr.addResource(r);
+			}
+			finally {
+				unsetProperty("@");
+			}
+		}
+
+		// Add last so the correct modification date is used
+		// to update the modified time.
+		if (cr != null)
+			jar.putResource(destination, cr);
+	}
+
+	private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
+			String destinationPath) throws Exception {
+		String filter = extra.get("filter:");
+		boolean flatten = isTrue(extra.get("flatten:"));
+		boolean recursive = true;
+		String directive = extra.get("recursive:");
+		if (directive != null) {
+			recursive = isTrue(directive);
+		}
+
+		Instruction.Filter iFilter = null;
+		if (filter != null) {
+			iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
+		} else {
+			iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
+		}
+
+		Map<String,File> files = newMap();
+		resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
+
+		for (Map.Entry<String,File> entry : files.entrySet()) {
+			copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
+		}
+		return destinationPath;
+	}
+
+	private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
+			boolean flatten) {
+
+		if (doNotCopy(dir.getName())) {
+			return;
+		}
+
+		File[] fs = dir.listFiles(filter);
+		for (File file : fs) {
+			if (file.isDirectory()) {
+				if (recursive) {
+					String nextPath;
+					if (flatten)
+						nextPath = path;
+					else
+						nextPath = appendPath(path, file.getName());
+
+					resolveFiles(file, filter, recursive, nextPath, files, flatten);
+				}
+				// Directories are ignored otherwise
+			} else {
+				String p = appendPath(path, file.getName());
+				if (files.containsKey(p))
+					warning("Include-Resource overwrites entry %s from file %s", p, file);
+				files.put(p, file);
+			}
+		}
+	}
+
+	private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
+			throws Exception {
+		Jar src = getJarFromName(source, "Include-Resource " + source);
+		if (src != null) {
+			// Do not touch the manifest so this also
+			// works for signed files.
+			src.setDoNotTouchManifest();
+			JarResource jarResource = new JarResource(src);
+			jar.putResource(destinationPath, jarResource);
+		} else {
+			Resource lastChance = make.process(source);
+			if (lastChance != null) {
+				String x = extra.get("extra");
+				if (x != null)
+					lastChance.setExtra(x);
+				jar.putResource(destinationPath, lastChance);
+			} else
+				error("Input file does not exist: " + source);
+		}
+	}
+
+	/**
+	 * Extra resources from a Jar and add them to the given jar. The clause is
+	 * the
+	 * 
+	 * @param jar
+	 * @param clauses
+	 * @param i
+	 * @throws ZipException
+	 * @throws IOException
+	 */
+	private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
+			IOException {
+		// Inline all resources and classes from another jar
+		// optionally appended with a modified regular expression
+		// like @zip.jar!/META-INF/MANIFEST.MF
+		int n = source.lastIndexOf("!/");
+		Instruction instr = null;
+		if (n > 0) {
+			instr = new Instruction(source.substring(n + 2));
+			source = source.substring(0, n);
+		}
+
+		// Pattern filter = null;
+		// if (n > 0) {
+		// String fstring = source.substring(n + 2);
+		// source = source.substring(0, n);
+		// filter = wildcard(fstring);
+		// }
+		Jar sub = getJarFromName(source, "extract from jar");
+		if (sub == null) {
+			if (absentIsOk)
+				return;
+
+			error("Can not find JAR file " + source);
+		} else {
+			addAll(jar, sub, instr, destination);
+		}
+	}
+
+	/**
+	 * Add all the resources in the given jar that match the given filter.
+	 * 
+	 * @param sub
+	 *            the jar
+	 * @param filter
+	 *            a pattern that should match the resoures in sub to be added
+	 */
+	public boolean addAll(Jar to, Jar sub, Instruction filter) {
+		return addAll(to, sub, filter, "");
+	}
+
+	/**
+	 * Add all the resources in the given jar that match the given filter.
+	 * 
+	 * @param sub
+	 *            the jar
+	 * @param filter
+	 *            a pattern that should match the resoures in sub to be added
+	 */
+	public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
+		boolean dupl = false;
+		for (String name : sub.getResources().keySet()) {
+			if ("META-INF/MANIFEST.MF".equals(name))
+				continue;
+
+			if (filter == null || filter.matches(name) != filter.isNegated())
+				dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
+		}
+		return dupl;
+	}
+
+	private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
+		if (doNotCopy(from.getName()))
+			return;
+
+		if (from.isDirectory()) {
+
+			File files[] = from.listFiles();
+			for (int i = 0; i < files.length; i++) {
+				copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
+			}
+		} else {
+			if (from.exists()) {
+				Resource resource = new FileResource(from);
+				if (preprocess) {
+					resource = new PreprocessResource(this, resource);
+				}
+				String x = extra.get("extra");
+				if (x != null)
+					resource.setExtra(x);
+				if (path.endsWith("/"))
+					path = path + from.getName();
+				jar.putResource(path, resource);
+
+				if (isTrue(extra.get(LIB_DIRECTIVE))) {
+					setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
+				}
+			} else {
+				error("Input file does not exist: " + from);
+			}
+		}
+	}
+
+	public void setSourcepath(File[] files) {
+		for (int i = 0; i < files.length; i++)
+			addSourcepath(files[i]);
+	}
+
+	public void addSourcepath(File cp) {
+		if (!cp.exists())
+			warning("File on sourcepath that does not exist: " + cp);
+
+		sourcePath.add(cp);
+	}
+
+	public void close() {
+		super.close();
+	}
+
+	/**
+	 * Build Multiple jars. If the -sub command is set, we filter the file with
+	 * the given patterns.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	public Jar[] builds() throws Exception {
+		begin();
+
+		// Are we acting as a conduit for another JAR?
+		String conduit = getProperty(CONDUIT);
+		if (conduit != null) {
+			Parameters map = parseHeader(conduit);
+			Jar[] result = new Jar[map.size()];
+			int n = 0;
+			for (String file : map.keySet()) {
+				Jar c = new Jar(getFile(file));
+				addClose(c);
+				String name = map.get(file).get("name");
+				if (name != null)
+					c.setName(name);
+
+				result[n++] = c;
+			}
+			return result;
+		}
+
+		List<Jar> result = new ArrayList<Jar>();
+		List<Builder> builders;
+
+		builders = getSubBuilders();
+
+		for (Builder builder : builders) {
+			try {
+				Jar jar = builder.build();
+				jar.setName(builder.getBsn());
+				result.add(jar);
+			}
+			catch (Exception e) {
+				e.printStackTrace();
+				error("Sub Building " + builder.getBsn(), e);
+			}
+			if (builder != this)
+				getInfo(builder, builder.getBsn() + ": ");
+		}
+		return result.toArray(new Jar[result.size()]);
+	}
+
+	/**
+	 * Answer a list of builders that represent this file or a list of files
+	 * specified in -sub. This list can be empty. These builders represents to
+	 * be created artifacts and are each scoped to such an artifacts. The
+	 * builders can be used to build the bundles or they can be used to find out
+	 * information about the to be generated bundles.
+	 * 
+	 * @return List of 0..n builders representing artifacts.
+	 * @throws Exception
+	 */
+	public List<Builder> getSubBuilders() throws Exception {
+		String sub = getProperty(SUB);
+		if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
+			return Arrays.asList(this);
+
+		List<Builder> builders = new ArrayList<Builder>();
+		if (isTrue(getProperty(NOBUNDLES)))
+			return builders;
+
+		Parameters subsMap = parseHeader(sub);
+		for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
+			File file = getFile(i.next());
+			if (file.isFile()) {
+				builders.add(getSubBuilder(file));
+				i.remove();
+			}
+		}
+
+		Instructions instructions = new Instructions(subsMap);
+
+		List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
+
+		nextFile: while (members.size() > 0) {
+
+			File file = members.remove(0);
+
+			// Check if the file is one of our parents
+			Processor p = this;
+			while (p != null) {
+				if (file.equals(p.getPropertiesFile()))
+					continue nextFile;
+				p = p.getParent();
+			}
+
+			for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
+
+				Instruction instruction = i.next();
+				if (instruction.matches(file.getName())) {
+
+					if (!instruction.isNegated()) {
+						builders.add(getSubBuilder(file));
+					}
+
+					// Because we matched (even though we could be negated)
+					// we skip any remaining searches
+					continue nextFile;
+				}
+			}
+		}
+		return builders;
+	}
+
+	public Builder getSubBuilder(File file) throws Exception {
+		Builder builder = getSubBuilder();
+		if (builder != null) {
+			builder.setProperties(file);
+			addClose(builder);
+		}
+		return builder;
+	}
+
+	public Builder getSubBuilder() throws Exception {
+		Builder builder = new Builder(this);
+		builder.setBase(getBase());
+
+		for (Jar file : getClasspath()) {
+			builder.addClasspath(file);
+		}
+
+		return builder;
+	}
+
+	/**
+	 * A macro to convert a maven version to an OSGi version
+	 */
+
+	public String _maven_version(String args[]) {
+		if (args.length > 2)
+			error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
+		else if (args.length < 2)
+			error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
+		else {
+			return cleanupVersion(args[1]);
+		}
+		return null;
+	}
+
+	public String _permissions(String args[]) {
+		StringBuilder sb = new StringBuilder();
+
+		for (String arg : args) {
+			if ("packages".equals(arg) || "all".equals(arg)) {
+				for (PackageRef imp : getImports().keySet()) {
+					if (!imp.isJava()) {
+						sb.append("(org.osgi.framework.PackagePermission \"");
+						sb.append(imp);
+						sb.append("\" \"import\")\r\n");
+					}
+				}
+				for (PackageRef exp : getExports().keySet()) {
+					sb.append("(org.osgi.framework.PackagePermission \"");
+					sb.append(exp);
+					sb.append("\" \"export\")\r\n");
+				}
+			} else if ("admin".equals(arg) || "all".equals(arg)) {
+				sb.append("(org.osgi.framework.AdminPermission)");
+			} else if ("permissions".equals(arg))
+				;
+			else
+				error("Invalid option in ${permissions}: %s", arg);
+		}
+		return sb.toString();
+	}
+
+	/**
+     * 
+     */
+	public void removeBundleSpecificHeaders() {
+		Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
+		setForceLocal(set);
+	}
+
+	/**
+	 * Check if the given resource is in scope of this bundle. That is, it
+	 * checks if the Include-Resource includes this resource or if it is a class
+	 * file it is on the class path and the Export-Package or Private-Package
+	 * include this resource.
+	 * 
+	 * @param f
+	 * @return
+	 */
+	public boolean isInScope(Collection<File> resources) throws Exception {
+		Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+		clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+		if (isTrue(getProperty(Constants.UNDERTEST))) {
+			clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+		}
+
+		Collection<String> ir = getIncludedResourcePrefixes();
+
+		Instructions instructions = new Instructions(clauses);
+
+		for (File r : resources) {
+			String cpEntry = getClasspathEntrySuffix(r);
+
+			if (cpEntry != null) {
+
+				if (cpEntry.equals("")) // Meaning we actually have a CPE
+					return true;
+
+				String pack = Descriptors.getPackage(cpEntry);
+				Instruction i = matches(instructions, pack, null, r.getName());
+				if (i != null)
+					return !i.isNegated();
+			}
+
+			// Check if this resource starts with one of the I-C header
+			// paths.
+			String path = r.getAbsolutePath();
+			for (String p : ir) {
+				if (path.startsWith(p))
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Extra the paths for the directories and files that are used in the
+	 * Include-Resource header.
+	 * 
+	 * @return
+	 */
+	private Collection<String> getIncludedResourcePrefixes() {
+		List<String> prefixes = new ArrayList<String>();
+		Parameters includeResource = getIncludeResource();
+		for (Entry<String,Attrs> p : includeResource.entrySet()) {
+			if (p.getValue().containsKey("literal"))
+				continue;
+
+			Matcher m = IR_PATTERN.matcher(p.getKey());
+			if (m.matches()) {
+				File f = getFile(m.group(1));
+				prefixes.add(f.getAbsolutePath());
+			}
+		}
+		return prefixes;
+	}
+
+	/**
+	 * Answer the string of the resource that it has in the container. It is
+	 * possible that the resource is a classpath entry. In that case an empty
+	 * string is returned.
+	 * 
+	 * @param resource
+	 *            The resource to look for
+	 * @return A suffix on the classpath or "" if the resource is a class path
+	 *         entry
+	 * @throws Exception
+	 */
+	public String getClasspathEntrySuffix(File resource) throws Exception {
+		for (Jar jar : getClasspath()) {
+			File source = jar.getSource();
+			if (source != null) {
+
+				source = source.getCanonicalFile();
+				String sourcePath = source.getAbsolutePath();
+				String resourcePath = resource.getAbsolutePath();
+				if (sourcePath.equals(resourcePath))
+					return ""; // Matches a classpath entry
+
+				if (resourcePath.startsWith(sourcePath)) {
+					// Make sure that the path name is translated correctly
+					// i.e. on Windows the \ must be translated to /
+					String filePath = resourcePath.substring(sourcePath.length() + 1);
+
+					return filePath.replace(File.separatorChar, '/');
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * doNotCopy The doNotCopy variable maintains a patter for files that should
+	 * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
+	 * ca be overridden with the {@link Constants#DONOTCOPY} property.
+	 */
+
+	public boolean doNotCopy(String v) {
+		return getDoNotCopy().matcher(v).matches();
+	}
+
+	public Pattern getDoNotCopy() {
+		if (xdoNotCopy == null) {
+			String string = null;
+			try {
+				string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
+				xdoNotCopy = Pattern.compile(string);
+			}
+			catch (Exception e) {
+				error("Invalid value for %s, value is %s", DONOTCOPY, string);
+				xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
+			}
+		}
+		return xdoNotCopy;
+	}
+
+	/**
+	 */
+
+	static MakeBnd			makeBnd				= new MakeBnd();
+	static MakeCopy			makeCopy			= new MakeCopy();
+	static ServiceComponent	serviceComponent	= new ServiceComponent();
+	static DSAnnotations	dsAnnotations		= new DSAnnotations();
+	static MetatypePlugin	metatypePlugin		= new MetatypePlugin();
+
+	@Override
+	protected void setTypeSpecificPlugins(Set<Object> list) {
+		list.add(makeBnd);
+		list.add(makeCopy);
+		list.add(serviceComponent);
+		list.add(dsAnnotations);
+		list.add(metatypePlugin);
+		super.setTypeSpecificPlugins(list);
+	}
+
+	/**
+	 * Diff this bundle to another bundle for the given packages.
+	 * 
+	 * @throws Exception
+	 */
+
+	public void doDiff(@SuppressWarnings("unused") Jar dot) throws Exception {
+		Parameters diffs = parseHeader(getProperty("-diff"));
+		if (diffs.isEmpty())
+			return;
+
+		trace("diff %s", diffs);
+
+		if (tree == null)
+			tree = differ.tree(this);
+
+		for (Entry<String,Attrs> entry : diffs.entrySet()) {
+			String path = entry.getKey();
+			File file = getFile(path);
+			if (!file.isFile()) {
+				error("Diffing against %s that is not a file", file);
+				continue;
+			}
+
+			boolean full = entry.getValue().get("--full") != null;
+			boolean warning = entry.getValue().get("--warning") != null;
+
+			Tree other = differ.tree(file);
+			Diff api = tree.diff(other).get("<api>");
+			Instructions instructions = new Instructions(entry.getValue().get("--pack"));
+
+			trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
+			for (Diff p : api.getChildren()) {
+				String pname = p.getName();
+				if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
+					if (p.getDelta() != Delta.UNCHANGED) {
+
+						if (!full)
+							if (warning)
+								warning("Differ %s", p);
+							else
+								error("Differ %s", p);
+						else {
+							if (warning)
+								warning("Diff found a difference in %s for packages %s", file, instructions);
+							else
+								error("Diff found a difference in %s for packages %s", file, instructions);
+							show(p, "", warning);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Show the diff recursively
+	 * 
+	 * @param p
+	 * @param i
+	 */
+	private void show(Diff p, String indent, boolean warning) {
+		Delta d = p.getDelta();
+		if (d == Delta.UNCHANGED)
+			return;
+
+		if (warning)
+			warning("%s%s", indent, p);
+		else
+			error("%s%s", indent, p);
+
+		indent = indent + " ";
+		switch (d) {
+			case CHANGED :
+			case MAJOR :
+			case MINOR :
+			case MICRO :
+				break;
+
+			default :
+				return;
+		}
+		for (Diff c : p.getChildren())
+			show(c, indent, warning);
+	}
+
+	/**
+	 * Base line against a previous version
+	 * 
+	 * @throws Exception
+	 */
+
+	private void doBaseline(Jar dot) throws Exception {
+		Parameters diffs = parseHeader(getProperty("-baseline"));
+		if (diffs.isEmpty())
+			return;
+
+		System.err.printf("baseline %s%n", diffs);
+
+		Jar other = getBaselineJar();
+		if (other == null) {
+			return;
+		}
+		Baseline baseline = new Baseline(this, differ);
+		Set<Info> infos = baseline.baseline(dot, other, null);
+		for (Info info : infos) {
+			if (info.mismatch) {
+				error("%s %-50s %-10s %-10s %-10s %-10s %-10s\n", info.mismatch ? '*' : ' ', info.packageName,
+						info.packageDiff.getDelta(), info.newerVersion, info.olderVersion, info.suggestedVersion,
+						info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
+			}
+		}
+	}
+
+	public void addSourcepath(Collection<File> sourcepath) {
+		for (File f : sourcepath) {
+			addSourcepath(f);
+		}
+	}
+
+	public Jar getBaselineJar() throws Exception {
+
+		List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
+
+		Parameters diffs = parseHeader(getProperty("-baseline"));
+		File baselineFile = null;
+		if (diffs.isEmpty()) {
+			String repoName = getProperty("-baseline-repo");
+			if (repoName == null) {
+				return null;
+			}
+			for (RepositoryPlugin repo : repos) {
+				if (repoName.equals(repo.getName())) {
+					baselineFile = repo.get(getBsn(), null, Strategy.HIGHEST, null);
+					break;
+				}
+			}
+		} else {
+
+			String bsn = null;
+			String version = null;
+			for (Entry<String,Attrs> entry : diffs.entrySet()) {
+				bsn = entry.getKey();
+				if ("@".equals(bsn)) {
+					bsn = getBsn();
+				}
+				version = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
+				break;
+			}
+	
+			for (RepositoryPlugin repo : repos) {
+				if (version == null) {
+					baselineFile = repo.get(bsn, null, Strategy.HIGHEST, null);
+				} else {
+					baselineFile = repo.get(bsn, version, Strategy.EXACT, null);
+				}
+				if (baselineFile != null) {
+					break;
+				}
+			}
+		}
+		if (baselineFile == null) {
+			return new Jar(".");
+		}
+		return new Jar(baselineFile);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/BundleId.java b/bundleplugin/src/main/java/aQute/bnd/osgi/BundleId.java
new file mode 100644
index 0000000..2d711ad
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/BundleId.java
@@ -0,0 +1,42 @@
+package aQute.bnd.osgi;
+
+/**
+ * Holds the bundle bsn + version pair
+ */
+public class BundleId implements Comparable<BundleId> {
+	final String	bsn;
+	final String	version;
+
+	public BundleId(String bsn, String version) {
+		this.bsn = bsn.trim();
+		this.version = version.trim();
+	}
+
+	public String getVersion() {
+		return version;
+	}
+
+	public String getBsn() {
+		return bsn;
+	}
+
+	public boolean isValid() {
+		return Verifier.isVersion(version) && Verifier.isBsn(bsn);
+	}
+
+	public boolean equals(Object o) {
+		return this == o || ((o instanceof BundleId) && compareTo((BundleId) o) == 0);
+	}
+
+	public int hashCode() {
+		return bsn.hashCode() ^ version.hashCode();
+	}
+
+	public int compareTo(BundleId other) {
+		int result = bsn.compareTo(other.bsn);
+		if (result != 0)
+			return result;
+
+		return version.compareTo(other.version);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java b/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java
new file mode 100644
index 0000000..c8d79c6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/ClassDataCollector.java
@@ -0,0 +1,86 @@
+package aQute.bnd.osgi;
+
+import aQute.bnd.osgi.Descriptors.TypeRef;
+
+public class ClassDataCollector {
+	public void classBegin(@SuppressWarnings("unused") int access, @SuppressWarnings("unused") TypeRef name) {}
+
+	public boolean classStart(int access, TypeRef className) {
+		classBegin(access, className);
+		return true;
+	}
+
+	public void extendsClass(@SuppressWarnings("unused") TypeRef zuper) throws Exception {}
+
+	public void implementsInterfaces(@SuppressWarnings("unused") TypeRef[] interfaces) throws Exception {}
+
+	public void addReference(@SuppressWarnings("unused") TypeRef ref) {}
+
+	public void annotation(@SuppressWarnings("unused") Annotation annotation) {}
+
+	public void parameter(@SuppressWarnings("unused") int p) {}
+
+	public void method(@SuppressWarnings("unused") Clazz.MethodDef defined) {}
+
+	public void field(@SuppressWarnings("unused") Clazz.FieldDef defined) {}
+
+	public void reference(@SuppressWarnings("unused") Clazz.MethodDef referenced) {}
+
+	public void reference(@SuppressWarnings("unused") Clazz.FieldDef referenced) {}
+
+	public void classEnd() throws Exception {}
+
+	public void deprecated() throws Exception {}
+
+	/**
+	 * The EnclosingMethod attribute
+	 * 
+	 * @param cName
+	 *            The name of the enclosing class, never null. Name is with
+	 *            slashes.
+	 * @param mName
+	 *            The name of the enclosing method in the class with cName or
+	 *            null
+	 * @param mDescriptor
+	 *            The descriptor of this type
+	 */
+	public void enclosingMethod(TypeRef cName, String mName, String mDescriptor) {
+
+	}
+
+	/**
+	 * The InnerClass attribute
+	 * 
+	 * @param innerClass
+	 *            The name of the inner class (with slashes). Can be null.
+	 * @param outerClass
+	 *            The name of the outer class (with slashes) Can be null.
+	 * @param innerName
+	 *            The name inside the outer class, can be null.
+	 * @param modifiers
+	 *            The access flags
+	 * @throws Exception
+	 */
+	public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName, @SuppressWarnings("unused") int innerClassAccessFlags)
+			throws Exception {}
+
+	public void signature(@SuppressWarnings("unused") String signature) {}
+
+	public void constant(@SuppressWarnings("unused") Object object) {}
+
+	public void memberEnd() {}
+
+	public void version(@SuppressWarnings("unused") int minor, @SuppressWarnings("unused") int major) {
+		// TODO Auto-generated method stub
+
+	}
+
+	public void referenceMethod(@SuppressWarnings("unused")
+	int access, @SuppressWarnings("unused")
+	TypeRef className, @SuppressWarnings("unused")
+	String method, @SuppressWarnings("unused") String descriptor) {
+		// TODO Auto-generated method stub
+
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java
new file mode 100755
index 0000000..dc8784c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Clazz.java
@@ -0,0 +1,1631 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.nio.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.osgi.Descriptors.Descriptor;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.libg.generics.*;
+
+public class Clazz {
+
+	static Pattern	METHOD_DESCRIPTOR	= Pattern.compile("\\((.*)\\)(.+)");
+
+	public class ClassConstant {
+		int	cname;
+
+		public ClassConstant(int class_index) {
+			this.cname = class_index;
+		}
+
+		public String getName() {
+			return (String) pool[cname];
+		}
+	}
+
+	public static enum JAVA {
+		JDK1_1(45, "JRE-1.1"), JDK1_2(46, "J2SE-1.2"), //
+		JDK1_3(47, "J2SE-1.3"), //
+		JDK1_4(48, "J2SE-1.4"), //
+		J2SE5(49, "J2SE-1.5"), //
+		J2SE6(50, "JavaSE-1.6"), //
+		OpenJDK7(51, "JavaSE-1.7"), //
+		UNKNOWN(Integer.MAX_VALUE, "<>")//
+		;
+
+		final int		major;
+		final String	ee;
+
+		JAVA(int major, String ee) {
+			this.major = major;
+			this.ee = ee;
+		}
+
+		static JAVA format(int n) {
+			for (JAVA e : JAVA.values())
+				if (e.major == n)
+					return e;
+			return UNKNOWN;
+		}
+
+		public int getMajor() {
+			return major;
+		}
+
+		public boolean hasAnnotations() {
+			return major >= J2SE5.major;
+		}
+
+		public boolean hasGenerics() {
+			return major >= J2SE5.major;
+		}
+
+		public boolean hasEnums() {
+			return major >= J2SE5.major;
+		}
+
+		public static JAVA getJava(int major, @SuppressWarnings("unused") int minor) {
+			for (JAVA j : JAVA.values()) {
+				if (j.major == major)
+					return j;
+			}
+			return UNKNOWN;
+		}
+
+		public String getEE() {
+			return ee;
+		}
+	}
+
+	public static enum QUERY {
+		IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATED, RUNTIMEANNOTATIONS, CLASSANNOTATIONS;
+
+	}
+
+	public final static EnumSet<QUERY>	HAS_ARGUMENT	= EnumSet.of(QUERY.IMPLEMENTS, QUERY.EXTENDS, QUERY.IMPORTS,
+																QUERY.NAMED, QUERY.VERSION, QUERY.ANNOTATED);
+
+	/**
+	 * <pre>
+	 * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its
+	 * package. 
+	 * ACC_FINAL 0x0010 Declared final; no subclasses allowed.
+	 * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
+	 * invokespecial instruction. 
+	 * ACC_INTERFACE 0x0200 Is an interface, not a
+	 * class. 
+	 * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated.
+	 * </pre>
+	 * 
+	 * @param mod
+	 */
+	final static int					ACC_PUBLIC		= 0x0001;												// Declared
+	// public;
+	// may
+	// be
+	// accessed
+	// from outside its package.
+	final static int					ACC_FINAL		= 0x0010;												// Declared
+	// final;
+	// no
+	// subclasses
+	// allowed.
+	final static int					ACC_SUPER		= 0x0020;												// Treat
+	// superclass
+	// methods
+	// specially when invoked by the
+	// invokespecial instruction.
+	final static int					ACC_INTERFACE	= 0x0200;												// Is
+	// an
+	// interface,
+	// not
+	// a
+	// classs
+	final static int					ACC_ABSTRACT	= 0x0400;												// Declared
+
+	// a thing not in the source code
+	final static int					ACC_SYNTHETIC	= 0x1000;
+	final static int					ACC_ANNOTATION	= 0x2000;
+	final static int					ACC_ENUM		= 0x4000;
+
+	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;
+	}
+
+	public class Def {
+		final int		access;
+		Set<TypeRef>	annotations;
+
+		public Def(int access) {
+			this.access = access;
+		}
+
+		public int getAccess() {
+			return access;
+		}
+
+		public boolean isEnum() {
+			return (access & ACC_ENUM) != 0;
+		}
+
+		public boolean isPublic() {
+			return Modifier.isPublic(access);
+		}
+
+		public boolean isAbstract() {
+			return Modifier.isAbstract(access);
+		}
+
+		public boolean isProtected() {
+			return Modifier.isProtected(access);
+		}
+
+		public boolean isFinal() {
+			return Modifier.isFinal(access) || Clazz.this.isFinal();
+		}
+
+		public boolean isStatic() {
+			return Modifier.isStatic(access);
+		}
+
+		public boolean isPrivate() {
+			return Modifier.isPrivate(access);
+		}
+
+		public boolean isNative() {
+			return Modifier.isNative(access);
+		}
+
+		public boolean isTransient() {
+			return Modifier.isTransient(access);
+		}
+
+		public boolean isVolatile() {
+			return Modifier.isVolatile(access);
+		}
+
+		public boolean isInterface() {
+			return Modifier.isInterface(access);
+		}
+
+		public boolean isSynthetic() {
+			return (access & ACC_SYNTHETIC) != 0;
+		}
+
+		void addAnnotation(Annotation a) {
+			if (annotations == null)
+				annotations = Create.set();
+			annotations.add(analyzer.getTypeRef(a.name.getBinary()));
+		}
+
+		public Collection<TypeRef> getAnnotations() {
+			return annotations;
+		}
+	}
+
+	public class FieldDef extends Def {
+		final String		name;
+		final Descriptor	descriptor;
+		String				signature;
+		Object				constant;
+		boolean				deprecated;
+
+		public boolean isDeprecated() {
+			return deprecated;
+		}
+
+		public void setDeprecated(boolean deprecated) {
+			this.deprecated = deprecated;
+		}
+
+		public FieldDef(int access, String name, String descriptor) {
+			super(access);
+			this.name = name;
+			this.descriptor = analyzer.getDescriptor(descriptor);
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String toString() {
+			return getName();
+		}
+
+		public TypeRef getType() {
+			return descriptor.getType();
+		}
+
+		public TypeRef getContainingClass() {
+			return getClassName();
+		}
+
+		public Descriptor getDescriptor() {
+			return descriptor;
+		}
+
+		public void setConstant(Object o) {
+			this.constant = o;
+		}
+
+		public Object getConstant() {
+			return this.constant;
+		}
+
+		// TODO change to use proper generics
+		public String getGenericReturnType() {
+			String use = descriptor.toString();
+			if (signature != null)
+				use = signature;
+
+			Matcher m = METHOD_DESCRIPTOR.matcher(use);
+			if (!m.matches())
+				throw new IllegalArgumentException("Not a valid method descriptor: " + descriptor);
+
+			String returnType = m.group(2);
+			return objectDescriptorToFQN(returnType);
+		}
+
+		public String getSignature() {
+			return signature;
+		}
+
+	}
+
+	public class MethodDef extends FieldDef {
+		public MethodDef(int access, String method, String descriptor) {
+			super(access, method, descriptor);
+		}
+
+		public boolean isConstructor() {
+			return name.equals("<init>") || name.equals("<clinit>");
+		}
+
+		public TypeRef[] getPrototype() {
+			return descriptor.getPrototype();
+		}
+
+	}
+
+	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
+			-1, // 13 Not defined
+			-1, // 14 Not defined
+			3, // 15 CONSTANT_MethodHandle
+			2, // 16 CONSTANT_MethodType
+			-1, // 17 Not defined
+			4, // 18 CONSTANT_InvokeDynamic
+									};
+
+	boolean				hasRuntimeAnnotations;
+	boolean				hasClassAnnotations;
+
+	TypeRef				className;
+	Object				pool[];
+	int					intPool[];
+	Set<PackageRef>		imports		= Create.set();
+	String				path;
+	int					minor		= 0;
+	int					major		= 0;
+	int					innerAccess	= -1;
+	int					accessx		= 0;
+	String				sourceFile;
+	Set<TypeRef>		xref;
+	Set<TypeRef>		annotations;
+	int					forName		= 0;
+	int					class$		= 0;
+	TypeRef[]			interfaces;
+	TypeRef				zuper;
+	ClassDataCollector	cd			= null;
+	Resource			resource;
+	FieldDef			last		= null;
+	boolean				deprecated;
+	Set<PackageRef>		api;
+	final Analyzer		analyzer;
+
+
+	public Clazz(Analyzer analyzer, String path, Resource resource) {
+		this.path = path;
+		this.resource = resource;
+		this.analyzer = analyzer;
+	}
+
+	public Set<TypeRef> parseClassFile() throws Exception {
+		return parseClassFileWithCollector(null);
+	}
+
+	public Set<TypeRef> parseClassFile(InputStream in) throws Exception {
+		return parseClassFile(in, null);
+	}
+
+	public Set<TypeRef> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
+		InputStream in = resource.openInputStream();
+		try {
+			return parseClassFile(in, cd);
+		}
+		finally {
+			in.close();
+		}
+	}
+
+	public Set<TypeRef> parseClassFile(InputStream in, ClassDataCollector cd) throws Exception {
+		DataInputStream din = new DataInputStream(in);
+		try {
+			this.cd = cd;
+			return parseClassFile(din);
+		}
+		finally {
+			cd = null;
+			din.close();
+		}
+	}
+
+	Set<TypeRef> parseClassFile(DataInputStream in) throws Exception {
+		xref = new HashSet<TypeRef>();
+		
+		boolean crawl = cd != null; // Crawl the byte code if we have a
+		// collector
+		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
+		if (cd != null)
+			cd.version(minor, major);
+		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;
+
+				case 3 :
+					constantInteger(in, poolIndex);
+					break;
+
+				case 4 :
+					constantFloat(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
+				case 11 : // Interface 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);
+		
+		// All name& type and class constant records contain descriptors we must treat
+		// as references, though not API
+		
+		for ( Object o : pool ) {
+			if ( o == null)
+				continue;
+			
+			if (o instanceof Assoc && ((Assoc)o).tag ==12 ) {
+				referTo( ((Assoc)o).b, 0); // Descriptor
+			} else if ( o instanceof ClassConstant) {
+				String binaryClassName = (String) pool[((ClassConstant)o).cname];
+				TypeRef typeRef = analyzer.getTypeRef(binaryClassName);
+				referTo( typeRef, 0);
+			}
+		}
+		
+		/*
+		 * Parse after the constant pool, code thanks to Hans Christian
+		 * Falkenberg
+		 */
+
+		accessx = in.readUnsignedShort(); // access
+		if ( Modifier.isPublic(accessx))
+			api = new HashSet<PackageRef>();
+		
+		int this_class = in.readUnsignedShort();
+		className = analyzer.getTypeRef((String) pool[intPool[this_class]]);
+		referTo(className, Modifier.PUBLIC);
+		
+		try {
+
+			if (cd != null) {
+				if (!cd.classStart(accessx, className))
+					return null;
+			}
+
+			int super_class = in.readUnsignedShort();
+			String superName = (String) pool[intPool[super_class]];
+			if (superName != null) {
+				zuper = analyzer.getTypeRef(superName);
+			}
+
+			if (zuper != null) {
+				referTo(zuper, accessx);
+				if (cd != null)
+					cd.extendsClass(zuper);
+			}
+
+			int interfacesCount = in.readUnsignedShort();
+			if (interfacesCount > 0) {
+				interfaces = new TypeRef[interfacesCount];
+				for (int i = 0; i < interfacesCount; i++) {
+					interfaces[i] = analyzer.getTypeRef((String) pool[intPool[in.readUnsignedShort()]]);
+					referTo(interfaces[i],accessx);
+				}
+				if (cd != null)
+					cd.implementsInterfaces(interfaces);
+			}
+
+			int fieldsCount = in.readUnsignedShort();
+			for (int i = 0; i < fieldsCount; i++) {
+				int 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;
+				}
+				if (cd != null)
+					cd.field(last = new FieldDef(access_flags, name, pool[descriptor_index].toString()));
+				
+				referTo( descriptor_index, access_flags);
+				doAttributes(in, ElementType.FIELD, false, access_flags);
+			}
+
+			//
+			// 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 = findMethodReference("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
+				class$ = findMethodReference(className.getBinary(), "class$", "(Ljava/lang/String;)Ljava/lang/Class;");
+			} else if (major == 48) {
+				forName = findMethodReference("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
+				if (forName > 0) {
+					crawl = true;
+					class$ = findMethodReference(className.getBinary(), "class$",
+							"(Ljava/lang/String;)Ljava/lang/Class;");
+				}
+			}
+
+			// There are some serious changes in the
+			// class file format. So we do not do any crawling
+			// it has also become less important
+			if (major >= JAVA.OpenJDK7.major)
+				crawl = false;
+
+			//
+			// Handle the methods
+			//
+			int methodCount = in.readUnsignedShort();
+			for (int i = 0; i < methodCount; i++) {
+				int access_flags = in.readUnsignedShort();
+				int name_index = in.readUnsignedShort();
+				int descriptor_index = in.readUnsignedShort();
+				referTo(descriptor_index, access_flags);
+				String name = pool[name_index].toString();
+				String descriptor = pool[descriptor_index].toString();
+				if (cd != null) {
+					MethodDef mdef = new MethodDef(access_flags, name, descriptor);
+					last = mdef;
+					cd.method(mdef);
+				}
+
+				if ("<init>".equals(name)) {
+					doAttributes(in, ElementType.CONSTRUCTOR, crawl, access_flags);
+				} else {
+					doAttributes(in, ElementType.METHOD, crawl, access_flags);
+				}
+			}
+			if (cd != null)
+				cd.memberEnd();
+
+			doAttributes(in, ElementType.TYPE, false, accessx);
+
+			//
+			// Parse all the descriptors we found
+			//
+
+			Set<TypeRef> xref = this.xref;
+			reset();
+			return xref;
+		}
+		finally {
+			if (cd != null)
+				cd.classEnd();
+		}
+	}
+
+	private void constantFloat(DataInputStream in, int poolIndex) throws IOException {
+		if (cd != null)
+			pool[poolIndex] = in.readFloat(); // ALU
+		else
+			in.skipBytes(4);
+	}
+
+	private void constantInteger(DataInputStream in, int poolIndex) throws IOException {
+		intPool[poolIndex] = in.readInt();
+		if (cd != null)
+			pool[poolIndex] = intPool[poolIndex];
+	}
+
+	protected void pool(@SuppressWarnings("unused") Object[] pool, @SuppressWarnings("unused") 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();
+		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();
+		intPool[poolIndex] = class_index;
+		ClassConstant c = new ClassConstant(class_index);
+		pool[poolIndex] = c;
+	}
+
+	/**
+	 * @param in
+	 * @throws IOException
+	 */
+	protected void constantDouble(DataInputStream in, int poolIndex) throws IOException {
+		if (cd != null)
+			pool[poolIndex] = in.readDouble();
+		else
+			in.skipBytes(8);
+	}
+
+	/**
+	 * @param in
+	 * @throws IOException
+	 */
+	protected void constantLong(DataInputStream in, int poolIndex) throws IOException {
+		if (cd != null) {
+			pool[poolIndex] = in.readLong();
+		} else
+			in.skipBytes(8);
+	}
+
+	/**
+	 * @param in
+	 * @param poolIndex
+	 * @throws IOException
+	 */
+	protected void constantUtf8(DataInputStream in, int poolIndex) throws IOException {
+		// CONSTANT_Utf8
+
+		String name = in.readUTF();
+		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 findMethodReference(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;
+	}
+
+	/**
+	 * Called for each attribute in the class, field, or method.
+	 * 
+	 * @param in
+	 *            The stream
+	 * @param access_flags 
+	 * @throws Exception
+	 */
+	private void doAttributes(DataInputStream in, ElementType member, boolean crawl, int access_flags) throws Exception {
+		int attributesCount = in.readUnsignedShort();
+		for (int j = 0; j < attributesCount; j++) {
+			// skip name CONSTANT_Utf8 pointer
+			doAttribute(in, member, crawl, access_flags);
+		}
+	}
+
+	/**
+	 * Process a single attribute, if not recognized, skip it.
+	 * 
+	 * @param in
+	 *            the data stream
+	 * @param access_flags 
+	 * @throws Exception
+	 */
+	private void doAttribute(DataInputStream in, ElementType member, boolean crawl, int access_flags) throws Exception {
+		int attribute_name_index = in.readUnsignedShort();
+		String attributeName = (String) pool[attribute_name_index];
+		long attribute_length = in.readInt();
+		attribute_length &= 0xFFFFFFFF;
+		if ("Deprecated".equals(attributeName)) {
+			if (cd != null)
+				cd.deprecated();
+		} else if ("RuntimeVisibleAnnotations".equals(attributeName))
+			doAnnotations(in, member, RetentionPolicy.RUNTIME);
+		else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
+			doParameterAnnotations(in, member, RetentionPolicy.RUNTIME);
+		else if ("RuntimeInvisibleAnnotations".equals(attributeName))
+			doAnnotations(in, member, RetentionPolicy.CLASS);
+		else if ("RuntimeInvisibleParameterAnnotations".equals(attributeName))
+			doParameterAnnotations(in, member, RetentionPolicy.CLASS);
+		else if ("InnerClasses".equals(attributeName))
+			doInnerClasses(in);
+		else if ("EnclosingMethod".equals(attributeName))
+			doEnclosingMethod(in);
+		else if ("SourceFile".equals(attributeName))
+			doSourceFile(in);
+		else if ("Code".equals(attributeName) && crawl)
+			doCode(in);
+		else if ("Signature".equals(attributeName))
+			doSignature(in, member, access_flags);
+		else if ("ConstantValue".equals(attributeName))
+			doConstantValue(in);
+		else {
+			if (attribute_length > 0x7FFFFFFF) {
+				throw new IllegalArgumentException("Attribute > 2Gb");
+			}
+			in.skipBytes((int) attribute_length);
+		}
+	}
+
+	/**
+	 * <pre>
+	 * EnclosingMethod_attribute { 
+	 * 	u2 attribute_name_index; 
+	 * 	u4 attribute_length; 
+	 * 	u2 class_index
+	 * 	u2 method_index;
+	 * }
+	 * </pre>
+	 * 
+	 * @param in
+	 * @throws IOException
+	 */
+	private void doEnclosingMethod(DataInputStream in) throws IOException {
+		int cIndex = in.readShort();
+		int mIndex = in.readShort();
+
+		if (cd != null) {
+			int nameIndex = intPool[cIndex];
+			TypeRef cName = analyzer.getTypeRef((String) pool[nameIndex]);
+
+			String mName = null;
+			String mDescriptor = null;
+
+			if (mIndex != 0) {
+				Assoc nameAndType = (Assoc) pool[mIndex];
+				mName = (String) pool[nameAndType.a];
+				mDescriptor = (String) pool[nameAndType.b];
+			}
+			cd.enclosingMethod(cName, mName, mDescriptor);
+		}
+	}
+
+	/**
+	 * <pre>
+	 * InnerClasses_attribute {
+	 * 	u2 attribute_name_index; 
+	 * 	u4 attribute_length; 
+	 * 	u2 number_of_classes; {	
+	 * 		u2 inner_class_info_index;
+	 * 		u2 outer_class_info_index; 
+	 * 		u2 inner_name_index; 
+	 * 		u2 inner_class_access_flags;
+	 * 	} classes[number_of_classes];
+	 * }
+	 * </pre>
+	 * 
+	 * @param in
+	 * @throws Exception
+	 */
+	private void doInnerClasses(DataInputStream in) throws Exception {
+		int number_of_classes = in.readShort();
+		for (int i = 0; i < number_of_classes; i++) {
+			int inner_class_info_index = in.readShort();
+			int outer_class_info_index = in.readShort();
+			int inner_name_index = in.readShort();
+			int inner_class_access_flags = in.readShort() & 0xFFFF;
+
+			if (cd != null) {
+				TypeRef innerClass = null;
+				TypeRef outerClass = null;
+				String innerName = null;
+
+				if (inner_class_info_index != 0) {
+					int nameIndex = intPool[inner_class_info_index];
+					innerClass = analyzer.getTypeRef((String) pool[nameIndex]);
+				}
+
+				if (outer_class_info_index != 0) {
+					int nameIndex = intPool[outer_class_info_index];
+					outerClass = analyzer.getTypeRef((String) pool[nameIndex]);
+				}
+
+				if (inner_name_index != 0)
+					innerName = (String) pool[inner_name_index];
+
+				cd.innerClass(innerClass, outerClass, innerName, inner_class_access_flags);
+			}
+		}
+	}
+
+	/**
+	 * Handle a signature
+	 * 
+	 * <pre>
+	 * Signature_attribute { 
+	 *     u2 attribute_name_index; 
+	 *     u4 attribute_length; 
+	 *     u2 signature_index; 
+	 *     }
+	 * </pre>
+	 * 
+	 * @param member
+	 * @param access_flags 
+	 */
+
+	void doSignature(DataInputStream in, ElementType member, int access_flags) throws IOException {
+		int signature_index = in.readUnsignedShort();
+		String signature = (String) pool[signature_index];
+
+		parseDescriptor(signature, access_flags);
+
+		if (last != null)
+			last.signature = signature;
+
+		if (cd != null)
+			cd.signature(signature);
+	}
+
+	/**
+	 * Handle a constant value call the data collector with it
+	 */
+	void doConstantValue(DataInputStream in) throws IOException {
+		int constantValue_index = in.readUnsignedShort();
+		if (cd == null)
+			return;
+
+		Object object = pool[constantValue_index];
+		if (object == null)
+			object = pool[intPool[constantValue_index]];
+
+		last.constant = object;
+		cd.constant(object);
+	}
+
+	/**
+	 * <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 Exception
+	 */
+	private void doCode(DataInputStream in) throws Exception {
+		/* 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, ElementType.METHOD, false, 0);
+	}
+
+	/**
+	 * 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.invokespecial : {
+					int mref = 0xFFFF & bb.getShort();
+					if (cd != null)
+						getMethodDef(0, mref);
+					break;
+				}
+
+				case OpCodes.invokevirtual : {
+					int mref = 0xFFFF & bb.getShort();
+					if (cd != null)
+						getMethodDef(0, mref);
+					break;
+				}
+
+				case OpCodes.invokeinterface : {
+					int mref = 0xFFFF & bb.getShort();
+					if (cd != null)
+						getMethodDef(0, mref);
+					break;
+				}
+
+				case OpCodes.invokestatic : {
+					int methodref = 0xFFFF & bb.getShort();
+					if (cd != null)
+						getMethodDef(0, methodref);
+
+					if ((methodref == forName || methodref == class$) && lastReference != -1
+							&& pool[intPool[lastReference]] instanceof String) {
+						String fqn = (String) pool[intPool[lastReference]];
+						if (!fqn.equals("class") && fqn.indexOf('.') > 0) {
+							TypeRef clazz = analyzer.getTypeRefFromFQN(fqn);
+							referTo(clazz, 0);
+						}
+						lastReference = -1;
+					}
+					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();
+					try {
+						bb.position(bb.position() + (high - low + 1) * 4);
+					}
+					catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+					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, ElementType member, RetentionPolicy policy)
+			throws IOException {
+		int num_parameters = in.readUnsignedByte();
+		for (int p = 0; p < num_parameters; p++) {
+			if (cd != null)
+				cd.parameter(p);
+			doAnnotations(in, member, policy);
+		}
+	}
+
+	private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy) throws IOException {
+		int num_annotations = in.readUnsignedShort(); // # of annotations
+		for (int a = 0; a < num_annotations; a++) {
+			if (cd == null)
+				doAnnotation(in, member, policy, false);
+			else {
+				Annotation annotion = doAnnotation(in, member, policy, true);
+				cd.annotation(annotion);
+			}
+		}
+	}
+
+	private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect)
+			throws IOException {
+		int type_index = in.readUnsignedShort();
+		if (annotations == null)
+			annotations = new HashSet<TypeRef>();
+
+		TypeRef tr = analyzer.getTypeRef(pool[type_index].toString());
+		annotations.add(tr);
+
+		if (policy == RetentionPolicy.RUNTIME) {
+			referTo(type_index,0);
+			hasRuntimeAnnotations = true;
+		} else {
+			hasClassAnnotations = true;
+		}
+		TypeRef name = analyzer.getTypeRef((String) pool[type_index]);
+		int num_element_value_pairs = in.readUnsignedShort();
+		Map<String,Object> elements = null;
+		for (int v = 0; v < num_element_value_pairs; v++) {
+			int element_name_index = in.readUnsignedShort();
+			String element = (String) pool[element_name_index];
+			Object value = doElementValue(in, member, policy, collect);
+			if (collect) {
+				if (elements == null)
+					elements = new LinkedHashMap<String,Object>();
+				elements.put(element, value);
+			}
+		}
+		if (collect)
+			return new Annotation(name, elements, member, policy);
+		return null;
+	}
+
+	private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy, boolean collect)
+			throws IOException {
+		char tag = (char) in.readUnsignedByte();
+		switch (tag) {
+			case 'B' : // Byte
+			case 'C' : // Character
+			case 'I' : // Integer
+			case 'S' : // Short
+				int const_value_index = in.readUnsignedShort();
+				return intPool[const_value_index];
+
+			case 'D' : // Double
+			case 'F' : // Float
+			case 's' : // String
+			case 'J' : // Long
+				const_value_index = in.readUnsignedShort();
+				return pool[const_value_index];
+
+			case 'Z' : // Boolean
+				const_value_index = in.readUnsignedShort();
+				return pool[const_value_index] == null || pool[const_value_index].equals(0) ? false : true;
+
+			case 'e' : // enum constant
+				int type_name_index = in.readUnsignedShort();
+				if (policy == RetentionPolicy.RUNTIME)
+					referTo(type_name_index,0);
+				int const_name_index = in.readUnsignedShort();
+				return pool[const_name_index];
+
+			case 'c' : // Class
+				int class_info_index = in.readUnsignedShort();
+				if (policy == RetentionPolicy.RUNTIME)
+					referTo(class_info_index,0);
+				return pool[class_info_index];
+
+			case '@' : // Annotation type
+				return doAnnotation(in, member, policy, collect);
+
+			case '[' : // Array
+				int num_values = in.readUnsignedShort();
+				Object[] result = new Object[num_values];
+				for (int i = 0; i < num_values; i++) {
+					result[i] = doElementValue(in, member, policy, collect);
+				}
+				return result;
+
+			default :
+				throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag " + tag);
+		}
+	}
+
+	/**
+	 * Add a new package reference.
+	 * 
+	 * @param packageRef
+	 *            A '.' delimited package name
+	 */
+	void referTo(TypeRef typeRef, int modifiers) {
+		if (xref != null)
+			xref.add(typeRef);
+		if (typeRef.isPrimitive())
+			return;
+
+		PackageRef packageRef = typeRef.getPackageRef();
+		if (packageRef.isPrimitivePackage())
+			return;
+
+		imports.add(packageRef);
+		
+		if ( api != null && (Modifier.isPublic(modifiers)||Modifier.isProtected(modifiers)))
+			api.add(packageRef);
+	}
+	
+	void referTo( int index, int modifiers) {
+		String descriptor = (String) pool[index];
+		parseDescriptor(descriptor, modifiers);
+	}
+
+	/**
+	 * This method parses a descriptor and adds the package of the descriptor to
+	 * the referenced packages. The syntax of the descriptor is:
+	 * 
+	 * <pre>
+	 *   descriptor ::= ( '(' reference * ')' )? reference
+	 *   reference  ::= 'L' classname ( '&lt;' references '&gt;' )? ';' | 'B' | 'Z' | ... | '+' | '-' | '['
+	 * </pre>
+	 * 
+	 * This methods uses heavy recursion to parse the descriptor and a roving
+	 * pointer to limit the creation of string objects.
+	 * 
+	 * @param descriptor
+	 *            The to be parsed descriptor
+	 * @param rover
+	 *            The pointer to start at
+	 */
+	
+	public void parseDescriptor(String descriptor, int modifiers) {
+		// Some descriptors are weird, they start with a generic
+		// declaration that contains ':', not sure what they mean ...
+		int rover = 0;
+		if (descriptor.charAt(0) == '<') {
+			rover = parseFormalTypeParameters(descriptor, rover, modifiers);
+		}
+
+		if (descriptor.charAt(rover) == '(') {
+			rover = parseReferences(descriptor, rover + 1, ')', modifiers);
+			rover++;
+		}
+		parseReferences(descriptor, rover, (char) 0, modifiers);
+	}
+
+	/**
+	 * Parse a sequence of references. A sequence ends with a given character or
+	 * when the string ends.
+	 * 
+	 * @param descriptor
+	 *            The whole descriptor.
+	 * @param rover
+	 *            The index in the descriptor
+	 * @param delimiter
+	 *            The end character or 0
+	 * @return the last index processed, one character after the delimeter
+	 */
+	int parseReferences(String descriptor, int rover, char delimiter, int modifiers) {
+		int r = rover;
+		while (r < descriptor.length() && descriptor.charAt(r) != delimiter) {
+			r = parseReference(descriptor, r, modifiers);
+		}
+		return r;
+	}
+
+	/**
+	 * Parse a single reference. This can be a single character or an object
+	 * reference when it starts with 'L'.
+	 * 
+	 * @param descriptor
+	 *            The descriptor
+	 * @param rover
+	 *            The place to start
+	 * @return The return index after the reference
+	 */
+	int parseReference(String descriptor, int rover, int modifiers) {
+		int r = rover;
+		char c = descriptor.charAt(r);
+		while (c == '[')
+			c = descriptor.charAt(++r);
+
+		if (c == '<') {
+			r = parseReferences(descriptor, r + 1, '>', modifiers);
+		} else if (c == 'T') {
+			// Type variable name
+			r++;
+			while (descriptor.charAt(r) != ';')
+				r++;
+		} else if (c == 'L') {
+			StringBuilder sb = new StringBuilder();
+			r++;
+			while ((c = descriptor.charAt(r)) != ';') {
+				if (c == '<') {
+					r = parseReferences(descriptor, r + 1, '>', modifiers);
+				} else
+					sb.append(c);
+				r++;
+			}
+			TypeRef ref = analyzer.getTypeRef(sb.toString());
+			if (cd != null)
+				cd.addReference(ref);
+
+			referTo(ref, modifiers);
+		} else {
+			if ("+-*BCDFIJSZV".indexOf(c) < 0)
+				;// System.err.println("Should not skip: " + c);
+		}
+
+		// this skips a lot of characters
+		// [, *, +, -, B, etc.
+
+		return r + 1;
+	}
+
+	/**
+	 * FormalTypeParameters
+	 * 
+	 * @param descriptor
+	 * @param index
+	 * @return
+	 */
+	private int parseFormalTypeParameters(String descriptor, int index, int modifiers) {
+		index++;
+		while (descriptor.charAt(index) != '>') {
+			// Skip IDENTIFIER
+			index = descriptor.indexOf(':', index) + 1;
+			if (index == 0)
+				throw new IllegalArgumentException("Expected IDENTIFIER: " + descriptor);
+
+			// ClassBound? InterfaceBounds
+
+			char c = descriptor.charAt(index);
+
+			// Class Bound?
+			if (c == 'L' || c == 'T') {
+				index = parseReference(descriptor, index, modifiers); // class reference
+				c = descriptor.charAt(index);
+			}
+
+			// Interface Bounds
+			while (c == ':') {
+				index++;
+				index = parseReference(descriptor, index, modifiers);
+				c = descriptor.charAt(index);
+			} // for each interface
+
+		} // for each formal parameter
+		return index + 1; // skip >
+	}
+
+	public Set<PackageRef> getReferred() {
+		return imports;
+	}
+
+	public String getAbsolutePath() {
+		return path;
+	}
+
+	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;
+	}
+
+	public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
+		switch (query) {
+			case ANY :
+				return true;
+
+			case NAMED :
+				if (instr.matches(getClassName().getDottedOnly()))
+					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].getDottedOnly()))
+						return !instr.isNegated();
+				}
+				break;
+
+			case EXTENDS :
+				if (zuper == null)
+					return false;
+
+				if (instr.matches(zuper.getDottedOnly()))
+					return !instr.isNegated();
+				break;
+
+			case PUBLIC :
+				return Modifier.isPublic(accessx);
+
+			case CONCRETE :
+				return !Modifier.isAbstract(accessx);
+
+			case ANNOTATED :
+				if (annotations == null)
+					return false;
+
+				for (TypeRef annotation : annotations) {
+					if (instr.matches(annotation.getFQN()))
+						return !instr.isNegated();
+				}
+
+				return false;
+
+			case RUNTIMEANNOTATIONS :
+				return hasClassAnnotations;
+			case CLASSANNOTATIONS :
+				return hasClassAnnotations;
+
+			case ABSTRACT :
+				return Modifier.isAbstract(accessx);
+
+			case IMPORTS :
+				for (PackageRef imp : imports) {
+					if (instr.matches(imp.getFQN()))
+						return !instr.isNegated();
+				}
+		}
+
+		if (zuper == null)
+			return false;
+
+		Clazz clazz = analyzer.findClass(zuper);
+		if (clazz == null)
+			return false;
+
+		return clazz.is(query, instr, analyzer);
+	}
+
+	public String toString() {
+		return className.getFQN();
+	}
+
+	/**
+	 * Called when crawling the byte code and a method reference is found
+	 */
+	void getMethodDef(int access, int methodRefPoolIndex) {
+		if (methodRefPoolIndex == 0)
+			return;
+
+		Object o = pool[methodRefPoolIndex];
+		if (o != null && o instanceof Assoc) {
+			Assoc assoc = (Assoc) o;
+			if (assoc.tag == 10) {
+				int string_index = intPool[assoc.a];
+				TypeRef className = analyzer.getTypeRef((String) pool[string_index]);
+				int name_and_type_index = assoc.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;
+					String method = (String) pool[name_index];
+					String descriptor = (String) pool[type_index];
+					cd.referenceMethod(access, className, method, descriptor);
+				} else
+					throw new IllegalArgumentException(
+							"Invalid class file (or parsing is wrong), assoc is not type + name (12)");
+			} else
+				throw new IllegalArgumentException(
+						"Invalid class file (or parsing is wrong), Assoc is not method ref! (10)");
+		} else
+			throw new IllegalArgumentException("Invalid class file (or parsing is wrong), Not an assoc at a method ref");
+	}
+
+	public boolean isPublic() {
+		return Modifier.isPublic(accessx);
+	}
+
+	public boolean isProtected() {
+		return Modifier.isProtected(accessx);
+	}
+
+	public boolean isEnum() {
+		return zuper != null && zuper.getBinary().equals("java/lang/Enum");
+	}
+
+	public JAVA getFormat() {
+		return JAVA.format(major);
+
+	}
+
+	public static String objectDescriptorToFQN(String string) {
+		if (string.startsWith("L") && string.endsWith(";"))
+			return string.substring(1, string.length() - 1).replace('/', '.');
+
+		switch (string.charAt(0)) {
+			case 'V' :
+				return "void";
+			case 'B' :
+				return "byte";
+			case 'C' :
+				return "char";
+			case 'I' :
+				return "int";
+			case 'S' :
+				return "short";
+			case 'D' :
+				return "double";
+			case 'F' :
+				return "float";
+			case 'J' :
+				return "long";
+			case 'Z' :
+				return "boolean";
+			case '[' : // Array
+				return objectDescriptorToFQN(string.substring(1)) + "[]";
+		}
+		throw new IllegalArgumentException("Invalid type character in descriptor " + string);
+	}
+
+	public static String unCamel(String id) {
+		StringBuilder out = new StringBuilder();
+		for (int i = 0; i < id.length(); i++) {
+			char c = id.charAt(i);
+			if (c == '_' || c == '$' || c == '.') {
+				if (out.length() > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
+					out.append(' ');
+				continue;
+			}
+
+			int n = i;
+			while (n < id.length() && Character.isUpperCase(id.charAt(n))) {
+				n++;
+			}
+			if (n == i)
+				out.append(id.charAt(i));
+			else {
+				boolean tolower = (n - i) == 1;
+				if (i > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
+					out.append(' ');
+
+				for (; i < n;) {
+					if (tolower)
+						out.append(Character.toLowerCase(id.charAt(i)));
+					else
+						out.append(id.charAt(i));
+					i++;
+				}
+				i--;
+			}
+		}
+		if (id.startsWith("."))
+			out.append(" *");
+		out.replace(0, 1, Character.toUpperCase(out.charAt(0)) + "");
+		return out.toString();
+	}
+
+	public boolean isInterface() {
+		return Modifier.isInterface(accessx);
+	}
+
+	public boolean isAbstract() {
+		return Modifier.isAbstract(accessx);
+	}
+
+	public int getAccess() {
+		if (innerAccess == -1)
+			return accessx;
+		return innerAccess;
+	}
+
+	public TypeRef getClassName() {
+		return className;
+	}
+
+	/**
+	 * To provide an enclosing instance
+	 * 
+	 * @param access
+	 * @param name
+	 * @param descriptor
+	 * @return
+	 */
+	public MethodDef getMethodDef(int access, String name, String descriptor) {
+		return new MethodDef(access, name, descriptor);
+	}
+
+	public TypeRef getSuper() {
+		return zuper;
+	}
+
+	public String getFQN() {
+		return className.getFQN();
+	}
+
+	public TypeRef[] getInterfaces() {
+		return interfaces;
+	}
+
+	public void setInnerAccess(int access) {
+		innerAccess = access;
+	}
+
+	public boolean isFinal() {
+		return Modifier.isFinal(accessx);
+	}
+
+	public void setDeprecated(boolean b) {
+		deprecated = b;
+	}
+
+	public boolean isDeprecated() {
+		return deprecated;
+	}
+
+	public boolean isAnnotation() {
+		return (accessx & ACC_ANNOTATION) != 0;
+	}
+
+	public Set<PackageRef> getAPIUses() {
+		if ( api == null)
+			return Collections.emptySet();
+		return  api;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/CombinedResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/CombinedResource.java
new file mode 100644
index 0000000..d38f416
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/CombinedResource.java
@@ -0,0 +1,33 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.*;
+
+public class CombinedResource extends WriteResource {
+	final List<Resource>	resources		= new ArrayList<Resource>();
+	long					lastModified	= 0;
+
+	@Override
+	public void write(final OutputStream out) throws IOException, Exception {
+		OutputStream unclosable = new FilterOutputStream(out) {
+			public void close() {
+				// Ignore
+			}
+		};
+		for (Resource r : resources) {
+			r.write(unclosable);
+			unclosable.flush();
+		}
+	}
+
+	@Override
+	public long lastModified() {
+		return lastModified;
+	}
+
+	public void addResource(Resource r) {
+		lastModified = Math.max(lastModified, r.lastModified());
+		resources.add(r);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java
new file mode 100644
index 0000000..eb66635
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/CommandResource.java
@@ -0,0 +1,53 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+import aQute.libg.command.*;
+
+public class CommandResource extends WriteResource {
+	final long		lastModified;
+	final Builder	domain;
+	final String	command;
+
+	public CommandResource(String command, Builder domain, long lastModified) {
+		this.lastModified = lastModified;
+		this.domain = domain;
+		this.command = command;
+	}
+
+	@Override
+	public void write(OutputStream out) throws IOException, Exception {
+		StringBuilder errors = new StringBuilder();
+		StringBuilder stdout = new StringBuilder();
+		try {
+			domain.trace("executing command %s", command);
+			Command cmd = new Command("sh");
+			cmd.inherit();
+			String oldpath = cmd.var("PATH");
+
+			String path = domain.getProperty("-PATH");
+			if (path != null) {
+				path = path.replaceAll("\\s*,\\s*", File.pathSeparator);
+				path = path.replaceAll("\\$\\{@\\}", oldpath);
+				cmd.var("PATH", path);
+				domain.trace("PATH: %s", path);
+			}
+			OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
+			int result = cmd.execute(command, stdout, errors);
+			osw.append(stdout);
+			osw.flush();
+			if (result != 0) {
+				domain.error("executing command failed %s %s", command, stdout + "\n" + errors);
+			}
+		}
+		catch (Exception e) {
+			domain.error("executing command failed %s %s", command, e.getMessage());
+		}
+	}
+
+	@Override
+	public long lastModified() {
+		return lastModified;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java
new file mode 100644
index 0000000..755e9a2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Constants.java
@@ -0,0 +1,313 @@
+package aQute.bnd.osgi;
+
+import java.nio.charset.*;
+import java.util.*;
+import java.util.regex.*;
+
+public interface Constants {
+	/*
+	 * Defined in OSGi
+	 */
+	/**
+	 * @syntax Bundle-ActivationPolicy ::= policy ( ’;’ directive )* policy ::=
+	 *         ’lazy’
+	 */
+	String							BND_ADDXMLTOTEST							= "Bnd-AddXMLToTest";
+	String							BUNDLE_ACTIVATIONPOLICY						= "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							PROVIDE_CAPABILITY							= "Provide-Capability";
+	String							REQUIRE_BUNDLE								= "Require-Bundle";
+	String							REQUIRE_CAPABILITY							= "Require-Capability";
+	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";
+	/**
+	 * @deprecated Use {@link Constants#TESTCASES}.
+	 */
+	@Deprecated
+	String							TESTSUITES									= "Test-Suites";
+	String							SIGNATURE_TEST								= "-signaturetest";
+
+	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, SIGNATURE_TEST, REQUIRE_CAPABILITY,
+			PROVIDE_CAPABILITY, BUNDLE_ICON
+																				};
+
+	String							BUILDPATH									= "-buildpath";
+	String							BUILDPACKAGES								= "-buildpackages";
+	String							BUMPPOLICY									= "-bumppolicy";
+	String							CONDUIT										= "-conduit";
+	String							COMPILER_SOURCE								= "-source";
+	String							COMPILER_TARGET								= "-target";
+	String							DEPENDSON									= "-dependson";
+	String							DEPLOY										= "-deploy";
+	String							DEPLOYREPO									= "-deployrepo";
+	String							DIGESTS										= "-digests";
+	String							DSANNOTATIONS								= "-dsannotations";
+	String							DONOTCOPY									= "-donotcopy";
+	String							DEBUG										= "-debug";
+	String							EXPERIMENTS									= "-experiments";
+	String							EXPORT_CONTENTS								= "-exportcontents";
+	String							FAIL_OK										= "-failok";
+	String							INCLUDE										= "-include";
+	String							INCLUDERESOURCE								= "-includeresource";
+	String							MAKE										= "-make";
+	String							METATYPE									= "-metatype";
+	String							MANIFEST									= "-manifest";
+	String							SAVEMANIFEST								= "-savemanifest";
+	String							NAMESECTION									= "-namesection";
+	String							NODEFAULTVERSION							= "-nodefaultversion";
+	String							NOEXTRAHEADERS								= "-noextraheaders";
+	String							NOMANIFEST									= "-nomanifest";
+	String							NOUSES										= "-nouses";
+	String							NOBUNDLES									= "-nobundles";
+	String							PEDANTIC									= "-pedantic";
+	String							PLUGIN										= "-plugin";
+	String							PLUGINPATH									= "-pluginpath";
+	String							POM											= "-pom";
+	String							RELEASEREPO									= "-releaserepo";
+	String							REMOVEHEADERS								= "-removeheaders";
+	String							RESOURCEONLY								= "-resourceonly";
+	String							SOURCES										= "-sources";
+	String							SOURCEPATH									= "-sourcepath";
+	String							SUB											= "-sub";
+	String							RUNPROPERTIES								= "-runproperties";
+	String							RUNSYSTEMPACKAGES							= "-runsystempackages";
+	String							RUNBUNDLES									= "-runbundles";
+	String							RUNREPOS									= "-runrepos";
+
+	/**
+	 * @deprecated This is for support of the legacy OBR requirement format, use
+	 *             {@link #RUNREQUIRES} for new format.
+	 */
+	@Deprecated
+	String							RUNREQUIRE									= "-runrequire";
+
+	String							RUNREQUIRES									= "-runrequires";
+
+	String							RUNEE										= "-runee";
+	String							RUNPATH										= "-runpath";
+	String							RUNSTORAGE									= "-runstorage";
+	String							RUNBUILDS									= "-runbuilds";
+	String							RUNPATH_MAIN_DIRECTIVE						= "main:";
+	String							RUNPATH_LAUNCHER_DIRECTIVE					= "launcher:";
+	String							RUNVM										= "-runvm";
+	String							RUNTRACE									= "-runtrace";
+	String							RUNFRAMEWORK								= "-runframework";
+	String							RUNTIMEOUT									= "-runtimeout";
+	String							SNAPSHOT									= "-snapshot";
+	String							RUNFRAMEWORK_SERVICES						= "services";
+	String							RUNFRAMEWORK_NONE							= "none";
+	String							REPORTNEWER									= "-reportnewer";
+	String							SIGN										= "-sign";
+	String							TESTPACKAGES								= "-testpackages";
+	String							TESTREPORT									= "-testreport";
+	String							TESTPATH									= "-testpath";
+	String							TESTCONTINUOUS								= "-testcontinuous";
+	String							UNDERTEST									= "-undertest";
+	String							VERBOSE										= "-verbose";
+	String							PROVIDER_POLICY								= "-provider-policy";
+	String							CONSUMER_POLICY								= "-consumer-policy";
+	String							WAB											= "-wab";
+	String							WABLIB										= "-wablib";
+	String							REQUIRE_BND									= "-require-bnd";
+
+	// Deprecated
+	String							CLASSPATH									= "-classpath";
+	String							OUTPUT										= "-output";
+
+	String							options[]									= {
+			BUILDPATH, BUMPPOLICY, CONDUIT, CLASSPATH, CONSUMER_POLICY, DEPENDSON, DONOTCOPY, EXPORT_CONTENTS, FAIL_OK,
+			INCLUDE, INCLUDERESOURCE, MAKE, MANIFEST, NOEXTRAHEADERS, NOUSES, NOBUNDLES, PEDANTIC, PLUGIN, POM,
+			PROVIDER_POLICY, REMOVEHEADERS, RESOURCEONLY, SOURCES, SOURCEPATH, SOURCES, SOURCEPATH, SUB, RUNBUNDLES,
+			RUNPATH, RUNSYSTEMPACKAGES, RUNPROPERTIES, REPORTNEWER, UNDERTEST, TESTPATH, TESTPACKAGES, TESTREPORT,
+			VERBOSE, NOMANIFEST, DEPLOYREPO, RELEASEREPO, SAVEMANIFEST, RUNVM, WAB, WABLIB, RUNFRAMEWORK, RUNTRACE,
+			TESTCONTINUOUS, SNAPSHOT, NAMESECTION, DIGESTS, DSANNOTATIONS, EXPERIMENTS
+																				};
+
+	// 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, SIGNATURE_TEST, WAB, WABLIB, REQUIRE_CAPABILITY,
+			PROVIDE_CAPABILITY, DSANNOTATIONS, SERVICE_COMPONENT
+																				};
+
+	char							DUPLICATE_MARKER							= '~';
+	String							SPECIFICATION_VERSION						= "specification-version";
+	String							SPLIT_PACKAGE_DIRECTIVE						= "-split-package:";
+	String							IMPORT_DIRECTIVE							= "-import:";
+	String							NO_IMPORT_DIRECTIVE							= "-noimport:";
+	String							REMOVE_ATTRIBUTE_DIRECTIVE					= "-remove-attribute:";
+	String							LIB_DIRECTIVE								= "lib:";
+	String							NOANNOTATIONS								= "-noannotations";
+	String							COMMAND_DIRECTIVE							= "command:";
+	String							USES_DIRECTIVE								= "uses:";
+	String							MANDATORY_DIRECTIVE							= "mandatory:";
+	String							INCLUDE_DIRECTIVE							= "include:";
+	String							PROVIDE_DIRECTIVE							= "provide:";
+	String							EXCLUDE_DIRECTIVE							= "exclude:";
+	String							PRESENCE_DIRECTIVE							= "presence:";
+	String							PRIVATE_DIRECTIVE							= "private:";
+	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							FROM_DIRECTIVE								= "from:";
+
+	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, COMMAND_DIRECTIVE, NOANNOTATIONS, LIB_DIRECTIVE,
+			RUNPATH_LAUNCHER_DIRECTIVE, FROM_DIRECTIVE, PRIVATE_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							DEFAULT_BNDRUN_EXTENSION					= ".bndrun";
+	String[]						METAPACKAGES								= {
+			"META-INF", "OSGI-INF", "OSGI-OPT"
+																				};
+
+	String							CURRENT_VERSION								= "@";
+	String							CURRENT_PACKAGE								= "@package";
+
+	String							BUILDFILES									= "buildfiles";
+
+	String							EMPTY_HEADER								= "<<EMPTY>>";
+
+	String							EMBEDDED_REPO								= "/embedded-repo.jar";
+	String							LAUNCHER_PLUGIN								= "Launcher-Plugin";
+	String							TESTER_PLUGIN								= "Tester-Plugin";
+
+	String							DEFAULT_LAUNCHER_BSN						= "biz.aQute.launcher";
+	String							DEFAULT_TESTER_BSN							= "biz.aQute.junit";
+
+	String							DEFAULT_DO_NOT_COPY							= "CVS|\\.svn|\\.git|\\.DS_Store";
+
+	Charset							DEFAULT_CHARSET								= Charset.forName("UTF8");
+	String							VERSION_FILTER								= "version";
+	String							PROVIDER_TYPE_DIRECTIVE						= "x-provider-type:";
+	/**
+	 * Component constants
+	 */
+	public final static String		NAMESPACE_STEM								= "http://www.osgi.org/xmlns/scr";
+	public final static String		JIDENTIFIER									= "<<identifier>>";
+	public final static String		COMPONENT_NAME								= "name:";
+	public final static String		COMPONENT_FACTORY							= "factory:";
+	public final static String		COMPONENT_SERVICEFACTORY					= "servicefactory:";
+	public final static String		COMPONENT_IMMEDIATE							= "immediate:";
+	public final static String		COMPONENT_ENABLED							= "enabled:";
+	public final static String		COMPONENT_DYNAMIC							= "dynamic:";
+	public final static String		COMPONENT_MULTIPLE							= "multiple:";
+	public final static String		COMPONENT_PROVIDE							= "provide:";
+	public final static String		COMPONENT_OPTIONAL							= "optional:";
+	public final static String		COMPONENT_PROPERTIES						= "properties:";
+	public final static String		COMPONENT_IMPLEMENTATION					= "implementation:";
+	public final static String		COMPONENT_DESIGNATE							= "designate:";
+	public final static String		COMPONENT_DESIGNATEFACTORY					= "designateFactory:";
+	public final static String		COMPONENT_DESCRIPTORS						= ".descriptors:";
+
+	// v1.1.0
+	public final static String		COMPONENT_VERSION							= "version:";
+	public final static String		COMPONENT_CONFIGURATION_POLICY				= "configuration-policy:";
+	public final static String		COMPONENT_MODIFIED							= "modified:";
+	public final static String		COMPONENT_ACTIVATE							= "activate:";
+	public final static String		COMPONENT_DEACTIVATE						= "deactivate:";
+
+	final static Map<String,String>	EMPTY										= Collections.emptyMap();
+
+	public final static String[]	componentDirectives							= new String[] {
+			COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED, COMPONENT_DYNAMIC, COMPONENT_MULTIPLE,
+			COMPONENT_PROVIDE, COMPONENT_OPTIONAL, COMPONENT_PROPERTIES, COMPONENT_IMPLEMENTATION,
+			COMPONENT_SERVICEFACTORY, COMPONENT_VERSION, COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED,
+			COMPONENT_ACTIVATE, COMPONENT_DEACTIVATE, COMPONENT_NAME, COMPONENT_DESCRIPTORS, COMPONENT_DESIGNATE,
+			COMPONENT_DESIGNATEFACTORY
+																				};
+
+	public final static Set<String>	SET_COMPONENT_DIRECTIVES					= new HashSet<String>(
+																						Arrays.asList(componentDirectives));
+
+	public final static Set<String>	SET_COMPONENT_DIRECTIVES_1_1				= //
+																				new HashSet<String>(Arrays.asList(
+																						COMPONENT_VERSION,
+																						COMPONENT_CONFIGURATION_POLICY,
+																						COMPONENT_MODIFIED,
+																						COMPONENT_ACTIVATE,
+																						COMPONENT_DEACTIVATE));
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Descriptors.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Descriptors.java
new file mode 100644
index 0000000..b435b27
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Descriptors.java
@@ -0,0 +1,557 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+
+import aQute.libg.generics.*;
+
+public class Descriptors {
+	Map<String,TypeRef>		typeRefCache		= Create.map();
+	Map<String,Descriptor>	descriptorCache		= Create.map();
+	Map<String,PackageRef>	packageCache		= Create.map();
+
+	// MUST BE BEFORE PRIMITIVES, THEY USE THE DEFAULT PACKAGE!!
+	final static PackageRef	DEFAULT_PACKAGE		= new PackageRef();
+	final static PackageRef	PRIMITIVE_PACKAGE	= new PackageRef();
+
+	final static TypeRef	VOID				= new ConcreteRef("V", "void", PRIMITIVE_PACKAGE);
+	final static TypeRef	BOOLEAN				= new ConcreteRef("Z", "boolean", PRIMITIVE_PACKAGE);
+	final static TypeRef	BYTE				= new ConcreteRef("B", "byte", PRIMITIVE_PACKAGE);
+	final static TypeRef	CHAR				= new ConcreteRef("C", "char", PRIMITIVE_PACKAGE);
+	final static TypeRef	SHORT				= new ConcreteRef("S", "short", PRIMITIVE_PACKAGE);
+	final static TypeRef	INTEGER				= new ConcreteRef("I", "int", PRIMITIVE_PACKAGE);
+	final static TypeRef	LONG				= new ConcreteRef("J", "long", PRIMITIVE_PACKAGE);
+	final static TypeRef	DOUBLE				= new ConcreteRef("D", "double", PRIMITIVE_PACKAGE);
+	final static TypeRef	FLOAT				= new ConcreteRef("F", "float", PRIMITIVE_PACKAGE);
+
+	{
+		packageCache.put("", DEFAULT_PACKAGE);
+	}
+
+	public interface TypeRef extends Comparable<TypeRef> {
+		String getBinary();
+
+		String getFQN();
+
+		String getPath();
+
+		boolean isPrimitive();
+
+		TypeRef getComponentTypeRef();
+
+		TypeRef getClassRef();
+
+		PackageRef getPackageRef();
+
+		String getShortName();
+
+		boolean isJava();
+
+		boolean isObject();
+
+		String getSourcePath();
+
+		String getDottedOnly();
+
+	}
+
+	public static class PackageRef implements Comparable<PackageRef> {
+		final String	binaryName;
+		final String	fqn;
+		final boolean	java;
+
+		PackageRef(String binaryName) {
+			this.binaryName = fqnToBinary(binaryName);
+			this.fqn = binaryToFQN(binaryName);
+			this.java = this.fqn.startsWith("java."); // &&
+														// !this.fqn.equals("java.sql)"
+
+			// For some reason I excluded java.sql but the classloader will
+			// delegate anyway. So lost the understanding why I did it??
+		}
+
+		PackageRef() {
+			this.binaryName = "";
+			this.fqn = ".";
+			this.java = false;
+		}
+
+		public PackageRef getDuplicate() {
+			return new PackageRef(binaryName + Constants.DUPLICATE_MARKER);
+		}
+
+		public String getFQN() {
+			return fqn;
+		}
+
+		public String getBinary() {
+			return binaryName;
+		}
+
+		public String getPath() {
+			return binaryName;
+		}
+
+		public boolean isJava() {
+			return java;
+		}
+
+		public String toString() {
+			return fqn;
+		}
+
+		boolean isDefaultPackage() {
+			return this.fqn.equals(".");
+		}
+
+		boolean isPrimitivePackage() {
+			return this == PRIMITIVE_PACKAGE;
+		}
+
+		public int compareTo(PackageRef other) {
+			return fqn.compareTo(other.fqn);
+		}
+
+		public boolean equals(Object o) {
+			assert o instanceof PackageRef;
+			return o == this;
+		}
+
+		public int hashCode() {
+			return super.hashCode();
+		}
+
+		/**
+		 * Decide if the package is a metadata package.
+		 * 
+		 * @param pack
+		 * @return
+		 */
+		public boolean isMetaData() {
+			if (isDefaultPackage())
+				return true;
+
+			for (int i = 0; i < Constants.METAPACKAGES.length; i++) {
+				if (fqn.startsWith(Constants.METAPACKAGES[i]))
+					return true;
+			}
+			return false;
+		}
+
+	}
+
+	// We "intern" the
+	private static class ConcreteRef implements TypeRef {
+		final String		binaryName;
+		final String		fqn;
+		final boolean		primitive;
+		final PackageRef	packageRef;
+
+		ConcreteRef(PackageRef packageRef, String binaryName) {
+			if (packageRef.getFQN().length() < 2)
+				System.err.println("in default pack? " + binaryName);
+			this.binaryName = binaryName;
+			this.fqn = binaryToFQN(binaryName);
+			this.primitive = false;
+			this.packageRef = packageRef;
+		}
+
+		ConcreteRef(String binaryName, String fqn, PackageRef pref) {
+			this.binaryName = binaryName;
+			this.fqn = fqn;
+			this.primitive = true;
+			this.packageRef = pref;
+		}
+
+		public String getBinary() {
+			return binaryName;
+		}
+
+		public String getPath() {
+			return binaryName + ".class";
+		}
+
+		public String getSourcePath() {
+			return binaryName + ".java";
+		}
+
+		public String getFQN() {
+			return fqn;
+		}
+
+		public String getDottedOnly() {
+			return fqn.replace('$', '.');
+		}
+
+		public boolean isPrimitive() {
+			return primitive;
+		}
+
+		public TypeRef getComponentTypeRef() {
+			return null;
+		}
+
+		public TypeRef getClassRef() {
+			return this;
+		}
+
+		public PackageRef getPackageRef() {
+			return packageRef;
+		}
+
+		public String getShortName() {
+			int n = binaryName.lastIndexOf('/');
+			return binaryName.substring(n + 1);
+		}
+
+		public boolean isJava() {
+			return packageRef.isJava();
+		}
+
+		public String toString() {
+			return fqn;
+		}
+
+		public boolean isObject() {
+			return fqn.equals("java.lang.Object");
+		}
+
+		public boolean equals(Object other) {
+			assert other instanceof TypeRef;
+			return this == other;
+		}
+
+		public int compareTo(TypeRef other) {
+			if (this == other)
+				return 0;
+			return fqn.compareTo(other.getFQN());
+		}
+
+		@Override
+		public int hashCode() {
+			return super.hashCode();
+		}
+
+	}
+
+	private static class ArrayRef implements TypeRef {
+		final TypeRef	component;
+
+		ArrayRef(TypeRef component) {
+			this.component = component;
+		}
+
+		public String getBinary() {
+			return "[" + component.getBinary();
+		}
+
+		public String getFQN() {
+			return component.getFQN() + "[]";
+		}
+
+		public String getPath() {
+			return component.getPath();
+		}
+
+		public String getSourcePath() {
+			return component.getSourcePath();
+		}
+
+		public boolean isPrimitive() {
+			return false;
+		}
+
+		public TypeRef getComponentTypeRef() {
+			return component;
+		}
+
+		public TypeRef getClassRef() {
+			return component.getClassRef();
+		}
+
+		public boolean equals(Object other) {
+			if (other == null || other.getClass() != getClass())
+				return false;
+
+			return component.equals(((ArrayRef) other).component);
+		}
+
+		public PackageRef getPackageRef() {
+			return component.getPackageRef();
+		}
+
+		public String getShortName() {
+			return component.getShortName() + "[]";
+		}
+
+		public boolean isJava() {
+			return component.isJava();
+		}
+
+		public String toString() {
+			return component.toString() + "[]";
+		}
+
+		public boolean isObject() {
+			return false;
+		}
+
+		public String getDottedOnly() {
+			return component.getDottedOnly();
+		}
+
+		public int compareTo(TypeRef other) {
+			if (this == other)
+				return 0;
+
+			return getFQN().compareTo(other.getFQN());
+		}
+
+		@Override
+		public int hashCode() {
+			return super.hashCode();
+		}
+
+	}
+
+	public TypeRef getTypeRef(String binaryClassName) {
+		assert !binaryClassName.endsWith(".class");
+
+		TypeRef ref = typeRefCache.get(binaryClassName);
+		if (ref != null)
+			return ref;
+
+		if (binaryClassName.startsWith("[")) {
+			ref = getTypeRef(binaryClassName.substring(1));
+			ref = new ArrayRef(ref);
+		} else {
+			if (binaryClassName.length() >= 1) {
+				switch (binaryClassName.charAt(0)) {
+					case 'V' :
+						return VOID;
+					case 'B' :
+						return BYTE;
+					case 'C' :
+						return CHAR;
+					case 'I' :
+						return INTEGER;
+					case 'S' :
+						return SHORT;
+					case 'D' :
+						return DOUBLE;
+					case 'F' :
+						return FLOAT;
+					case 'J' :
+						return LONG;
+					case 'Z' :
+						return BOOLEAN;
+					case 'L' :
+						binaryClassName = binaryClassName.substring(1, binaryClassName.length() - 1);
+						break;
+				}
+				// falls trough for other 1 letter class names
+			}
+			ref = typeRefCache.get(binaryClassName);
+			if (ref != null)
+				return ref;
+
+			PackageRef pref;
+			int n = binaryClassName.lastIndexOf('/');
+			if (n < 0)
+				pref = DEFAULT_PACKAGE;
+			else
+				pref = getPackageRef(binaryClassName.substring(0, n));
+
+			ref = new ConcreteRef(pref, binaryClassName);
+		}
+
+		typeRefCache.put(binaryClassName, ref);
+		return ref;
+	}
+
+	public PackageRef getPackageRef(String binaryPackName) {
+		if (binaryPackName.indexOf('.') >= 0) {
+			binaryPackName = binaryPackName.replace('.', '/');
+		}
+		PackageRef ref = packageCache.get(binaryPackName);
+		if (ref != null)
+			return ref;
+
+		ref = new PackageRef(binaryPackName);
+		packageCache.put(binaryPackName, ref);
+		return ref;
+	}
+
+	public Descriptor getDescriptor(String descriptor) {
+		Descriptor d = descriptorCache.get(descriptor);
+		if (d != null)
+			return d;
+		d = new Descriptor(descriptor);
+		descriptorCache.put(descriptor, d);
+		return d;
+	}
+
+	public class Descriptor {
+		final TypeRef	type;
+		final TypeRef[]	prototype;
+		final String	descriptor;
+
+		Descriptor(String descriptor) {
+			this.descriptor = descriptor;
+			int index = 0;
+			List<TypeRef> types = Create.list();
+			if (descriptor.charAt(index) == '(') {
+				index++;
+				while (descriptor.charAt(index) != ')') {
+					index = parse(types, descriptor, index);
+				}
+				index++; // skip )
+				prototype = types.toArray(new TypeRef[types.size()]);
+				types.clear();
+			} else
+				prototype = null;
+
+			index = parse(types, descriptor, index);
+			type = types.get(0);
+		}
+
+		int parse(List<TypeRef> types, String descriptor, int index) {
+			char c;
+			StringBuilder sb = new StringBuilder();
+			while ((c = descriptor.charAt(index++)) == '[') {
+				sb.append('[');
+			}
+
+			switch (c) {
+				case 'L' :
+					while ((c = descriptor.charAt(index++)) != ';') {
+						// TODO
+						sb.append(c);
+					}
+					break;
+
+				case 'V' :
+				case 'B' :
+				case 'C' :
+				case 'I' :
+				case 'S' :
+				case 'D' :
+				case 'F' :
+				case 'J' :
+				case 'Z' :
+					sb.append(c);
+					break;
+
+				default :
+					throw new IllegalArgumentException("Invalid type in descriptor: " + c + " from " + descriptor + "["
+							+ index + "]");
+			}
+			types.add(getTypeRef(sb.toString()));
+			return index;
+		}
+
+		public TypeRef getType() {
+			return type;
+		}
+
+		public TypeRef[] getPrototype() {
+			return prototype;
+		}
+
+		public boolean equals(Object other) {
+			if (other == null || other.getClass() != getClass())
+				return false;
+
+			return Arrays.equals(prototype, ((Descriptor) other).prototype) && type == ((Descriptor) other).type;
+		}
+
+		public int hashCode() {
+			return prototype == null ? type.hashCode() : type.hashCode() ^ Arrays.hashCode(prototype);
+		}
+
+		public String toString() {
+			return descriptor;
+		}
+	}
+
+	/**
+	 * Return the short name of a FQN
+	 */
+
+	public static String getShortName(String fqn) {
+		assert fqn.indexOf('/') < 0;
+
+		int n = fqn.lastIndexOf('.');
+		if (n >= 0) {
+			return fqn.substring(n + 1);
+		}
+		return fqn;
+	}
+
+	public static String binaryToFQN(String binary) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0, l = binary.length(); i < l; i++) {
+			char c = binary.charAt(i);
+
+			if (c == '/')
+				sb.append('.');
+			else
+				sb.append(c);
+		}
+		String result = sb.toString();
+		assert result.length() > 0;
+		return result;
+	}
+
+	public static String fqnToBinary(String binary) {
+		return binary.replace('.', '/');
+	}
+
+	public static String getPackage(String binaryNameOrFqn) {
+		int n = binaryNameOrFqn.lastIndexOf('/');
+		if (n >= 0)
+			return binaryNameOrFqn.substring(0, n).replace('/', '.');
+
+		n = binaryNameOrFqn.lastIndexOf(".");
+		if (n >= 0)
+			return binaryNameOrFqn.substring(0, n);
+
+		return ".";
+	}
+
+	public static String fqnToPath(String s) {
+		return fqnToBinary(s) + ".class";
+	}
+
+	public TypeRef getTypeRefFromFQN(String fqn) {
+		if (fqn.equals("boolean"))
+			return BOOLEAN;
+
+		if (fqn.equals("byte"))
+			return BOOLEAN;
+
+		if (fqn.equals("char"))
+			return CHAR;
+
+		if (fqn.equals("short"))
+			return SHORT;
+
+		if (fqn.equals("int"))
+			return INTEGER;
+
+		if (fqn.equals("long"))
+			return LONG;
+
+		if (fqn.equals("float"))
+			return FLOAT;
+
+		if (fqn.equals("double"))
+			return DOUBLE;
+
+		return getTypeRef(fqnToBinary(fqn));
+	}
+
+	public TypeRef getTypeRefFromPath(String path) {
+		assert path.endsWith(".class");
+		return getTypeRef(path.substring(0, path.length() - 6));
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Domain.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Domain.java
new file mode 100644
index 0000000..828f1cf
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Domain.java
@@ -0,0 +1,304 @@
+package aQute.bnd.osgi;
+
+import static aQute.bnd.osgi.Constants.*;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+
+import aQute.bnd.header.*;
+import aQute.lib.converter.*;
+import aQute.service.reporter.*;
+
+/**
+ * This class abstracts domains that have properties holding OSGi meta data. It
+ * provides access to the keys, the set method and the get method. It then
+ * provides convenient methods to access these properties via semantic methods.
+ */
+public abstract class Domain implements Iterable<String> {
+
+	public abstract String get(String key);
+
+	public String get(String key, String deflt) {
+		String result = get(key);
+		if (result != null)
+			return result;
+		return deflt;
+	}
+
+	public abstract void set(String key, String value);
+
+	public abstract Iterator<String> iterator();
+
+	public static Domain domain(final Manifest manifest) {
+		Attributes attrs = manifest.getMainAttributes();
+		return domain(attrs);
+	}
+
+	public static Domain domain(final Attributes attrs) {
+		return new Domain() {
+
+			@Override
+			public String get(String key) {
+				return attrs.getValue(key);
+			}
+
+			@Override
+			public void set(String key, String value) {
+				attrs.putValue(key, value);
+			}
+
+			@Override
+			public Iterator<String> iterator() {
+				final Iterator<Object> it = attrs.keySet().iterator();
+
+				return new Iterator<String>() {
+
+					public boolean hasNext() {
+						return it.hasNext();
+					}
+
+					public String next() {
+						return it.next().toString();
+					}
+
+					public void remove() {
+						it.remove();
+					}
+				};
+			}
+		};
+	}
+
+	public static Domain domain(final Processor processor) {
+		return new Domain() {
+
+			@Override
+			public String get(String key) {
+				return processor.getProperty(key);
+			}
+
+			@Override
+			public String get(String key, String deflt) {
+				return processor.getProperty(key, deflt);
+			}
+
+			@Override
+			public void set(String key, String value) {
+				processor.setProperty(key, value);
+			}
+
+			@Override
+			public Iterator<String> iterator() {
+				final Iterator<String> it = processor.getPropertyKeys(true).iterator();
+
+				return new Iterator<String>() {
+					String	current;
+
+					public boolean hasNext() {
+						return it.hasNext();
+					}
+
+					public String next() {
+						return current = it.next().toString();
+					}
+
+					public void remove() {
+						processor.getProperties().remove(current);
+					}
+				};
+			}
+		};
+	}
+
+	public static Domain domain(final Map<String,String> map) {
+		return new Domain() {
+
+			@Override
+			public String get(String key) {
+				return map.get(key);
+			}
+
+			@Override
+			public void set(String key, String value) {
+				map.put(key, value);
+			}
+
+			@Override
+			public Iterator<String> iterator() {
+				return map.keySet().iterator();
+			}
+		};
+	}
+
+	public Parameters getParameters(String key, Reporter reporter) {
+		return new Parameters(get(key), reporter);
+	}
+
+	public Parameters getParameters(String key) {
+		return new Parameters(get(key));
+	}
+
+	public Parameters getParameters(String key, String deflt) {
+		return new Parameters(get(key, deflt));
+	}
+
+	public Parameters getParameters(String key, String deflt, Reporter reporter) {
+		return new Parameters(get(key, deflt), reporter);
+	}
+
+	public Parameters getImportPackage() {
+		return getParameters(IMPORT_PACKAGE);
+	}
+
+	public Parameters getExportPackage() {
+		return getParameters(EXPORT_PACKAGE);
+	}
+
+	public Parameters getBundleClassPath() {
+		return getParameters(BUNDLE_CLASSPATH);
+	}
+
+	public Parameters getPrivatePackage() {
+		return getParameters(PRIVATE_PACKAGE);
+	}
+
+	public Parameters getIncludeResource() {
+		Parameters ic = getParameters(INCLUDE_RESOURCE);
+		ic.putAll(getParameters(INCLUDERESOURCE));
+		return ic;
+	}
+
+	public Parameters getDynamicImportPackage() {
+		return getParameters(DYNAMICIMPORT_PACKAGE);
+	}
+
+	public Parameters getExportContents() {
+		return getParameters(EXPORT_CONTENTS);
+	}
+
+	public String getBundleActivator() {
+		return get(BUNDLE_ACTIVATOR);
+	}
+
+	public void setPrivatePackage(String s) {
+		if (s != null)
+			set(PRIVATE_PACKAGE, s);
+	}
+
+	public void setIncludeResource(String s) {
+		if (s != null)
+			set(INCLUDE_RESOURCE, s);
+	}
+
+	public void setBundleActivator(String s) {
+		if (s != null)
+			set(BUNDLE_ACTIVATOR, s);
+	}
+
+	public void setExportPackage(String s) {
+		if (s != null)
+			set(EXPORT_PACKAGE, s);
+	}
+
+	public void setImportPackage(String s) {
+		if (s != null)
+			set(IMPORT_PACKAGE, s);
+	}
+
+	public void setBundleClasspath(String s) {
+		if (s != null)
+			set(BUNDLE_CLASSPATH, s);
+	}
+
+	public Parameters getBundleClasspath() {
+		return getParameters(BUNDLE_CLASSPATH);
+	}
+
+	public void setBundleRequiredExecutionEnvironment(String s) {
+		if (s != null)
+			set(BUNDLE_REQUIREDEXECUTIONENVIRONMENT, s);
+	}
+
+	public Parameters getBundleRequiredExecutionEnvironment() {
+		return getParameters(BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+	}
+
+	public void setSources(boolean b) {
+		if (b)
+			set(SOURCES, "true");
+		else
+			set(SOURCES, "false");
+	}
+
+	public boolean isSources() {
+		return Processor.isTrue(get(SOURCES));
+	}
+
+	public Map.Entry<String,Attrs> getBundleSymbolicName() {
+		Parameters p = getParameters(BUNDLE_SYMBOLICNAME);
+		if (p.isEmpty())
+			return null;
+		return p.entrySet().iterator().next();
+	}
+
+	public void setBundleSymbolicName(String s) {
+		set(BUNDLE_SYMBOLICNAME, s);
+	}
+
+	public String getBundleVersion() {
+		return get(BUNDLE_VERSION);
+	}
+
+	public void setBundleVersion(String version) {
+		Version v = new Version(version);
+		set(BUNDLE_VERSION, v.toString());
+	}
+
+	public void setBundleVersion(Version version) {
+		set(BUNDLE_VERSION, version.toString());
+	}
+
+	public void setFailOk(boolean b) {
+		set(FAIL_OK, b + "");
+	}
+
+	public boolean isFailOk() {
+		return Processor.isTrue(get(FAIL_OK));
+	}
+
+	/**
+	 * Find an icon with the requested size in the list of icons.
+	 * 
+	 * @param requestedSize
+	 *            the number of pixels desired
+	 * @return null or a the selected URI (which may be relative)
+	 */
+	public String getIcon(int requestedSize) throws Exception {
+		String spec = get(Constants.BUNDLE_ICON);
+		if (spec == null)
+			return null;
+
+		Parameters p = OSGiHeader.parseHeader(spec);
+		int dist = Integer.MAX_VALUE;
+		String selected = null;
+
+		for (Entry<String,Attrs> e : p.entrySet()) {
+			String url = e.getKey();
+			if (selected == null)
+				selected = url;
+
+			int size = Converter.cnv(Integer.class, e.getValue().get("size"));
+			if (size != 0 && Math.abs(requestedSize - size) < dist) {
+				dist = Math.abs(requestedSize - size);
+				selected = url;
+			}
+		}
+		return selected;
+	}
+
+	public void setConditionalPackage(String string) {
+		set(CONDITIONAL_PACKAGE, string);
+		
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/EmbeddedResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/EmbeddedResource.java
new file mode 100755
index 0000000..0aad605
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/EmbeddedResource.java
@@ -0,0 +1,99 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.zip.*;
+
+import aQute.lib.io.*;
+
+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();
+		}
+		IO.drain(in);
+		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 Exception {
+		InputStream in = resource.openInputStream();
+		try {
+			build(sub, in, resource.lastModified());
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+		}
+		finally {
+			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/bnd/osgi/FileResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/FileResource.java
new file mode 100755
index 0000000..cd18cf5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/FileResource.java
@@ -0,0 +1,78 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.regex.*;
+
+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 Exception {
+		copy(this, out);
+	}
+
+	static synchronized void copy(Resource resource, OutputStream out) throws Exception {
+		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 != null && doNotCopy.matcher(directory.getName()).matches())
+			return;
+		jar.updateModified(directory.lastModified(), "Dir change");
+
+		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/bnd/osgi/Instruction.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Instruction.java
new file mode 100755
index 0000000..a660169
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Instruction.java
@@ -0,0 +1,184 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.regex.*;
+
+public class Instruction {
+
+	public static class Filter implements FileFilter {
+
+		private Instruction	instruction;
+		private boolean		recursive;
+		private Pattern		doNotCopy;
+
+		public Filter(Instruction instruction, boolean recursive, Pattern doNotCopy) {
+			this.instruction = instruction;
+			this.recursive = recursive;
+			this.doNotCopy = doNotCopy;
+		}
+
+		public Filter(Instruction instruction, boolean recursive) {
+			this(instruction, recursive, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
+		}
+
+		public boolean isRecursive() {
+			return recursive;
+		}
+
+		public boolean accept(File pathname) {
+			if (doNotCopy != null && 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());
+		}
+	}
+
+	transient Pattern	pattern;
+	transient boolean	optional;
+
+	final String		input;
+	final String		match;
+	final boolean		negated;
+	final boolean		duplicate;
+	final boolean		literal;
+	final boolean		any;
+
+	public Instruction(String input) {
+		this.input = input;
+
+		String s = Processor.removeDuplicateMarker(input);
+		duplicate = !s.equals(input);
+
+		if (s.startsWith("!")) {
+			negated = true;
+			s = s.substring(1);
+		} else
+			negated = false;
+
+		if (input.equals("*")) {
+			any = true;
+			literal = false;
+			match = null;
+			return;
+		}
+
+		any = false;
+		if (s.startsWith("=")) {
+			match = s.substring(1);
+			literal = true;
+		} else {
+			boolean wildcards = false;
+
+			StringBuilder sb = new StringBuilder();
+			loop: for (int c = 0; c < s.length(); c++) {
+				switch (s.charAt(c)) {
+					case '.' :
+						// If we end in a wildcard .* then we need to
+						// also include the last full package. I.e.
+						// com.foo.* includes com.foo (unlike OSGi)
+						if (c == s.length() - 2 && '*' == s.charAt(c + 1)) {
+							sb.append("(\\..*)?");
+							wildcards = true;
+							break loop;
+						}
+						sb.append("\\.");
+
+						break;
+					case '*' :
+						sb.append(".*");
+						wildcards = true;
+						break;
+					case '$' :
+						sb.append("\\$");
+						break;
+					case '?' :
+						sb.append(".?");
+						wildcards = true;
+						break;
+					case '|' :
+						sb.append('|');
+						wildcards = true;
+						break;
+					default :
+						sb.append(s.charAt(c));
+						break;
+				}
+			}
+
+			if (!wildcards) {
+				literal = true;
+				match = s;
+			} else {
+				literal = false;
+				match = sb.toString();
+			}
+		}
+
+	}
+
+	public boolean matches(String value) {
+		if (any)
+			return true;
+
+		if (literal)
+			return match.equals(value);
+		return getMatcher(value).matches();
+	}
+
+	public boolean isNegated() {
+		return negated;
+	}
+
+	public String getPattern() {
+		return match;
+	}
+
+	public String getInput() {
+		return input;
+	}
+
+	public String toString() {
+		return input;
+	}
+
+	public Matcher getMatcher(String value) {
+		if (pattern == null) {
+			pattern = Pattern.compile(match);
+		}
+		return pattern.matcher(value);
+	}
+
+	public void setOptional() {
+		optional = true;
+	}
+
+	public boolean isOptional() {
+		return optional;
+	}
+
+	public boolean isLiteral() {
+		return literal;
+	}
+
+	public String getLiteral() {
+		assert literal;
+		return match;
+	}
+
+	public boolean isDuplicate() {
+		return duplicate;
+	}
+
+	public boolean isAny() {
+		return any;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Instructions.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Instructions.java
new file mode 100644
index 0000000..bfb7e28
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Instructions.java
@@ -0,0 +1,221 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+
+import aQute.bnd.header.*;
+
+public class Instructions implements Map<Instruction,Attrs> {
+	private LinkedHashMap<Instruction,Attrs>	map;
+	static Map<Instruction,Attrs>				EMPTY	= Collections.emptyMap();
+
+	public Instructions(Instructions other) {
+		if (other.map != null && !other.map.isEmpty()) {
+			map = new LinkedHashMap<Instruction,Attrs>(other.map);
+		}
+	}
+
+	public Instructions(Collection<String> other) {
+		if (other != null)
+			for (String s : other) {
+				put(new Instruction(s), null);
+			}
+	}
+
+	public Instructions() {}
+
+	public Instructions(Parameters contained) {
+		append(contained);
+	}
+
+	public Instructions(String h) {
+		this(new Parameters(h));
+	}
+
+	public void clear() {
+		map.clear();
+	}
+
+	public boolean containsKey(Instruction name) {
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	@Deprecated
+	public boolean containsKey(Object name) {
+		assert name instanceof Instruction;
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	public boolean containsValue(Attrs value) {
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	@Deprecated
+	public boolean containsValue(Object value) {
+		assert value instanceof Attrs;
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	public Set<java.util.Map.Entry<Instruction,Attrs>> entrySet() {
+		if (map == null)
+			return EMPTY.entrySet();
+
+		return map.entrySet();
+	}
+
+	@Deprecated
+	public Attrs get(Object key) {
+		assert key instanceof Instruction;
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public Attrs get(Instruction key) {
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public boolean isEmpty() {
+		return map == null || map.isEmpty();
+	}
+
+	public Set<Instruction> keySet() {
+		if (map == null)
+			return EMPTY.keySet();
+
+		return map.keySet();
+	}
+
+	public Attrs put(Instruction key, Attrs value) {
+		if (map == null)
+			map = new LinkedHashMap<Instruction,Attrs>();
+
+		return map.put(key, value);
+	}
+
+	public void putAll(Map< ? extends Instruction, ? extends Attrs> map) {
+		if (this.map == null) {
+			if (map.isEmpty())
+				return;
+			this.map = new LinkedHashMap<Instruction,Attrs>();
+		}
+		this.map.putAll(map);
+	}
+
+	@Deprecated
+	public Attrs remove(Object var0) {
+		assert var0 instanceof Instruction;
+		if (map == null)
+			return null;
+
+		return map.remove(var0);
+	}
+
+	public Attrs remove(Instruction var0) {
+		if (map == null)
+			return null;
+		return map.remove(var0);
+	}
+
+	public int size() {
+		if (map == null)
+			return 0;
+		return map.size();
+	}
+
+	public Collection<Attrs> values() {
+		if (map == null)
+			return EMPTY.values();
+
+		return map.values();
+	}
+
+	public String toString() {
+		return map == null ? "{}" : map.toString();
+	}
+
+	public void append(Parameters other) {
+		for (Map.Entry<String,Attrs> e : other.entrySet()) {
+			put(new Instruction(e.getKey()), e.getValue());
+		}
+	}
+
+	public <T> Collection<T> select(Collection<T> set, boolean emptyIsAll) {
+		return select(set, null, emptyIsAll);
+	}
+
+	public <T> Collection<T> select(Collection<T> set, Set<Instruction> unused, boolean emptyIsAll) {
+		List<T> input = new ArrayList<T>(set);
+		if (emptyIsAll && isEmpty())
+			return input;
+
+		List<T> result = new ArrayList<T>();
+
+		for (Instruction instruction : keySet()) {
+			boolean used = false;
+			for (Iterator<T> o = input.iterator(); o.hasNext();) {
+				T oo = o.next();
+				String s = oo.toString();
+				if (instruction.matches(s)) {
+					if (!instruction.isNegated())
+						result.add(oo);
+					o.remove();
+					used = true;
+				}
+			}
+			if (!used && unused != null)
+				unused.add(instruction);
+		}
+		return result;
+	}
+
+	public <T> Collection<T> reject(Collection<T> set) {
+		List<T> input = new ArrayList<T>(set);
+		List<T> result = new ArrayList<T>();
+
+		for (Instruction instruction : keySet()) {
+			for (Iterator<T> o = input.iterator(); o.hasNext();) {
+				T oo = o.next();
+				String s = oo.toString();
+				if (instruction.matches(s)) {
+					if (instruction.isNegated())
+						result.add(oo);
+					o.remove();
+				} else
+					result.add(oo);
+
+			}
+		}
+		return result;
+	}
+
+	public boolean matches(String value) {
+		if (size() == 0)
+			return true;
+
+		for (Instruction i : keySet()) {
+			if (i.matches(value)) {
+				if (i.isNegated())
+					return false; // we deny this one explicitly
+				return true; // we allow it explicitly
+			}
+		}
+		return false;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java
new file mode 100755
index 0000000..b864297
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Jar.java
@@ -0,0 +1,813 @@
+package aQute.bnd.osgi;
+
+import static aQute.lib.io.IO.*;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+import aQute.service.reporter.*;
+
+public class Jar implements Closeable {
+	public enum Compression {
+		DEFLATE, STORE
+	}
+
+	public static final Object[]			EMPTY_ARRAY	= new Jar[0];
+	final Map<String,Resource>				resources	= new TreeMap<String,Resource>();
+	final 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;
+	Compression								compression	= Compression.DEFLATE;
+	boolean									closed;
+
+	public Jar(String name) {
+		this.name = name;
+	}
+
+	public Jar(String name, File dirOrFile, Pattern doNotCopy) throws ZipException, IOException {
+		this(name);
+		source = dirOrFile;
+		if (dirOrFile.isDirectory())
+			FileResource.build(this, dirOrFile, doNotCopy);
+		else if (dirOrFile.isFile()) {
+			zipFile = ZipResource.build(this, dirOrFile);
+		} else {
+			throw new IllegalArgumentException("A Jar can only accept a valid file or directory: " + 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 f) throws IOException {
+		this(getName(f), f, null);
+	}
+
+	/**
+	 * 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();
+		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 Jar(String string, File file) throws ZipException, IOException {
+		this(string, file, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String toString() {
+		return "Jar:" + name;
+	}
+
+	public boolean putResource(String path, Resource resource) {
+		check();
+		return putResource(path, resource, true);
+	}
+
+	public boolean putResource(String path, Resource resource, boolean overwrite) {
+		check();
+		updateModified(resource.lastModified(), path);
+		while (path.startsWith("/"))
+			path = path.substring(1);
+
+		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) {
+		check();
+		if (resources == null)
+			return null;
+		return resources.get(path);
+	}
+
+	private String getDirectory(String path) {
+		check();
+		int n = path.lastIndexOf('/');
+		if (n < 0)
+			return "";
+
+		return path.substring(0, n);
+	}
+
+	public Map<String,Map<String,Resource>> getDirectories() {
+		check();
+		return directories;
+	}
+
+	public Map<String,Resource> getResources() {
+		check();
+		return resources;
+	}
+
+	public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
+		check();
+		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, entry.getValue(), overwrite);
+			}
+		}
+		return duplicates;
+	}
+
+	public Manifest getManifest() throws Exception {
+		check();
+		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) {
+		check();
+		return resources.containsKey(path);
+	}
+
+	public void setManifest(Manifest manifest) {
+		check();
+		manifestFirst = true;
+		this.manifest = manifest;
+	}
+
+	public void setManifest(File file) throws IOException {
+		check();
+		FileInputStream fin = new FileInputStream(file);
+		try {
+			Manifest m = new Manifest(fin);
+			setManifest(m);
+		}
+		finally {
+			fin.close();
+		}
+	}
+
+	public void write(File file) throws Exception {
+		check();
+		try {
+			OutputStream out = new FileOutputStream(file);
+			try {
+				write(out);
+			}
+			finally {
+				IO.close(out);
+			}
+			return;
+
+		}
+		catch (Exception t) {
+			file.delete();
+			throw t;
+		}
+	}
+
+	public void write(String file) throws Exception {
+		check();
+		write(new File(file));
+	}
+
+	public void write(OutputStream out) throws Exception {
+		check();
+		ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
+
+		switch (compression) {
+			case STORE :
+				jout.setMethod(ZipOutputStream.DEFLATED);
+				break;
+
+			default :
+				// default is DEFLATED
+		}
+
+		Set<String> done = new HashSet<String>();
+
+		Set<String> directories = new HashSet<String>();
+		if (doNotTouchManifest) {
+			Resource r = getResource("META-INF/MANIFEST.MF");
+			if (r != null) {
+				writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
+				done.add("META-INF/MANIFEST.MF");
+			}
+		} else
+			doManifest(done, jout);
+
+		for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
+			// Skip metainf contents
+			if (!done.contains(entry.getKey()))
+				writeResource(jout, directories, entry.getKey(), entry.getValue());
+		}
+		jout.finish();
+	}
+
+	private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
+		check();
+		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 Exception {
+		check();
+		writeManifest(getManifest(), out);
+	}
+
+	public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
+		if (manifest == null)
+			return;
+
+		manifest = clean(manifest);
+		outputManifest(manifest, out);
+	}
+
+	/**
+	 * Unfortunately we have to write our own manifest :-( because of a stupid
+	 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
+	 * it makes the bytes platform dependent. So the following code outputs the
+	 * manifest. A Manifest consists of
+	 * 
+	 * <pre>
+	 *   'Manifest-Version: 1.0\r\n'
+	 *   main-attributes *
+	 *   \r\n
+	 *   name-section
+	 *   
+	 *   main-attributes ::= attributes
+	 *   attributes      ::= key ': ' value '\r\n'
+	 *   name-section    ::= 'Name: ' name '\r\n' attributes
+	 * </pre>
+	 * 
+	 * Lines in the manifest should not exceed 72 bytes (! this is where the
+	 * manifest screwed up as well when 16 bit unicodes were used).
+	 * <p>
+	 * As a bonus, we can now sort the manifest!
+	 */
+	static byte[]	CONTINUE	= new byte[] {
+			'\r', '\n', ' '
+								};
+
+	/**
+	 * Main function to output a manifest properly in UTF-8.
+	 * 
+	 * @param manifest
+	 *            The manifest to output
+	 * @param out
+	 *            The output stream
+	 * @throws IOException
+	 *             when something fails
+	 */
+	public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
+		writeEntry(out, "Manifest-Version", "1.0");
+		attributes(manifest.getMainAttributes(), out);
+
+		TreeSet<String> keys = new TreeSet<String>();
+		for (Object o : manifest.getEntries().keySet())
+			keys.add(o.toString());
+
+		for (String key : keys) {
+			write(out, 0, "\r\n");
+			writeEntry(out, "Name", key);
+			attributes(manifest.getAttributes(key), out);
+		}
+		out.flush();
+	}
+
+	/**
+	 * Write out an entry, handling proper unicode and line length constraints
+	 */
+	private static void writeEntry(OutputStream out, String name, String value) throws IOException {
+		int n = write(out, 0, name + ": ");
+		write(out, n, value);
+		write(out, 0, "\r\n");
+	}
+
+	/**
+	 * Convert a string to bytes with UTF8 and then output in max 72 bytes
+	 * 
+	 * @param out
+	 *            the output string
+	 * @param i
+	 *            the current width
+	 * @param s
+	 *            the string to output
+	 * @return the new width
+	 * @throws IOException
+	 *             when something fails
+	 */
+	private static int write(OutputStream out, int i, String s) throws IOException {
+		byte[] bytes = s.getBytes("UTF8");
+		return write(out, i, bytes);
+	}
+
+	/**
+	 * Write the bytes but ensure that the line length does not exceed 72
+	 * characters. If it is more than 70 characters, we just put a cr/lf +
+	 * space.
+	 * 
+	 * @param out
+	 *            The output stream
+	 * @param width
+	 *            The nr of characters output in a line before this method
+	 *            started
+	 * @param bytes
+	 *            the bytes to output
+	 * @return the nr of characters in the last line
+	 * @throws IOException
+	 *             if something fails
+	 */
+	private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
+		int w = width;
+		for (int i = 0; i < bytes.length; i++) {
+			if (w >= 72) { // we need to add the \n\r!
+				out.write(CONTINUE);
+				w = 1;
+			}
+			out.write(bytes[i]);
+			w++;
+		}
+		return w;
+	}
+
+	/**
+	 * Output an Attributes map. We will sort this map before outputing.
+	 * 
+	 * @param value
+	 *            the attrbutes
+	 * @param out
+	 *            the output stream
+	 * @throws IOException
+	 *             when something fails
+	 */
+	private static void attributes(Attributes value, OutputStream out) throws IOException {
+		TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+		for (Map.Entry<Object,Object> entry : value.entrySet()) {
+			map.put(entry.getKey().toString(), entry.getValue().toString());
+		}
+
+		map.remove("Manifest-Version"); // get rid of
+		// manifest
+		// version
+		for (Map.Entry<String,String> entry : map.entrySet()) {
+			writeEntry(out, entry.getKey(), entry.getValue());
+		}
+	}
+
+	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(entry.getKey(), nice);
+			}
+		}
+		return result;
+	}
+
+	private static String clean(String s) {
+		if (s.indexOf('\n') < 0)
+			return s;
+
+		StringBuilder sb = new StringBuilder(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 Exception {
+		if (resource == null)
+			return;
+		try {
+			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("UTF-8"));
+			jout.putNextEntry(ze);
+			resource.write(jout);
+			jout.closeEntry();
+		}
+		catch (Exception e) {
+			throw new Exception("Problem writing resource " + path, e);
+		}
+	}
+
+	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, Instruction filter) {
+		return addAll(sub, filter, "");
+	}
+
+	/**
+	 * Add all the resources in the given jar that match the given filter.
+	 * 
+	 * @param sub
+	 *            the jar
+	 * @param filter
+	 *            a pattern that should match the resoures in sub to be added
+	 */
+	public boolean addAll(Jar sub, Instruction filter, String destination) {
+		check();
+		boolean dupl = false;
+		for (String name : sub.getResources().keySet()) {
+			if ("META-INF/MANIFEST.MF".equals(name))
+				continue;
+
+			if (filter == null || filter.matches(name) != filter.isNegated())
+				dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
+		}
+		return dupl;
+	}
+
+	public void close() {
+		this.closed = true;
+		if (zipFile != null)
+			try {
+				zipFile.close();
+			}
+			catch (IOException e) {
+				// Ignore
+			}
+		resources.clear();
+		directories.clear();
+		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) {
+		check();
+		return directories.get(path) != null;
+	}
+
+	public List<String> getPackages() {
+		check();
+		List<String> list = new ArrayList<String>(directories.size());
+
+		for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
+			if (i.getValue() != null) {
+				String path = i.getKey();
+				String pack = path.replace('/', '.');
+				list.add(pack);
+			}
+		}
+		return list;
+	}
+
+	public File getSource() {
+		check();
+		return source;
+	}
+
+	public boolean addAll(Jar src) {
+		check();
+		return addAll(src, null);
+	}
+
+	public boolean rename(String oldPath, String newPath) {
+		check();
+		Resource resource = remove(oldPath);
+		if (resource == null)
+			return false;
+
+		return putResource(newPath, resource);
+	}
+
+	public Resource remove(String path) {
+		check();
+		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;
+	}
+
+	/**
+	 * Calculate the checksums and set them in the manifest.
+	 */
+
+	public void calcChecksums(String algorithms[]) throws Exception {
+		check();
+		if (algorithms == null)
+			algorithms = new String[] {
+					"SHA", "MD5"
+			};
+
+		Manifest m = getManifest();
+		if (m == null) {
+			m = new Manifest();
+			setManifest(m);
+		}
+
+		MessageDigest digests[] = new MessageDigest[algorithms.length];
+		int n = 0;
+		for (String algorithm : algorithms)
+			digests[n++] = MessageDigest.getInstance(algorithm);
+
+		byte buffer[] = new byte[30000];
+
+		for (Map.Entry<String,Resource> entry : resources.entrySet()) {
+
+			// Skip the manifest
+			if (entry.getKey().equals("META-INF/MANIFEST.MF"))
+				continue;
+
+			Resource r = entry.getValue();
+			Attributes attributes = m.getAttributes(entry.getKey());
+			if (attributes == null) {
+				attributes = new Attributes();
+				getManifest().getEntries().put(entry.getKey(), attributes);
+			}
+			InputStream in = r.openInputStream();
+			try {
+				for (MessageDigest d : digests)
+					d.reset();
+				int size = in.read(buffer);
+				while (size > 0) {
+					for (MessageDigest d : digests)
+						d.update(buffer, 0, size);
+					size = in.read(buffer);
+				}
+			}
+			finally {
+				in.close();
+			}
+			for (MessageDigest d : digests)
+				attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
+		}
+	}
+
+	Pattern	BSN	= Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
+
+	public String getBsn() throws Exception {
+		check();
+		Manifest m = getManifest();
+		if (m == null)
+			return null;
+
+		String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+		if (s == null)
+			return null;
+
+		Matcher matcher = BSN.matcher(s);
+		if (matcher.matches()) {
+			return matcher.group(1);
+		}
+		return null;
+	}
+
+	public String getVersion() throws Exception {
+		check();
+		Manifest m = getManifest();
+		if (m == null)
+			return null;
+
+		String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+		if (s == null)
+			return null;
+
+		return s.trim();
+	}
+
+	/**
+	 * Expand the JAR file to a directory.
+	 * 
+	 * @param dir
+	 *            the dst directory, is not required to exist
+	 * @throws Exception
+	 *             if anything does not work as expected.
+	 */
+	public void expand(File dir) throws Exception {
+		check();
+		dir = dir.getAbsoluteFile();
+		dir.mkdirs();
+		if (!dir.isDirectory()) {
+			throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
+		}
+
+		for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
+			File f = getFile(dir, entry.getKey());
+			f.getParentFile().mkdirs();
+			IO.copy(entry.getValue().openInputStream(), f);
+		}
+	}
+
+	/**
+	 * Make sure we have a manifest
+	 * 
+	 * @throws Exception
+	 */
+	public void ensureManifest() throws Exception {
+		if (getManifest() != null)
+			return;
+		manifest = new Manifest();
+	}
+
+	/**
+	 * Answer if the manifest was the first entry
+	 */
+
+	public boolean isManifestFirst() {
+		return manifestFirst;
+	}
+
+	public void copy(Jar srce, String path, boolean overwrite) {
+		check();
+		addDirectory(srce.getDirectories().get(path), overwrite);
+	}
+
+	public void setCompression(Compression compression) {
+		this.compression = compression;
+	}
+
+	public Compression hasCompression() {
+		return this.compression;
+	}
+
+	void check() {
+		if (closed)
+			throw new RuntimeException("Already closed " + name);
+	}
+
+	/**
+	 * Return a data uri from the JAR. The data must be less than 32k
+	 * 
+	 * @param jar
+	 *            The jar to load the data from
+	 * @param path
+	 *            the path in the jar
+	 * @param mime
+	 *            the mime type
+	 * @return a URI or null if conversion could not take place
+	 */
+
+	public URI getDataURI(String path, String mime, int max) throws Exception {
+		Resource r = getResource(path);
+
+		if (r.size() >= max || r.size() <= 0)
+			return null;
+
+		byte[] data = new byte[(int) r.size()];
+		DataInputStream din = new DataInputStream(r.openInputStream());
+		try {
+			din.readFully(data);
+			String encoded = Base64.encodeBase64(data);
+			return new URI("data:" + mime + ";base64," + encoded);
+		}
+		finally {
+			din.close();
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/JarResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/JarResource.java
new file mode 100755
index 0000000..2fb5c54
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/JarResource.java
@@ -0,0 +1,35 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+public class JarResource extends WriteResource {
+	Jar		jar;
+	long	size	= -1;
+
+	public JarResource(Jar jar) {
+		this.jar = jar;
+	}
+
+	public long lastModified() {
+		return jar.lastModified();
+	}
+
+	public void write(OutputStream out) throws Exception {
+		try {
+			jar.write(out);
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	public Jar getJar() {
+		return jar;
+	}
+
+	public String toString() {
+		return ":" + jar.getName() + ":";
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java
new file mode 100755
index 0000000..b6a6087
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Macro.java
@@ -0,0 +1,975 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.libg.sed.*;
+
+/**
+ * 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. Add POSIX macros: ${#parameter} String
+ * length. ${parameter%word} Remove smallest suffix pattern. ${parameter%%word}
+ * Remove largest suffix pattern. ${parameter#word} Remove smallest prefix
+ * pattern. ${parameter##word} Remove largest prefix pattern.
+ */
+public class Macro implements Replacer {
+	Processor	domain;
+	Object		targets[];
+	boolean		flattening;
+
+	public Macro(Processor domain, Object... targets) {
+		this.domain = domain;
+		this.targets = targets;
+		if (targets != null) {
+			for (Object o : targets) {
+				assert o != null;
+			}
+		}
+	}
+
+	public String process(String line, Processor source) {
+		return process(line, new Link(source, null, line));
+	}
+
+	String process(String line, Link link) {
+		StringBuilder sb = new StringBuilder();
+		process(line, 0, '\u0000', '\u0000', sb, link);
+		return sb.toString();
+	}
+
+	int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
+		StringBuilder line = new StringBuilder(org);
+		int nesting = 1;
+
+		StringBuilder variable = new StringBuilder();
+		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;
+				}
+			} else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
+				// Found the sequence ./
+				if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
+					// make sure it is preceded by whitespace or starts at begin
+					index++;
+					variable.append(domain.getBase().getAbsolutePath());
+					variable.append('/');
+					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) {
+				Processor source = domain;
+				String value = null;
+
+				if (key.indexOf(';') < 0) {
+					Instruction ins = new Instruction(key);
+					if (!ins.isLiteral()) {
+						SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
+						StringBuilder sb = new StringBuilder();
+						String del = "";
+						for (String k : sortedList) {
+							if (ins.matches(k)) {
+								String v = replace(k, new Link(source, link, key));
+								if (v != null) {
+									sb.append(del);
+									del = ",";
+									sb.append(v);
+								}
+							}
+						}
+						return sb.toString();
+					}
+				}
+				while (value == null && source != null) {
+					value = source.getProperties().getProperty(key);
+					source = source.getParent();
+				}
+
+				if (value != null)
+					return process(value, new Link(source, link, key));
+
+				value = doCommands(key, link);
+				if (value != null)
+					return process(value, new Link(source, link, key));
+
+				if (key != null && key.trim().length() > 0) {
+					value = System.getProperty(key);
+					if (value != null)
+						return value;
+				}
+				if (!flattening && !key.equals("@"))
+					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, Link source) {
+		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("\\\\;", ";");
+
+		if (args[0].startsWith("^")) {
+			String varname = args[0].substring(1).trim();
+
+			Processor parent = source.start.getParent();
+			if (parent != null)
+				return parent.getProperty(varname);
+			return null;
+		}
+
+		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.err.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) {
+				if (e.getCause() instanceof IllegalArgumentException) {
+					domain.error("%s, for cmd: %s, arguments; %s", e.getMessage(), method, Arrays.toString(args));
+				} else {
+					domain.warning("Exception in replace: " + e.getCause());
+					e.getCause().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 _pathseparator(@SuppressWarnings("unused") String args[]) {
+		return File.pathSeparator;
+	}
+
+	public String _separator(@SuppressWarnings("unused") String args[]) {
+		return File.separator;
+	}
+
+	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.equalsIgnoreCase("false"))
+			if (condition.length() != 0)
+				return args[2];
+
+		if (args.length > 3)
+			return args[3];
+		return "";
+	}
+
+	public String _now(@SuppressWarnings("unused") String args[]) {
+		return new Date().toString();
+	}
+
+	public final 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");
+
+		return domain.getProperty(args[1], "");
+	}
+
+	/**
+	 * 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*");
+		StringBuilder sb = new StringBuilder();
+		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 = Boolean.valueOf(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;
+		}
+		String del = "";
+		StringBuilder sb = new StringBuilder();
+		for (int i = 1; i < args.length; i++) {
+			File f = domain.getFile(args[i]);
+			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;
+		}
+		String del = "";
+		StringBuilder sb = new StringBuilder();
+		for (int i = 1; i < args.length; i++) {
+			File f = domain.getFile(args[i]);
+			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;
+		}
+		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;
+		}
+		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();
+		TimeZone tz = TimeZone.getTimeZone("UTC");
+
+		if (args.length > 1) {
+			format = args[1];
+		}
+		if (args.length > 2) {
+			tz = TimeZone.getTimeZone(args[2]);
+		}
+		if (args.length > 3) {
+			now = Long.parseLong(args[3]);
+		}
+		if (args.length > 4) {
+			domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
+		}
+
+		SimpleDateFormat sdf = new SimpleDateFormat(format);
+		sdf.setTimeZone(tz);
+
+		return sdf.format(new Date(now));
+	}
+
+	/**
+	 * Wildcard a directory. The lists can contain Instruction that are matched
+	 * against the given directory ${lsr;<dir>;<list>(;<list>)*}
+	 * ${lsa;<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 = domain.getFile(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);
+
+		List<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));
+
+		for (int i = 2; i < args.length; i++) {
+			Instructions filters = new Instructions(args[i]);
+			filters.select(files, true);
+		}
+
+		List<String> result = new ArrayList<String>();
+		for (File file : files)
+			result.add(relative ? file.getName() : file.getAbsolutePath());
+
+		return Processor.join(result, ",");
+	}
+
+	public String _currenttime(@SuppressWarnings("unused") 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
+	 * &tilde;           discard
+	 * 
+	 * ==+      = maintain major, minor, increment micro, discard qualifier
+	 * &tilde;&tilde;&tilde;=     = just get the qualifier
+	 * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
+	 * </pre>
+	 * 
+	 * @param args
+	 * @return
+	 */
+	final static String		MASK_STRING			= "[\\-+=~0123456789]{0,3}[=~]?";
+	final static Pattern	MASK				= Pattern.compile(MASK_STRING);
+	final static String		_versionHelp		= "${version;<mask>;<version>}, modify a version\n"
+														+ "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+														+ "M ::= '+' | '-' | MQ\n" + "MQ ::= '~' | '='";
+	final static Pattern	_versionPattern[]	= new Pattern[] {
+			null, null, MASK, Verifier.VERSION
+												};
+
+	public String _version(String args[]) {
+		verifyCommand(args, _versionHelp, null, 2, 3);
+
+		String mask = args[1];
+
+		Version version = null;
+		if (args.length >= 3)
+			version = new Version(args[2]);
+
+		return version(version, mask);
+	}
+
+	String version(Version version, String mask) {
+		if (version == null) {
+			String v = domain.getProperty("@");
+			if (v == null) {
+				domain.error(
+						"No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
+						mask);
+				v = "0";
+			}
+			version = new Version(v);
+		}
+
+		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();
+	}
+
+	/**
+	 * Schortcut for version policy
+	 * 
+	 * <pre>
+	 * -provide-policy : ${policy;[==,=+)}
+	 * -consume-policy : ${policy;[==,+)}
+	 * </pre>
+	 * 
+	 * @param args
+	 * @return
+	 */
+
+	static Pattern	RANGE_MASK		= Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
+	static String	_rangeHelp		= "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
+											+ "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+											+ "M ::= '+' | '-' | MQ\n"
+											+ "MQ ::= '~' | '='";
+	static Pattern	_rangePattern[]	= new Pattern[] {
+			null, RANGE_MASK
+									};
+
+	public String _range(String args[]) {
+		verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
+		Version version = null;
+		if (args.length >= 3)
+			version = new Version(args[2]);
+		else {
+			String v = domain.getProperty("@");
+			if (v == null)
+				return null;
+			version = new Version(v);
+		}
+		String spec = args[1];
+
+		Matcher m = RANGE_MASK.matcher(spec);
+		m.matches();
+		String floor = m.group(1);
+		String floorMask = m.group(2);
+		String ceilingMask = m.group(3);
+		String ceiling = m.group(4);
+
+		String left = version(version, floorMask);
+		String right = version(version, ceilingMask);
+		StringBuilder sb = new StringBuilder();
+		sb.append(floor);
+		sb.append(left);
+		sb.append(",");
+		sb.append(right);
+		sb.append(ceiling);
+
+		String s = sb.toString();
+		VersionRange vr = new VersionRange(s);
+		if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
+			domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
+		}
+		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_internal(boolean allowFail, String args[]) throws Exception {
+		verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "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 = IO.collect(process.getInputStream(), "UTF-8");
+		int exitValue = process.waitFor();
+		if (exitValue != 0)
+			return exitValue + "";
+
+		if (!allowFail && (exitValue != 0)) {
+			domain.error("System command " + command + " failed with " + exitValue);
+		}
+		return s.trim();
+	}
+
+	public String _system(String args[]) throws Exception {
+		return system_internal(false, args);
+	}
+
+	public String _system_allow_fail(String args[]) throws Exception {
+		String result = "";
+		try {
+			result = system_internal(true, args);
+		}
+		catch (Throwable t) {
+			/* ignore */
+		}
+		return result;
+	}
+
+	public String _env(String args[]) {
+		verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);
+
+		try {
+			return System.getenv(args[1]);
+		}
+		catch (Throwable t) {
+			return null;
+		}
+	}
+
+	/**
+	 * 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()) {
+			return IO.collect(f);
+		} else if (f.isDirectory()) {
+			return Arrays.toString(f.list());
+		} else {
+			try {
+				URL url = new URL(args[1]);
+				return IO.collect(url, "UTF-8");
+			}
+			catch (MalformedURLException mfue) {
+				// Ignore here
+			}
+			return null;
+		}
+	}
+
+	public static void verifyCommand(String args[], @SuppressWarnings("unused") 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; i++) {
+				if (patterns[i] != null) {
+					Matcher m = patterns[i].matcher(args[i]);
+					if (!m.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;
+		Processor	start;
+
+		public Link(Processor start, Link previous, String key) {
+			this.previous = previous;
+			this.key = key;
+			this.start = start;
+		}
+
+		public boolean contains(String key) {
+			if (this.key.equals(key))
+				return true;
+
+			if (previous == null)
+				return false;
+
+			return previous.contains(key);
+		}
+
+		public String toString() {
+			StringBuilder sb = new StringBuilder();
+			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();
+			Properties source = domain.getProperties();
+			for (Enumeration< ? > e = source.propertyNames(); e.hasMoreElements();) {
+				String key = (String) e.nextElement();
+				if (!key.startsWith("_"))
+					if (key.startsWith("-"))
+						flattened.put(key, source.getProperty(key));
+					else
+						flattened.put(key, process(source.getProperty(key)));
+			}
+			return flattened;
+		}
+		finally {
+			flattening = false;
+		}
+	}
+
+	public final static String	_fileHelp	= "${file;<base>;<paths>...}, create correct OS dependent path";
+
+	public String _osfile(String args[]) {
+		verifyCommand(args, _fileHelp, null, 3, 3);
+		File base = new File(args[1]);
+		File f = Processor.getFile(base, args[2]);
+		return f.getAbsolutePath();
+	}
+
+	public String _path(String args[]) {
+		List<String> list = new ArrayList<String>();
+		for (int i = 1; i < args.length; i++) {
+			list.addAll(Processor.split(args[i]));
+		}
+		return Processor.join(list, File.pathSeparator);
+	}
+
+	public static Properties getParent(Properties p) {
+		try {
+			Field f = Properties.class.getDeclaredField("defaults");
+			f.setAccessible(true);
+			return (Properties) f.get(p);
+		}
+		catch (Exception e) {
+			Field[] fields = Properties.class.getFields();
+			System.err.println(Arrays.toString(fields));
+			return null;
+		}
+	}
+
+	public String process(String line) {
+		return process(line, domain);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/OpCodes.java b/bundleplugin/src/main/java/aQute/bnd/osgi/OpCodes.java
new file mode 100755
index 0000000..4299556
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/OpCodes.java
@@ -0,0 +1,1250 @@
+package aQute.bnd.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/bnd/osgi/Packages.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Packages.java
new file mode 100644
index 0000000..53a92da
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Packages.java
@@ -0,0 +1,232 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+
+import aQute.bnd.header.*;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+
+public class Packages implements Map<PackageRef,Attrs> {
+	private LinkedHashMap<PackageRef,Attrs>	map;
+	static Map<PackageRef,Attrs>			EMPTY	= Collections.emptyMap();
+
+	public Packages(Packages other) {
+		if (other.map != null) {
+			map = new LinkedHashMap<Descriptors.PackageRef,Attrs>(other.map);
+		}
+	}
+
+	public Packages() {}
+
+	public void clear() {
+		if (map != null)
+			map.clear();
+	}
+
+	public boolean containsKey(PackageRef name) {
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	@Deprecated
+	public boolean containsKey(Object name) {
+		assert name instanceof PackageRef;
+		if (map == null)
+			return false;
+
+		return map.containsKey(name);
+	}
+
+	public boolean containsValue(Attrs value) {
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	@Deprecated
+	public boolean containsValue(Object value) {
+		assert value instanceof Attrs;
+		if (map == null)
+			return false;
+
+		return map.containsValue(value);
+	}
+
+	public Set<java.util.Map.Entry<PackageRef,Attrs>> entrySet() {
+		if (map == null)
+			return EMPTY.entrySet();
+
+		return map.entrySet();
+	}
+
+	@Deprecated
+	public Attrs get(Object key) {
+		assert key instanceof PackageRef;
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public Attrs get(PackageRef key) {
+		if (map == null)
+			return null;
+
+		return map.get(key);
+	}
+
+	public boolean isEmpty() {
+		return map == null || map.isEmpty();
+	}
+
+	public Set<PackageRef> keySet() {
+		if (map == null)
+			return EMPTY.keySet();
+
+		return map.keySet();
+	}
+
+	public Attrs put(PackageRef ref) {
+		Attrs attrs = get(ref);
+		if (attrs != null)
+			return attrs;
+
+		attrs = new Attrs();
+		put(ref, attrs);
+		return attrs;
+	}
+
+	public Attrs put(PackageRef key, Attrs value) {
+		if (map == null)
+			map = new LinkedHashMap<PackageRef,Attrs>();
+
+		return map.put(key, value);
+	}
+
+	public void putAll(Map< ? extends PackageRef, ? extends Attrs> map) {
+		if (this.map == null) {
+			if (map.isEmpty())
+				return;
+			this.map = new LinkedHashMap<PackageRef,Attrs>();
+		}
+		this.map.putAll(map);
+	}
+
+	public void putAllIfAbsent(Map<PackageRef, ? extends Attrs> map) {
+		for (Map.Entry<PackageRef, ? extends Attrs> entry : map.entrySet()) {
+			if (!containsKey(entry.getKey()))
+				put(entry.getKey(), entry.getValue());
+		}
+	}
+
+	@Deprecated
+	public Attrs remove(Object var0) {
+		assert var0 instanceof PackageRef;
+		if (map == null)
+			return null;
+
+		return map.remove(var0);
+	}
+
+	public Attrs remove(PackageRef var0) {
+		if (map == null)
+			return null;
+		return map.remove(var0);
+	}
+
+	public int size() {
+		if (map == null)
+			return 0;
+		return map.size();
+	}
+
+	public Collection<Attrs> values() {
+		if (map == null)
+			return EMPTY.values();
+
+		return map.values();
+	}
+
+	public Attrs getByFQN(String s) {
+		if (map == null)
+			return null;
+
+		for (Map.Entry<PackageRef,Attrs> pr : map.entrySet()) {
+			if (pr.getKey().getFQN().equals(s))
+				return pr.getValue();
+		}
+		return null;
+	}
+
+	public Attrs getByBinaryName(String s) {
+		if (map == null)
+			return null;
+
+		for (Map.Entry<PackageRef,Attrs> pr : map.entrySet()) {
+			if (pr.getKey().getBinary().equals(s))
+				pr.getValue();
+		}
+		return null;
+	}
+
+	public boolean containsFQN(String s) {
+		return getByFQN(s) != null;
+	}
+
+	public boolean containsBinaryName(String s) {
+		return getByFQN(s) != null;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		append(sb);
+		return sb.toString();
+	}
+
+	public void append(StringBuilder sb) {
+		String del = "";
+		for (Map.Entry<PackageRef,Attrs> s : entrySet()) {
+			sb.append(del);
+			sb.append(s.getKey());
+			if (!s.getValue().isEmpty()) {
+				sb.append(';');
+				s.getValue().append(sb);
+			}
+			del = ",";
+		}
+	}
+
+	public void merge(PackageRef ref, boolean unique, Attrs... attrs) {
+		if (unique) {
+			while (containsKey(ref))
+				ref = ref.getDuplicate();
+		}
+
+		Attrs org = put(ref);
+		for (Attrs a : attrs) {
+			if (a != null)
+				org.putAll(a);
+		}
+	}
+
+	public Attrs get(PackageRef packageRef, Attrs deflt) {
+		Attrs mine = get(packageRef);
+		if (mine != null)
+			return mine;
+
+		return deflt;
+	}
+
+	@Deprecated
+	public boolean equals(Object other) {
+		return super.equals(other);
+	}
+
+	@Deprecated
+	public int hashCode() {
+		return super.hashCode();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/PreprocessResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/PreprocessResource.java
new file mode 100644
index 0000000..a1b5095
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/PreprocessResource.java
@@ -0,0 +1,45 @@
+package aQute.bnd.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;
+		setExtra(resource.getExtra());
+	}
+
+	protected byte[] getBytes() throws Exception {
+		ByteArrayOutputStream bout = new ByteArrayOutputStream(2000);
+		OutputStreamWriter osw = new OutputStreamWriter(bout, Constants.DEFAULT_CHARSET);
+		PrintWriter pw = new PrintWriter(osw);
+		InputStream in = null;
+		BufferedReader rdr = null;
+		try {
+			in = resource.openInputStream();
+			rdr = new BufferedReader(new InputStreamReader(in, "UTF8"));
+			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 {
+			if (rdr != null) {
+				rdr.close();
+			}
+			if (in != null) {
+				in.close();
+			}
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
new file mode 100755
index 0000000..17b4df3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Processor.java
@@ -0,0 +1,1666 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.header.*;
+import aQute.bnd.service.*;
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.libg.generics.*;
+import aQute.service.reporter.*;
+
+public class Processor extends Domain implements Reporter, Registry, Constants, Closeable {
+
+	static ThreadLocal<Processor>	current			= new ThreadLocal<Processor>();
+	static ExecutorService			executor		= Executors.newCachedThreadPool();
+	static Random					random			= new Random();
+
+	// TODO handle include files out of date
+	// TODO make splitter skip eagerly whitespace so trim is not necessary
+	public final static String		LIST_SPLITTER	= "\\s*,\\s*";
+	final List<String>				errors			= new ArrayList<String>();
+	final List<String>				warnings		= new ArrayList<String>();
+	final Set<Object>				basicPlugins	= new HashSet<Object>();
+	private final Set<Closeable>	toBeClosed		= new HashSet<Closeable>();
+	Set<Object>						plugins;
+
+	boolean							pedantic;
+	boolean							trace;
+	boolean							exceptions;
+	boolean							fileMustExist	= true;
+
+	private File					base			= new File("").getAbsoluteFile();
+
+	Properties						properties;
+	private Macro					replacer;
+	private long					lastModified;
+	private File					propertiesFile;
+	private boolean					fixup			= true;
+	long							modified;
+	Processor						parent;
+	List<File>						included;
+
+	CL								pluginLoader;
+	Collection<String>				filter;
+	HashSet<String>					missingCommand;
+
+	public Processor() {
+		properties = new Properties();
+	}
+
+	public Processor(Properties parent) {
+		properties = new Properties(parent);
+	}
+
+	public Processor(Processor child) {
+		this(child.properties);
+		this.parent = child;
+	}
+
+	public void setParent(Processor processor) {
+		this.parent = processor;
+		Properties ext = new Properties(processor.properties);
+		ext.putAll(this.properties);
+		this.properties = ext;
+	}
+
+	public Processor getParent() {
+		return parent;
+	}
+
+	public Processor getTop() {
+		if (parent == null)
+			return this;
+		return parent.getTop();
+	}
+
+	public void getInfo(Reporter processor, String prefix) {
+		if (isFailOk())
+			addAll(warnings, processor.getErrors(), prefix);
+		else
+			addAll(errors, processor.getErrors(), prefix);
+		addAll(warnings, processor.getWarnings(), prefix);
+
+		processor.getErrors().clear();
+		processor.getWarnings().clear();
+	}
+
+	public void getInfo(Reporter processor) {
+		getInfo(processor, "");
+	}
+
+	private <T> void addAll(List<String> to, List< ? extends T> from, String prefix) {
+		for (T x : from) {
+			to.add(prefix + x);
+		}
+	}
+
+	/**
+	 * A processor can mark itself current for a thread.
+	 * 
+	 * @return
+	 */
+	private Processor current() {
+		Processor p = current.get();
+		if (p == null)
+			return this;
+		return p;
+	}
+
+	public SetLocation warning(String string, Object... args) {
+		Processor p = current();
+		String s = formatArrays(string, args);
+		if (!p.warnings.contains(s))
+			p.warnings.add(s);
+		p.signal();
+		return location(s);
+	}
+
+	public SetLocation error(String string, Object... args) {
+		Processor p = current();
+		try {
+			if (p.isFailOk())
+				return p.warning(string, args);
+			else {
+				String s = formatArrays(string, args == null ? new Object[0] : args);
+				if (!p.errors.contains(s))
+					p.errors.add(s);
+				return location(s);
+			}
+		}
+		finally {
+			p.signal();
+		}
+	}
+
+	public void progress(float progress, String format, Object... args) {
+		format = String.format("[%2d] %s", (int)progress, format);
+		trace(format, args);
+	}
+
+	public void progress(String format, Object... args) {
+		progress(-1f, format, args);
+	}
+
+	public SetLocation exception(Throwable t, String format, Object... args) {
+		return error(format, t, args);
+	}
+
+	public SetLocation error(String string, Throwable t, Object... args) {
+		Processor p = current();
+		try {
+			if (p.exceptions)
+				t.printStackTrace();
+			if (p.isFailOk()) {
+				return p.warning(string + ": " + t, args);
+			}
+			else {
+				p.errors.add("Exception: " + t.getMessage());
+				String s = formatArrays(string, args == null ? new Object[0] : args);
+				if (!p.errors.contains(s))
+					p.errors.add(s);
+				return location(s);
+			}
+		}
+		finally {
+			p.signal();
+		}
+	}
+
+	public void signal() {}
+
+	public List<String> getWarnings() {
+		return warnings;
+	}
+
+	public List<String> getErrors() {
+		return errors;
+	}
+
+	/**
+	 * Standard OSGi header parser.
+	 * 
+	 * @param value
+	 * @return
+	 */
+	static public Parameters parseHeader(String value, Processor logger) {
+		return new Parameters(value, logger);
+	}
+
+	public Parameters parseHeader(String value) {
+		return new Parameters(value, this);
+	}
+
+	public void addClose(Closeable jar) {
+		assert jar != null;
+		toBeClosed.add(jar);
+	}
+
+	public void removeClose(Closeable jar) {
+		assert jar != null;
+		toBeClosed.remove(jar);
+	}
+
+	public boolean isPedantic() {
+		return current().pedantic;
+	}
+
+	public void setPedantic(boolean pedantic) {
+		this.pedantic = pedantic;
+	}
+
+	public void use(Processor reporter) {
+		setPedantic(reporter.isPedantic());
+		setTrace(reporter.isTrace());
+		setBase(reporter.getBase());
+		setFailOk(reporter.isFailOk());
+	}
+
+	public static File getFile(File base, String file) {
+		return IO.getFile(base, file);
+	}
+
+	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>();
+		Set<Object> all = getPlugins();
+		for (Object plugin : all) {
+			if (clazz.isInstance(plugin))
+				l.add(clazz.cast(plugin));
+		}
+		return l;
+	}
+
+	/**
+	 * Returns the first plugin it can find of the given type.
+	 * 
+	 * @param <T>
+	 * @param clazz
+	 * @return
+	 */
+	public <T> T getPlugin(Class<T> clazz) {
+		Set<Object> all = getPlugins();
+		for (Object plugin : all) {
+			if (clazz.isInstance(plugin))
+				return clazz.cast(plugin);
+		}
+		return null;
+	}
+
+	/**
+	 * 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 synchronized Set<Object> getPlugins() {
+		if (this.plugins != null)
+			return this.plugins;
+
+		missingCommand = new HashSet<String>();
+		Set<Object> list = new LinkedHashSet<Object>();
+
+		// The owner of the plugin is always in there.
+		list.add(this);
+		setTypeSpecificPlugins(list);
+
+		if (parent != null)
+			list.addAll(parent.getPlugins());
+
+		// We only use plugins now when they are defined on our level
+		// and not if it is in our parent. We inherit from our parent
+		// through the previous block.
+
+		if (properties.containsKey(PLUGIN)) {
+			String spe = getProperty(PLUGIN);
+			if (spe.equals(NONE))
+				return new LinkedHashSet<Object>();
+
+			String pluginPath = getProperty(PLUGINPATH);
+			loadPlugins(list, spe, pluginPath);
+		}
+
+		return this.plugins = list;
+	}
+
+	/**
+	 * @param list
+	 * @param spe
+	 */
+	protected void loadPlugins(Set<Object> list, String spe, String pluginPath) {
+		Parameters plugins = new Parameters(spe);
+		CL loader = getLoader();
+
+		// First add the plugin-specific paths from their path: directives
+		for (Entry<String,Attrs> entry : plugins.entrySet()) {
+			String key = removeDuplicateMarker(entry.getKey());
+			String path = entry.getValue().get(PATH_DIRECTIVE);
+			if (path != null) {
+				String parts[] = path.split("\\s*,\\s*");
+				try {
+					for (String p : parts) {
+						File f = getFile(p).getAbsoluteFile();
+						loader.add(f.toURI().toURL());
+					}
+				}
+				catch (Exception e) {
+					error("Problem adding path %s to loader for plugin %s. Exception: (%s)", path, key, e);
+				}
+			}
+		}
+
+		// Next add -pluginpath entries
+		if (pluginPath != null && pluginPath.length() > 0) {
+			StringTokenizer tokenizer = new StringTokenizer(pluginPath, ",");
+			while (tokenizer.hasMoreTokens()) {
+				String path = tokenizer.nextToken().trim();
+				try {
+					File f = getFile(path).getAbsoluteFile();
+					loader.add(f.toURI().toURL());
+				}
+				catch (Exception e) {
+					error("Problem adding path %s from global plugin path. Exception: %s", path, e);
+				}
+			}
+		}
+
+		// Load the plugins
+		for (Entry<String,Attrs> entry : plugins.entrySet()) {
+			String key = entry.getKey();
+
+			try {
+				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);
+
+				try {
+					Class< ? > c = loader.loadClass(key);
+					Object plugin = c.newInstance();
+					customize(plugin, entry.getValue());
+					list.add(plugin);
+				}
+				catch (Throwable t) {
+					// We can defer the error if the plugin specifies
+					// a command name. In that case, we'll verify that
+					// a bnd file does not contain any references to a
+					// plugin
+					// command. The reason this feature was added was
+					// to compile plugin classes with the same build.
+					String commands = entry.getValue().get(COMMAND_DIRECTIVE);
+					if (commands == null)
+						error("Problem loading the plugin: %s exception: (%s)", key, t);
+					else {
+						Collection<String> cs = split(commands);
+						missingCommand.addAll(cs);
+					}
+				}
+			}
+			catch (Throwable e) {
+				error("Problem loading the plugin: %s exception: (%s)", key, e);
+			}
+		}
+	}
+
+	protected void setTypeSpecificPlugins(Set<Object> list) {
+		list.add(executor);
+		list.add(random);
+		list.addAll(basicPlugins);
+	}
+
+	/**
+	 * @param plugin
+	 * @param entry
+	 */
+	protected <T> T customize(T plugin, Attrs map) {
+		if (plugin instanceof Plugin) {
+			if (map != null)
+				((Plugin) plugin).setProperties(map);
+
+			((Plugin) plugin).setReporter(this);
+		}
+		if (plugin instanceof RegistryPlugin) {
+			((RegistryPlugin) plugin).setRegistry(this);
+		}
+		return plugin;
+	}
+
+	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) {
+		Processor p = current();
+		if (p.trace) {
+			System.err.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 LinkedHashMap<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.clear();
+	}
+
+	public String _basedir(@SuppressWarnings("unused") 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);
+		this.properties.putAll(properties);
+	}
+
+	public void addProperties(File file) throws Exception {
+		addIncluded(file);
+		Properties p = loadProperties(file);
+		setProperties(p);
+	}
+
+	public void addProperties(Map< ? , ? > properties) {
+		for (Entry< ? , ? > entry : properties.entrySet()) {
+			setProperty(entry.getKey().toString(), entry.getValue() + "");
+		}
+	}
+
+	public synchronized void addIncluded(File file) {
+		if (included == null)
+			included = new ArrayList<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
+	 * @throws IOException
+	 */
+
+	private void doIncludes(File ubase, Properties p) {
+		String includes = p.getProperty(INCLUDE);
+		if (includes != null) {
+			includes = getReplacer().process(includes);
+			p.remove(INCLUDE);
+			Collection<String> clauses = new Parameters(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() && fileMustExist) {
+						error("Included file " + file + (file.exists() ? " does not exist" : " is directory"));
+					} else
+						doIncludeFile(file, overwrite, p);
+				}
+				catch (Exception e) {
+					if (fileMustExist)
+						error("Error in processing included file: " + value, e);
+				}
+			}
+		}
+	}
+
+	/**
+	 * @param file
+	 * @param parent
+	 * @param done
+	 * @param overwrite
+	 * @throws FileNotFoundException
+	 * @throws IOException
+	 */
+	public void doIncludeFile(File file, boolean overwrite, Properties target) throws Exception {
+		doIncludeFile(file, overwrite, target, null);
+	}
+
+	/**
+	 * @param file
+	 * @param parent
+	 * @param done
+	 * @param overwrite
+	 * @param extensionName
+	 * @throws FileNotFoundException
+	 * @throws IOException
+	 */
+	public void doIncludeFile(File file, boolean overwrite, Properties target, String extensionName) throws Exception {
+		if (included != null && included.contains(file)) {
+			error("Cyclic or multiple include of " + file);
+		} else {
+			addIncluded(file);
+			updateModified(file.lastModified(), file.toString());
+			InputStream in = new FileInputStream(file);
+			try {
+				Properties sub;
+				if (file.getName().toLowerCase().endsWith(".mf")) {
+					sub = getManifestAsProperties(in);
+				} else
+					sub = loadProperties(in, file.getAbsolutePath());
+
+				doIncludes(file.getParentFile(), sub);
+				// make sure we do not override properties
+				for (Map.Entry< ? , ? > entry : sub.entrySet()) {
+					String key = (String) entry.getKey();
+					String value = (String) entry.getValue();
+
+					if (overwrite || !target.containsKey(key)) {
+						target.setProperty(key, value);
+					} else if (extensionName != null) {
+						String extensionKey = extensionName + "." + key;
+						if (!target.containsKey(extensionKey))
+							target.setProperty(extensionKey, value);
+					}
+				}
+			}
+			finally {
+				IO.close(in);
+			}
+		}
+	}
+
+	public void unsetProperty(String string) {
+		getProperties().remove(string);
+
+	}
+
+	public boolean refresh() {
+		plugins = null; // We always refresh our plugins
+
+		if (propertiesFile == null)
+			return false;
+
+		boolean changed = updateModified(propertiesFile.lastModified(), "properties file");
+		if (included != null) {
+			for (File file : included) {
+				if (changed)
+					break;
+
+				changed |= !file.exists() || updateModified(file.lastModified(), "include file: " + file);
+			}
+		}
+
+		if (changed) {
+			forceRefresh();
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * 
+	 */
+	public void forceRefresh() {
+		included = null;
+		properties.clear();
+		setProperties(propertiesFile, base);
+		propertiesChanged();
+	}
+
+	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.err.println("Loading properties " + propertiesFile);
+				long modified = propertiesFile.lastModified();
+				if (modified > System.currentTimeMillis() + 100) {
+					System.err.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) {
+		if (value == null)
+			return false;
+
+		return !"false".equalsIgnoreCase(value);
+	}
+
+	/**
+	 * Get a property without preprocessing it with a proper default
+	 * 
+	 * @param headerName
+	 * @param deflt
+	 * @return
+	 */
+
+	public String getUnprocessedProperty(String key, String deflt) {
+		return getProperties().getProperty(key, deflt);
+	}
+
+	/**
+	 * Get a property with preprocessing it with a proper default
+	 * 
+	 * @param headerName
+	 * @param deflt
+	 * @return
+	 */
+	public String getProperty(String key, String deflt) {
+		String value = null;
+
+		Instruction ins = new Instruction(key);
+		if (!ins.isLiteral()) {
+			// Handle a wildcard key, make sure they're sorted
+			// for consistency
+			SortedList<String> sortedList = SortedList.fromIterator(iterator());
+			StringBuilder sb = new StringBuilder();
+			String del = "";
+			for (String k : sortedList) {
+				if (ins.matches(k)) {
+					String v = getProperty(k, null);
+					if (v != null) {
+						sb.append(del);
+						del = ",";
+						sb.append(v);
+					}
+				}
+			}
+			if (sb.length() == 0)
+				return deflt;
+
+			return sb.toString();
+		}
+
+		Processor source = this;
+
+		if (filter != null && filter.contains(key)) {
+			value = (String) getProperties().get(key);
+		} else {
+			while (source != null) {
+				value = (String) source.getProperties().get(key);
+				if (value != null)
+					break;
+
+				source = source.getParent();
+			}
+		}
+
+		if (value != null)
+			return getReplacer().process(value, source);
+		else if (deflt != null)
+			return getReplacer().process(deflt, this);
+		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);
+		try {
+			Properties p = loadProperties(in, file.getAbsolutePath());
+			return p;
+		}
+		finally {
+			in.close();
+		}
+	}
+
+	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;
+	}
+
+	/**
+	 * Print a standard Map based OSGi header.
+	 * 
+	 * @param exports
+	 *            map { name => Map { attribute|directive => value } }
+	 * @return the clauses
+	 * @throws IOException
+	 */
+	public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports) throws IOException {
+		return printClauses(exports, false);
+	}
+
+	public static String printClauses(Map< ? , ? extends Map< ? , ? >> exports, @SuppressWarnings("unused") boolean checkMultipleVersions)
+			throws IOException {
+		StringBuilder sb = new StringBuilder();
+		String del = "";
+		for (Entry< ? , ? extends Map< ? , ? >> entry : exports.entrySet()) {
+			String name = entry.getKey().toString();
+			Map< ? , ? > clause = entry.getValue();
+
+			// 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, sb);
+			del = ",";
+		}
+		return sb.toString();
+	}
+
+	public static void printClause(Map< ? , ? > map, StringBuilder sb) throws IOException {
+
+		for (Entry< ? , ? > entry : map.entrySet()) {
+			Object key = entry.getKey();
+			// Skip directives we do not recognize
+			if (key.equals(NO_IMPORT_DIRECTIVE) || key.equals(PROVIDE_DIRECTIVE) || key.equals(SPLIT_PACKAGE_DIRECTIVE)
+					|| key.equals(FROM_DIRECTIVE))
+				continue;
+
+			String value = ((String) entry.getValue()).trim();
+			sb.append(";");
+			sb.append(key);
+			sb.append("=");
+
+			quote(sb, value);
+		}
+	}
+
+	/**
+	 * @param sb
+	 * @param value
+	 * @return
+	 * @throws IOException
+	 */
+	public static boolean quote(Appendable sb, String value) throws IOException {
+		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("\"");
+		return clean;
+	}
+
+	public Macro getReplacer() {
+		if (replacer == null)
+			return replacer = new Macro(this, getMacroDomains());
+		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();
+
+	}
+
+	/**
+	 * Return all inherited property keys
+	 * 
+	 * @return
+	 */
+	public Set<String> getPropertyKeys(boolean inherit) {
+		Set<String> result;
+		if (parent == null || !inherit) {
+			result = Create.set();
+		} else
+			result = parent.getPropertyKeys(inherit);
+		for (Object o : properties.keySet())
+			result.add(o.toString());
+
+		return result;
+	}
+
+	public boolean updateModified(long time, @SuppressWarnings("unused") String reason) {
+		if (time > lastModified) {
+			lastModified = time;
+			return true;
+		}
+		return false;
+	}
+
+	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, "UTF8");
+		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) {
+		return join(delimeter, list);
+	}
+
+	public static String join(String delimeter, Collection< ? >... list) {
+		StringBuilder sb = new StringBuilder();
+		String del = "";
+		if (list != null) {
+			for (Collection< ? > l : list) {
+				for (Object item : l) {
+					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 <T> String join(T 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 static String merge(String... strings) {
+		ArrayList<String> result = new ArrayList<String>();
+		for (String s : strings) {
+			if (s != null)
+				split(s, result);
+		}
+		return join(result);
+	}
+
+	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);
+		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);
+		}
+
+		public Class< ? > loadClass(String name) throws NoClassDefFoundError {
+			try {
+				Class< ? > c = super.loadClass(name);
+				return c;
+			}
+			catch (Throwable t) {
+				StringBuilder sb = new StringBuilder();
+				sb.append(name);
+				sb.append(" not found, parent:  ");
+				sb.append(getParent());
+				sb.append(" urls:");
+				sb.append(Arrays.toString(getURLs()));
+				sb.append(" exception:");
+				sb.append(t);
+				throw new NoClassDefFoundError(sb.toString());
+			}
+		}
+	}
+
+	private CL getLoader() {
+		if (pluginLoader == null) {
+			pluginLoader = new CL();
+		}
+		return pluginLoader;
+	}
+
+	/*
+	 * Check if this is a valid project.
+	 */
+	public boolean exists() {
+		return base != null && base.isDirectory() && propertiesFile != null && propertiesFile.isFile();
+	}
+
+	public boolean isOk() {
+		return isFailOk() || (getErrors().size() == 0);
+	}
+
+	public boolean check(String... pattern) throws IOException {
+		Set<String> missed = Create.set();
+
+		if (pattern != null) {
+			for (String p : pattern) {
+				boolean match = false;
+				Pattern pat = Pattern.compile(p);
+				for (Iterator<String> i = errors.iterator(); i.hasNext();) {
+					if (pat.matcher(i.next()).find()) {
+						i.remove();
+						match = true;
+					}
+				}
+				for (Iterator<String> i = warnings.iterator(); i.hasNext();) {
+					if (pat.matcher(i.next()).find()) {
+						i.remove();
+						match = true;
+					}
+				}
+				if (!match)
+					missed.add(p);
+
+			}
+		}
+		if (missed.isEmpty() && isPerfect())
+			return true;
+
+		if (!missed.isEmpty())
+			System.err.println("Missed the following patterns in the warnings or errors: " + missed);
+
+		report(System.err);
+		return false;
+	}
+
+	protected void report(Appendable out) throws IOException {
+		if (errors.size() > 0) {
+			out.append(String.format("-----------------%nErrors%n"));
+			for (int i = 0; i < errors.size(); i++) {
+				out.append(String.format("%03d: %s%n", i, errors.get(i)));
+			}
+		}
+		if (warnings.size() > 0) {
+			out.append(String.format("-----------------%nWarnings%n"));
+			for (int i = 0; i < warnings.size(); i++) {
+				out.append(String.format("%03d: %s%n", i, warnings.get(i)));
+			}
+		}
+	}
+
+	public boolean isPerfect() {
+		return getErrors().size() == 0 && getWarnings().size() == 0;
+	}
+
+	public void setForceLocal(Collection<String> local) {
+		filter = local;
+	}
+
+	/**
+	 * Answer if the name is a missing plugin's command name. If a bnd file
+	 * contains the command name of a plugin, and that plugin is not available,
+	 * then an error is reported during manifest calculation. This allows the
+	 * plugin to fail to load when it is not needed. We first get the plugins to
+	 * ensure it is properly initialized.
+	 * 
+	 * @param name
+	 * @return
+	 */
+	public boolean isMissingPlugin(String name) {
+		getPlugins();
+		return missingCommand != null && missingCommand.contains(name);
+	}
+
+	/**
+	 * Append two strings to for a path in a ZIP or JAR file. It is guaranteed
+	 * to return a string that does not start, nor ends with a '/', while it is
+	 * properly separated with slashes. Double slashes are properly removed.
+	 * 
+	 * <pre>
+	 *  &quot;/&quot; + &quot;abc/def/&quot; becomes &quot;abc/def&quot;
+	 *  
+	 * &#064;param prefix
+	 * &#064;param suffix
+	 * &#064;return
+	 */
+	public static String appendPath(String... parts) {
+		StringBuilder sb = new StringBuilder();
+		boolean lastSlash = true;
+		for (String part : parts) {
+			for (int i = 0; i < part.length(); i++) {
+				char c = part.charAt(i);
+				if (c == '/') {
+					if (!lastSlash)
+						sb.append('/');
+					lastSlash = true;
+				} else {
+					sb.append(c);
+					lastSlash = false;
+				}
+			}
+
+			if (!lastSlash && sb.length() > 0) {
+				sb.append('/');
+				lastSlash = true;
+			}
+		}
+		if (lastSlash && sb.length() > 0)
+			sb.deleteCharAt(sb.length() - 1);
+
+		return sb.toString();
+	}
+
+	/**
+	 * Parse the a=b strings and return a map of them.
+	 * 
+	 * @param attrs
+	 * @param clazz
+	 * @return
+	 */
+	public static Attrs doAttrbutes(Object[] attrs, Clazz clazz, Macro macro) {
+		Attrs map = new Attrs();
+
+		if (attrs == null || attrs.length == 0)
+			return map;
+
+		for (Object a : attrs) {
+			String attr = (String) a;
+			int n = attr.indexOf("=");
+			if (n > 0) {
+				map.put(attr.substring(0, n), macro.process(attr.substring(n + 1)));
+			} else
+				throw new IllegalArgumentException(formatArrays(
+						"Invalid attribute on package-info.java in %s , %s. Must be <key>=<name> ", clazz, attr));
+		}
+		return map;
+	}
+
+	/**
+	 * This method is the same as String.format but it makes sure that any
+	 * arrays are transformed to strings.
+	 * 
+	 * @param string
+	 * @param parms
+	 * @return
+	 */
+	public static String formatArrays(String string, Object... parms) {
+		Object[] parms2 = parms;
+		Object[] output = new Object[parms.length];
+		for (int i = 0; i < parms.length; i++) {
+			output[i] = makePrintable(parms[i]);
+		}
+		return String.format(string, parms2);
+	}
+
+	/**
+	 * Check if the object is an array and turn it into a string if it is,
+	 * otherwise unchanged.
+	 * 
+	 * @param object
+	 *            the object to make printable
+	 * @return a string if it was an array or the original object
+	 */
+	public static Object makePrintable(Object object) {
+		if (object == null)
+			return object;
+
+		if (object.getClass().isArray()) {
+			Object[] array = (Object[]) object;
+			Object[] output = new Object[array.length];
+			for (int i = 0; i < array.length; i++) {
+				output[i] = makePrintable(array[i]);
+			}
+			return Arrays.toString(output);
+		}
+		return object;
+	}
+
+	public static String append(String... strings) {
+		List<String> result = Create.list();
+		for (String s : strings) {
+			result.addAll(split(s));
+		}
+		return join(result);
+	}
+
+	public synchronized Class< ? > getClass(String type, File jar) throws Exception {
+		CL cl = getLoader();
+		cl.add(jar.toURI().toURL());
+		return cl.loadClass(type);
+	}
+
+	public boolean isTrace() {
+		return current().trace;
+	}
+
+	public static long getDuration(String tm, long dflt) {
+		if (tm == null)
+			return dflt;
+
+		tm = tm.toUpperCase();
+		TimeUnit unit = TimeUnit.MILLISECONDS;
+		Matcher m = Pattern
+				.compile("\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?").matcher(
+						tm);
+		if (m.matches()) {
+			long duration = Long.parseLong(tm);
+			String u = m.group(2);
+			if (u != null)
+				unit = TimeUnit.valueOf(u);
+			duration = TimeUnit.MILLISECONDS.convert(duration, unit);
+			return duration;
+		}
+		return dflt;
+	}
+
+	/**
+	 * Generate a random string, which is guaranteed to be a valid Java
+	 * identifier (first character is an ASCII letter, subsequent characters are
+	 * ASCII letters or numbers). Takes an optional parameter for the length of
+	 * string to generate; default is 8 characters.
+	 */
+	public String _random(String[] args) {
+		int numchars = 8;
+		if (args.length > 1) {
+			try {
+				numchars = Integer.parseInt(args[1]);
+			}
+			catch (NumberFormatException e) {
+				throw new IllegalArgumentException("Invalid character count parameter in ${random} macro.");
+			}
+		}
+
+		synchronized (Processor.class) {
+			if (random == null)
+				random = new Random();
+		}
+
+		char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+		char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
+
+		char[] array = new char[numchars];
+		for (int i = 0; i < numchars; i++) {
+			char c;
+			if (i == 0)
+				c = letters[random.nextInt(letters.length)];
+			else
+				c = alphanums[random.nextInt(alphanums.length)];
+			array[i] = c;
+		}
+
+		return new String(array);
+	}
+
+	/**
+	 * Set the current command thread. This must be balanced with the
+	 * {@link #end(Processor)} method. The method returns the previous command
+	 * owner or null. The command owner will receive all warnings and error
+	 * reports.
+	 */
+
+	protected Processor beginHandleErrors(String message) {
+		trace("begin %s", message);
+		Processor previous = current.get();
+		current.set(this);
+		return previous;
+	}
+
+	/**
+	 * End a command. Will restore the previous command owner.
+	 * 
+	 * @param previous
+	 */
+	protected void endHandleErrors(Processor previous) {
+		trace("end");
+		current.set(previous);
+	}
+
+	public static Executor getExecutor() {
+		return executor;
+	}
+
+	/**
+	 * These plugins are added to the total list of plugins. The separation is
+	 * necessary because the list of plugins is refreshed now and then so we
+	 * need to be able to add them at any moment in time.
+	 * 
+	 * @param plugin
+	 */
+	public synchronized void addBasicPlugin(Object plugin) {
+		basicPlugins.add(plugin);
+		if (plugins != null)
+			plugins.add(plugin);
+	}
+
+	public synchronized void removeBasicPlugin(Object plugin) {
+		basicPlugins.remove(plugin);
+		if (plugins != null)
+			plugins.remove(plugin);
+	}
+
+	public List<File> getIncluded() {
+		return included;
+	}
+
+	/**
+	 * Overrides for the Domain class
+	 */
+	@Override
+	public String get(String key) {
+		return getProperty(key);
+	}
+
+	@Override
+	public String get(String key, String deflt) {
+		return getProperty(key, deflt);
+	}
+
+	@Override
+	public void set(String key, String value) {
+		getProperties().setProperty(key, value);
+	}
+
+	@Override
+	public Iterator<String> iterator() {
+		Set<String> keys = keySet();
+		final Iterator<String> it = keys.iterator();
+
+		return new Iterator<String>() {
+			String	current;
+
+			public boolean hasNext() {
+				return it.hasNext();
+			}
+
+			public String next() {
+				return current = it.next().toString();
+			}
+
+			public void remove() {
+				getProperties().remove(current);
+			}
+		};
+	}
+
+	public Set<String> keySet() {
+		Set<String> set;
+		if (parent == null)
+			set = Create.set();
+		else
+			set = parent.keySet();
+
+		for (Object o : properties.keySet())
+			set.add(o.toString());
+
+		return set;
+	}
+
+	/**
+	 * Printout of the status of this processor for toString()
+	 */
+
+	public String toString() {
+		try {
+			StringBuilder sb = new StringBuilder();
+			report(sb);
+			return sb.toString();
+		}
+		catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * Utiltity to replace an extension
+	 * 
+	 * @param s
+	 * @param extension
+	 * @param newExtension
+	 * @return
+	 */
+	public String replaceExtension(String s, String extension, String newExtension) {
+		if (s.endsWith(extension))
+			s = s.substring(0, s.length() - extension.length());
+
+		return s + newExtension;
+	}
+
+	/**
+	 * Create a location object and add it to the locations
+	 * 
+	 * @param s
+	 * @return
+	 */
+	List<Location>	locations	= new ArrayList<Location>();
+
+	static class SetLocationImpl extends Location implements SetLocation {
+		public SetLocationImpl(String s) {
+			this.message = s;
+		}
+
+		public SetLocation file(String file) {
+			this.file = file;
+			return this;
+		}
+
+		public SetLocation header(String header) {
+			this.header = header;
+			return this;
+		}
+
+		public SetLocation context(String context) {
+			this.context = context;
+			return this;
+		}
+
+		public SetLocation method(String methodName) {
+			this.methodName = methodName;
+			return this;
+		}
+
+		public SetLocation line(int n) {
+			this.line = n;
+			return this;
+		}
+
+		public SetLocation reference(String reference) {
+			this.reference = reference;
+			return this;
+		}
+
+	}
+
+	private SetLocation location(String s) {
+		SetLocationImpl loc = new SetLocationImpl(s);
+		locations.add(loc);
+		return loc;
+	}
+
+	public Location getLocation(String msg) {
+		for (Location l : locations)
+			if ((l.message != null) && l.message.equals(msg))
+				return l;
+
+		return null;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
new file mode 100755
index 0000000..8be81aa
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Resource.java
@@ -0,0 +1,17 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+public interface Resource {
+	InputStream openInputStream() throws Exception;
+
+	void write(OutputStream out) throws Exception;
+
+	long lastModified();
+
+	void setExtra(String extra);
+
+	String getExtra();
+
+	long size() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java
new file mode 100644
index 0000000..183f1cd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/TagResource.java
@@ -0,0 +1,30 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+import aQute.lib.tag.*;
+
+public class TagResource extends WriteResource {
+	final Tag	tag;
+
+	public TagResource(Tag tag) {
+		this.tag = tag;
+	}
+
+	public void write(OutputStream out) throws UnsupportedEncodingException {
+		OutputStreamWriter ow = new OutputStreamWriter(out, "UTF-8");
+		PrintWriter pw = new PrintWriter(ow);
+		pw.println("<?xml version='1.1'?>");
+		try {
+			tag.print(0, pw);
+		}
+		finally {
+			pw.flush();
+		}
+	}
+
+	public long lastModified() {
+		return 0;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
new file mode 100755
index 0000000..2cd2375
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/URLResource.java
@@ -0,0 +1,84 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.net.*;
+
+import aQute.lib.io.*;
+
+public class URLResource implements Resource {
+	URL		url;
+	String	extra;
+	long	size	= -1;
+
+	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 Exception {
+		IO.copy(this.openInputStream(), out);
+	}
+
+	public long lastModified() {
+		return -1;
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	public long size() throws Exception {
+		if (size >= 0)
+			return size;
+
+		try {
+			if (url.getProtocol().equals("file:")) {
+				File file = new File(url.getPath());
+				if (file.isFile())
+					return size = file.length();
+			} else {
+				URLConnection con = url.openConnection();
+				if (con instanceof HttpURLConnection) {
+					HttpURLConnection http = (HttpURLConnection) con;
+					http.setRequestMethod("HEAD");
+					http.connect();
+					String l = http.getHeaderField("Content-Length");
+					if (l != null) {
+						return size = Long.parseLong(l);
+					}
+				}
+			}
+		}
+		catch (Exception e) {
+			// Forget this exception, we do it the hard way
+		}
+		InputStream in = openInputStream();
+		DataInputStream din = null;
+		try {
+			din = new DataInputStream(in);
+			long result = din.skipBytes(Integer.MAX_VALUE);
+			while (in.read() >= 0) {
+				result += din.skipBytes(Integer.MAX_VALUE);
+			}
+			size = result;
+		}
+		finally {
+			if (din != null) {
+				din.close();
+			}
+		}
+		return size;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
new file mode 100755
index 0000000..eac2f6f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Verifier.java
@@ -0,0 +1,913 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.header.*;
+import aQute.bnd.osgi.Descriptors.PackageRef;
+import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.qtokens.*;
+
+public class Verifier extends Processor {
+
+	private final Jar		dot;
+	private final Manifest	manifest;
+	private final Domain	main;
+
+	private boolean			r3;
+	private boolean			usesRequire;
+
+	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 int		V1_1	= 45;
+	final static int		V1_2	= 46;
+	final static int		V1_3	= 47;
+	final static int		V1_4	= 48;
+	final static int		V1_5	= 49;
+	final static int		V1_6	= 50;
+	final static int		V1_7	= 51;
+	final static int		V1_8	= 52;
+
+	static class EE {
+		String	name;
+		int		target;
+
+		EE(String name, @SuppressWarnings("unused") int source, int target) {
+			this.name = name;
+			this.target = target;
+		}
+
+		public String toString() {
+			return name + "(" + target + ")";
+		}
+	}
+
+	final static EE[]			ees								= {
+			new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
+			new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
+			new EE("OSGi/Minimum-1.0", V1_3, V1_1),
+			new EE("OSGi/Minimum-1.1", V1_3, V1_2),
+			new EE("JRE-1.1", V1_1, V1_1), //
+			new EE("J2SE-1.2", V1_2, V1_1), //
+			new EE("J2SE-1.3", V1_3, V1_1), //
+			new EE("J2SE-1.4", V1_3, V1_2), //
+			new EE("J2SE-1.5", V1_5, V1_5), //
+			new EE("JavaSE-1.6", V1_6, V1_6), //
+			new EE("PersonalJava-1.1", V1_1, V1_1), //
+			new EE("JavaSE-1.7", V1_7, V1_7), //
+			new EE("PersonalJava-1.1", V1_1, V1_1), //
+			new EE("PersonalJava-1.2", V1_1, V1_1), new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
+			new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1), new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
+			new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2)
+																};
+
+	final static Pattern		CARDINALITY_PATTERN				= Pattern.compile("single|multiple");
+	final static Pattern		RESOLUTION_PATTERN				= Pattern.compile("optional|mandatory");
+	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	PACKAGEPATTERN					= Pattern
+																		.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
+	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 static final Pattern	BUNDLE_ACTIVATIONPOLICYPATTERN	= Pattern.compile("lazy");
+
+	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 32 bit
+			"ia64w",// Hewlett Packard 64 bit mode
+			"Ignite", // psc1k PTSC
+			"Mips", // SGI
+			"PArisc", // Hewlett Packard
+			"PowerPC", // power ppc Motorola/IBM Power PC
+			"Sh4", // Hitachi
+			"Sparc", // SUN
+			"Sparcv9", // 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",
+																};
+
+	final Analyzer				analyzer;
+	private Instructions		dynamicImports;
+
+	public Verifier(Jar jar) throws Exception {
+		this.analyzer = new Analyzer(this);
+		this.analyzer.use(this);
+		addClose(analyzer);
+		this.analyzer.setJar(jar);
+		this.manifest = this.analyzer.calcManifest();
+		this.main = Domain.domain(manifest);
+		this.dot = jar;
+		getInfo(analyzer);
+	}
+
+	public Verifier(Analyzer analyzer) throws Exception {
+		this.analyzer = analyzer;
+		this.dot = analyzer.getJar();
+		this.manifest = dot.getManifest();
+		this.main = Domain.domain(manifest);
+	}
+
+	private void verifyHeaders() {
+		for (String h : main) {
+			if (!HEADER_PATTERN.matcher(h).matches())
+				error("Invalid Manifest header: " + h + ", pattern=" + HEADER_PATTERN);
+		}
+	}
+
+	/*
+	 * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
+	 * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+	 * optional ::= ’*’
+	 */
+	public void verifyNative() {
+		String nc = get("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 boolean verifyFilter(String value) {
+		String s = validateFilter(value);
+		if (s == null)
+			return true;
+
+		error(s);
+		return false;
+	}
+
+	public static String validateFilter(String value) {
+		try {
+			verifyFilter(value, 0);
+			return null;
+		}
+		catch (Exception e) {
+			return "Not a valid filter: " + value + e.getMessage();
+		}
+	}
+
+	private void verifyActivator() throws Exception {
+		String bactivator = main.get("Bundle-Activator");
+		if (bactivator != null) {
+			TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
+			if (analyzer.getClassspace().containsKey(ref))
+				return;
+
+			PackageRef packageRef = ref.getPackageRef();
+			if (packageRef.isDefaultPackage())
+				error("The Bundle Activator is not in the bundle and it is in the default package ");
+			else if (!analyzer.isImported(packageRef)) {
+				error("Bundle-Activator not found on the bundle class path nor in imports: " + bactivator);
+			}
+		}
+	}
+
+	private void verifyComponent() {
+		String serviceComponent = main.get("Service-Component");
+		if (serviceComponent != null) {
+			Parameters 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 ...
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check for unresolved imports. These are referrals that are not imported
+	 * by the manifest and that are not part of our bundle class path. The are
+	 * calculated by removing all the imported packages and contained from the
+	 * referred packages.
+	 */
+	private void verifyUnresolvedReferences() {
+		Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred().keySet());
+		unresolvedReferences.removeAll(analyzer.getImports().keySet());
+		unresolvedReferences.removeAll(analyzer.getContained().keySet());
+
+		// Remove any java.** packages.
+		for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
+			PackageRef pack = p.next();
+			if (pack.isJava())
+				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 : analyzer.getClassspace().values()) {
+				if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+					culprits.add(clazz.getAbsolutePath());
+			}
+
+			error("Unresolved references to %s by class(es) %s on the Bundle-Classpath: %s", unresolvedReferences,
+					culprits, analyzer.getBundleClasspath().keySet());
+		}
+	}
+
+	/**
+	 * @param p
+	 * @param pack
+	 */
+	private boolean isDynamicImport(PackageRef pack) {
+		if (dynamicImports == null)
+			dynamicImports = new Instructions(main.getDynamicImportPackage());
+
+		return dynamicImports.matches(pack.getFQN());
+	}
+
+	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 Exception {
+		verifyHeaders();
+		verifyDirectives("Export-Package", "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE, PACKAGEPATTERN,
+				"package");
+		verifyDirectives("Import-Package", "resolution:", PACKAGEPATTERN, "package");
+		verifyDirectives("Require-Bundle", "visibility:|resolution:", SYMBOLICNAME, "bsn");
+		verifyDirectives("Fragment-Host", "extension:", SYMBOLICNAME, "bsn");
+		verifyDirectives("Provide-Capability", "effective:|uses:", null, null);
+		verifyDirectives("Require-Capability", "effective:|resolution:|filter:", null, null);
+		verifyDirectives("Bundle-SymbolicName", "singleton:|fragment-attachment:|mandatory:", SYMBOLICNAME, "bsn");
+
+		verifyManifestFirst();
+		verifyActivator();
+		verifyActivationPolicy();
+		verifyComponent();
+		verifyNative();
+		verifyUnresolvedReferences();
+		verifySymbolicName();
+		verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
+		verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
+		verifyHeader("Bundle-Version", VERSION, true);
+		verifyListHeader("Bundle-Classpath", FILE, false);
+		verifyDynamicImportPackage();
+		verifyBundleClasspath();
+		verifyUses();
+		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");
+			}
+		}
+
+		verifyRequirements();
+		verifyCapabilities();
+	}
+
+	private void verifyRequirements() {
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
+		for (String key : map.keySet()) {
+			Attrs attrs = map.get(key);
+			verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
+			verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+			verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+			if (key.equals("osgi.extender")) {
+				// No requirements on extender
+			} else if (key.equals("osgi.serviceloader")) {
+				verify(attrs, "register:", PACKAGEPATTERN, false,
+						"Service Loader extender register: directive not a fully qualified Java name");
+			} else if (key.equals("osgi.contract")) {
+
+			} else if (key.equals("osgi.service")) {
+
+			} else if (key.equals("osgi.ee")) {
+
+			} else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+				error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+			}
+
+			verifyAttrs(attrs);
+
+			if (attrs.containsKey("mandatory:"))
+				error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
+
+			if (attrs.containsKey("uses:"))
+				error("uses: directive is intended for Capabilities, not Requirement %s", key);
+		}
+	}
+
+	/**
+	 * @param attrs
+	 */
+	void verifyAttrs(Attrs attrs) {
+		for (String a : attrs.keySet()) {
+			String v = attrs.get(a);
+
+			if (!a.endsWith(":")) {
+				Attrs.Type t = attrs.getType(a);
+				if ("version".equals(a)) {
+					if (t != Attrs.Type.VERSION)
+						error("Version attributes should always be of type version, it is %s", t);
+				} else
+					verifyType(t, v);
+			}
+		}
+	}
+
+	private void verifyCapabilities() {
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY));
+		for (String key : map.keySet()) {
+			Attrs attrs = map.get(key);
+			verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+			verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+			if (key.equals("osgi.extender")) {
+				verify(attrs, "osgi.extender", SYMBOLICNAME, true,
+						"Extender %s must always have the osgi.extender attribute set", key);
+				verify(attrs, "version", VERSION, true, "Extender %s must always have a version", key);
+			} else if (key.equals("osgi.serviceloader")) {
+				verify(attrs, "register:", PACKAGEPATTERN, false,
+						"Service Loader extender register: directive not a fully qualified Java name");
+			} else if (key.equals("osgi.contract")) {
+				verify(attrs, "osgi.contract", SYMBOLICNAME, true,
+						"Contracts %s must always have the osgi.contract attribute set", key);
+
+			} else if (key.equals("osgi.service")) {
+				verify(attrs, "objectClass", PACKAGEPATTERN, true,
+						"osgi.service %s must have the objectClass attribute set", key);
+
+			} else if (key.equals("osgi.ee")) {
+				// TODO
+			} else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+				error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+			}
+
+			verifyAttrs(attrs);
+
+			if (attrs.containsKey("filter:"))
+				error("filter: directive is intended for Requirements, not Capability %s", key);
+			if (attrs.containsKey("cardinality:"))
+				error("cardinality: directive is intended for Requirements, not Capability %s", key);
+			if (attrs.containsKey("resolution:"))
+				error("resolution: directive is intended for Requirements, not Capability %s", key);
+		}
+	}
+
+	private void verify(Attrs attrs, String ad, Pattern pattern, boolean mandatory, String msg, String... args) {
+		String v = attrs.get(ad);
+		if (v == null) {
+			if (mandatory)
+				error("Missing required attribute/directive %s", ad);
+		} else {
+			Matcher m = pattern.matcher(v);
+			if (!m.matches())
+				error(msg, (Object[]) args);
+		}
+	}
+
+	private void verifyType(@SuppressWarnings("unused") Attrs.Type type, @SuppressWarnings("unused") String string) {
+
+	}
+
+	/**
+	 * Verify if the header does not contain any other directives
+	 * 
+	 * @param header
+	 * @param directives
+	 */
+	private void verifyDirectives(String header, String directives, Pattern namePattern, String type) {
+		Pattern pattern = Pattern.compile(directives);
+		Parameters map = parseHeader(manifest.getMainAttributes().getValue(header));
+		for (Entry<String,Attrs> entry : map.entrySet()) {
+			String pname = removeDuplicateMarker(entry.getKey());
+
+			if (namePattern != null) {
+				if (!namePattern.matcher(pname).matches())
+					if (isPedantic())
+						error("Invalid %s name: '%s'", type, pname);
+					else
+						warning("Invalid %s name: '%s'", type, pname);
+			}
+
+			for (String key : entry.getValue().keySet()) {
+				if (key.endsWith(":")) {
+					if (!key.startsWith("x-")) {
+						Matcher m = pattern.matcher(key);
+						if (m.matches())
+							continue;
+
+						warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.", key, header,
+								directives.replace('|', ','));
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Verify the use clauses
+	 */
+	private void verifyUses() {
+		// Set<String> uses = Create.set();
+		// for ( Map<String,String> attrs : analyzer.getExports().values()) {
+		// if ( attrs.containsKey(Constants.USES_DIRECTIVE)) {
+		// String s = attrs.get(Constants.USES_DIRECTIVE);
+		// uses.addAll( split(s));
+		// }
+		// }
+		// uses.removeAll(analyzer.getExports().keySet());
+		// uses.removeAll(analyzer.getImports().keySet());
+		// if ( !uses.isEmpty())
+		// warning("Export-Package uses: directive contains packages that are not imported nor exported: %s",
+		// uses);
+	}
+
+	public boolean verifyActivationPolicy() {
+		String policy = main.get(Constants.BUNDLE_ACTIVATIONPOLICY);
+		if (policy == null)
+			return true;
+
+		return verifyActivationPolicy(policy);
+	}
+
+	public boolean verifyActivationPolicy(String policy) {
+		Parameters map = parseHeader(policy);
+		if (map.size() == 0)
+			warning("Bundle-ActivationPolicy is set but has no argument %s", policy);
+		else if (map.size() > 1)
+			warning("Bundle-ActivationPolicy has too many arguments %s", policy);
+		else {
+			Map<String,String> s = map.get("lazy");
+			if (s == null)
+				warning("Bundle-ActivationPolicy set but is not set to lazy: %s", policy);
+			else
+				return true;
+		}
+
+		return false;
+	}
+
+	public void verifyBundleClasspath() {
+		Parameters bcp = main.getBundleClassPath();
+		if (bcp.isEmpty() || bcp.containsKey("."))
+			return;
+
+		for (String path : bcp.keySet()) {
+			if (path.endsWith("/"))
+				error("A Bundle-ClassPath entry must not end with '/': %s", path);
+
+			if (dot.getDirectories().containsKey(path))
+				// We assume that any classes are in a directory
+				// and therefore do not care when the bundle is included
+				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 = get("DynamicImport-Package");
+		if (dynamicImportPackage == null)
+			return;
+
+		Parameters map = main.getDynamicImportPackage();
+		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.isManifestFirst()) {
+			error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
+		}
+	}
+
+	private void verifySymbolicName() {
+		Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
+		if (!bsn.isEmpty()) {
+			if (bsn.size() > 1)
+				error("More than one BSN specified " + bsn);
+
+			String name = bsn.keySet().iterator().next();
+			if (!isBsn(name)) {
+				error("Symbolic Name has invalid format: " + name);
+			}
+		}
+	}
+
+	/**
+	 * @param name
+	 * @return
+	 */
+	public static boolean isBsn(String name) {
+		return SYMBOLICNAME.matcher(name).matches();
+	}
+
+	/**
+	 * <pre>
+	 *         filter ::= ’(’ filter-comp ’)’
+	 *         filter-comp ::= and | or | not | operation
+	 *         and ::= ’&amp;’ 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 ::= ’&tilde;=’
+	 *         greater ::= ’&gt;=’
+	 *         less ::= ’&lt;=’
+	 *         present ::= attr ’=*’
+	 *         substring ::= attr ’=’ initial any final
+	 *         inital ::= () | value
+	 *         any ::= ’*’ star-value
+	 *         star-value ::= () | value ’*’ star-value
+	 *         final ::= () | value
+	 *         value ::= &lt;see text&gt;
+	 * </pre>
+	 * 
+	 * @param expr
+	 * @param index
+	 * @return
+	 */
+
+	public static 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++; // skip (
+
+			while (Character.isWhitespace(expr.charAt(index)))
+				index++;
+
+			switch (expr.charAt(index)) {
+				case '!' :
+					index++; // skip !
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+
+					if (expr.charAt(index) != '(')
+						throw new IllegalArgumentException("Filter mismatch: ! (not) must have one sub expression "
+								+ index + " : " + expr);
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+
+					index = verifyFilter(expr, index);
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1;
+
+				case '&' :
+				case '|' :
+					index++; // skip operator
+					while (Character.isWhitespace(expr.charAt(index)))
+						index++;
+					while (expr.charAt(index) == '(') {
+						index = verifyFilter(expr, index);
+						while (Character.isWhitespace(expr.charAt(index)))
+							index++;
+					}
+
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1; // skip )
+
+				default :
+					index = verifyFilterOperation(expr, index);
+					if (expr.charAt(index) != ')')
+						throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
+								+ expr);
+					return index + 1;
+			}
+		}
+		catch (IndexOutOfBoundsException e) {
+			throw new IllegalArgumentException("Filter mismatch: early EOF from " + index);
+		}
+	}
+
+	static private int verifyFilterOperation(String expr, int index) {
+		StringBuilder sb = new StringBuilder();
+		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 StringBuilder();
+		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 StringBuilder();
+		while (")".indexOf(expr.charAt(index)) < 0) {
+			switch (expr.charAt(index)) {
+				case '\\' :
+					if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0)
+						index++;
+					else
+						throw new IllegalArgumentException("Filter error, illegal use of backslash at index " + index
+								+ ". Backslash may only be used before * or () or \\");
+			}
+			sb.append(expr.charAt(index++));
+		}
+		return index;
+	}
+
+	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(i.next(), regex)) {
+				String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
+				if (error)
+					error(msg);
+				else
+					warning(msg);
+			}
+		}
+		return true;
+	}
+
+	static 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;
+
+		Parameters 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 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;
+	}
+
+	public static boolean isFQN(String name) {
+		if (name.length() == 0)
+			return false;
+		if (!Character.isJavaIdentifierStart(name.charAt(0)))
+			return false;
+
+		for (int i = 1; i < name.length(); i++) {
+			char c = name.charAt(i);
+			if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
+				continue;
+
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Verify checksums
+	 */
+	/**
+	 * Verify the checksums from the manifest against the real thing.
+	 * 
+	 * @param all
+	 *            if each resources must be digested
+	 * @return true if ok
+	 * @throws Exception
+	 */
+
+	public void verifyChecksums(boolean all) throws Exception {
+		Manifest m = dot.getManifest();
+		if (m == null || m.getEntries().isEmpty()) {
+			if (all)
+				error("Verify checksums with all but no digests");
+			return;
+		}
+
+		List<String> missingDigest = new ArrayList<String>();
+
+		for (String path : dot.getResources().keySet()) {
+			if (path.equals("META-INF/MANIFEST.MF"))
+				continue;
+
+			Attributes a = m.getAttributes(path);
+			String digest = a.getValue("SHA1-Digest");
+			if (digest == null) {
+				if (!path.matches(""))
+					missingDigest.add(path);
+			} else {
+				byte[] d = Base64.decodeBase64(digest);
+				SHA1 expected = new SHA1(d);
+				Digester<SHA1> digester = SHA1.getDigester();
+				InputStream in = dot.getResource(path).openInputStream();
+				IO.copy(in, digester);
+				digester.digest();
+				if (!expected.equals(digester.digest())) {
+					error("Checksum mismatch %s, expected %s, got %s", path, expected, digester.digest());
+				}
+			}
+		}
+		if (missingDigest.size() > 0) {
+			error("Entries in the manifest are missing digests: %s", missingDigest);
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java b/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
new file mode 100755
index 0000000..951ad25
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/Version.java
@@ -0,0 +1,164 @@
+package aQute.bnd.osgi;
+
+import java.util.regex.*;
+
+public class Version implements Comparable<Version> {
+	final int					major;
+	final int					minor;
+	final int					micro;
+	final String				qualifier;
+	public final static String	VERSION_STRING	= "(\\d+)(\\.(\\d+)(\\.(\\d+)(\\.([-_\\da-zA-Z]+))?)?)?";
+	public final static Pattern	VERSION			= Pattern.compile(VERSION_STRING);
+	public final static Version	LOWEST			= new Version();
+	public final static Version	HIGHEST			= new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+														"\uFFFF");
+
+	public static final Version	emptyVersion	= LOWEST;
+	public static final Version	ONE				= new Version(1, 0, 0);
+
+	public Version() {
+		this(0);
+	}
+
+	public Version(int major, int minor, int micro, String qualifier) {
+		this.major = major;
+		this.minor = minor;
+		this.micro = micro;
+		this.qualifier = qualifier;
+	}
+
+	public Version(int major, int minor, int micro) {
+		this(major, minor, micro, null);
+	}
+
+	public Version(int major, int minor) {
+		this(major, minor, 0, null);
+	}
+
+	public Version(int major) {
+		this(major, 0, 0, null);
+	}
+
+	public Version(String version) {
+		version = version.trim();
+		Matcher m = VERSION.matcher(version);
+		if (!m.matches())
+			throw new IllegalArgumentException("Invalid syntax for version: " + version);
+
+		major = Integer.parseInt(m.group(1));
+		if (m.group(3) != null)
+			minor = Integer.parseInt(m.group(3));
+		else
+			minor = 0;
+
+		if (m.group(5) != null)
+			micro = Integer.parseInt(m.group(5));
+		else
+			micro = 0;
+
+		qualifier = m.group(7);
+	}
+
+	public int getMajor() {
+		return major;
+	}
+
+	public int getMinor() {
+		return minor;
+	}
+
+	public int getMicro() {
+		return micro;
+	}
+
+	public String getQualifier() {
+		return qualifier;
+	}
+
+	public int compareTo(Version other) {
+		if (other == this)
+			return 0;
+
+		Version o = other;
+		if (major != o.major)
+			return major - o.major;
+
+		if (minor != o.minor)
+			return minor - o.minor;
+
+		if (micro != o.micro)
+			return micro - o.micro;
+
+		int c = 0;
+		if (qualifier != null)
+			c = 1;
+		if (o.qualifier != null)
+			c += 2;
+
+		switch (c) {
+			case 0 :
+				return 0;
+			case 1 :
+				return 1;
+			case 2 :
+				return -1;
+		}
+		return qualifier.compareTo(o.qualifier);
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(major);
+		sb.append(".");
+		sb.append(minor);
+		sb.append(".");
+		sb.append(micro);
+		if (qualifier != null) {
+			sb.append(".");
+			sb.append(qualifier);
+		}
+		return sb.toString();
+	}
+
+	public boolean equals(Object ot) {
+		if (!(ot instanceof Version))
+			return false;
+
+		return compareTo((Version) ot) == 0;
+	}
+
+	public int hashCode() {
+		return major * 97 ^ minor * 13 ^ micro + (qualifier == null ? 97 : qualifier.hashCode());
+	}
+
+	public int get(int i) {
+		switch (i) {
+			case 0 :
+				return major;
+			case 1 :
+				return minor;
+			case 2 :
+				return micro;
+			default :
+				throw new IllegalArgumentException("Version can only get 0 (major), 1 (minor), or 2 (micro)");
+		}
+	}
+
+	public static Version parseVersion(String version) {
+		if (version == null) {
+			return LOWEST;
+		}
+
+		version = version.trim();
+		if (version.length() == 0) {
+			return LOWEST;
+		}
+
+		return new Version(version);
+
+	}
+
+	public Version getWithoutQualifier() {
+		return new Version(major, minor, micro);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java b/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
new file mode 100755
index 0000000..8ff69a3
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/VersionRange.java
@@ -0,0 +1,93 @@
+package aQute.bnd.osgi;
+
+import java.util.*;
+import java.util.regex.*;
+
+public class VersionRange {
+	Version			high;
+	Version			low;
+	char			start	= '[';
+	char			end		= ']';
+
+	static Pattern	RANGE	= Pattern.compile("(\\(|\\[)\\s*(" + Version.VERSION_STRING + ")\\s*,\\s*("
+									+ Version.VERSION_STRING + ")\\s*(\\)|\\])");
+
+	public VersionRange(String string) {
+		string = string.trim();
+		Matcher m = RANGE.matcher(string);
+		if (m.matches()) {
+			start = m.group(1).charAt(0);
+			String v1 = m.group(2);
+			String v2 = m.group(10);
+			low = new Version(v1);
+			high = new Version(v2);
+			end = m.group(18).charAt(0);
+			if (low.compareTo(high) > 0)
+				throw new IllegalArgumentException("Low Range is higher than High Range: " + low + "-" + high);
+
+		} else
+			high = low = new Version(string);
+	}
+
+	public boolean isRange() {
+		return high != low;
+	}
+
+	public boolean includeLow() {
+		return start == '[';
+	}
+
+	public boolean includeHigh() {
+		return end == ']';
+	}
+
+	public String toString() {
+		if (high == low)
+			return high.toString();
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(start);
+		sb.append(low);
+		sb.append(',');
+		sb.append(high);
+		sb.append(end);
+		return sb.toString();
+	}
+
+	public Version getLow() {
+		return low;
+	}
+
+	public Version getHigh() {
+		return high;
+	}
+
+	public boolean includes(Version v) {
+		if (!isRange()) {
+			return low.compareTo(v) <= 0;
+		}
+		if (includeLow()) {
+			if (v.compareTo(low) < 0)
+				return false;
+		} else if (v.compareTo(low) <= 0)
+			return false;
+
+		if (includeHigh()) {
+			if (v.compareTo(high) > 0)
+				return false;
+		} else if (v.compareTo(high) >= 0)
+			return false;
+
+		return true;
+	}
+
+	public Iterable<Version> filter(final Iterable<Version> versions) {
+		List<Version> list = new ArrayList<Version>();
+		for (Version v : versions) {
+			if (includes(v))
+				list.add(v);
+		}
+		return list;
+	}
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java
new file mode 100644
index 0000000..494c678
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/WriteResource.java
@@ -0,0 +1,74 @@
+package aQute.bnd.osgi;
+
+import java.io.*;
+
+public abstract class WriteResource implements Resource {
+	String			extra;
+	volatile long	size	= -1;
+
+	public InputStream openInputStream() throws Exception {
+		PipedInputStream pin = new PipedInputStream();
+		final PipedOutputStream pout = new PipedOutputStream(pin);
+		Thread t = new Thread() {
+			public void run() {
+				try {
+					write(pout);
+					pout.flush();
+				}
+				catch (Exception e) {
+					e.printStackTrace();
+				}
+				finally {
+					try {
+						pout.close();
+					}
+					catch (IOException e) {
+						// Ignore
+					}
+				}
+			}
+		};
+		t.start();
+		return pin;
+	}
+
+	public abstract void write(OutputStream out) throws IOException, Exception;
+
+	public abstract long lastModified();
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	static class CountingOutputStream extends OutputStream {
+		long	size;
+
+		@Override
+		public void write(int var0) throws IOException {
+			size++;
+		}
+
+		@Override
+		public void write(byte[] buffer) throws IOException {
+			size += buffer.length;
+		}
+
+		@Override
+		public void write(byte[] buffer, int start, int length) throws IOException {
+			size += length;
+		}
+	}
+
+	public long size() throws IOException, Exception {
+		if (size == -1) {
+			CountingOutputStream cout = new CountingOutputStream();
+			write(cout);
+			size = cout.size;
+		}
+		return size;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/ZipResource.java b/bundleplugin/src/main/java/aQute/bnd/osgi/ZipResource.java
new file mode 100755
index 0000000..edf6a68
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/ZipResource.java
@@ -0,0 +1,83 @@
+package aQute.bnd.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) throws UnsupportedEncodingException {
+		this.zip = zip;
+		this.entry = entry;
+		this.lastModified = lastModified;
+		byte[] data = entry.getExtra();
+		if (data != null)
+			this.extra = new String(data, "UTF-8");
+	}
+
+	public InputStream openInputStream() throws IOException {
+		return zip.getInputStream(entry);
+	}
+
+	public String toString() {
+		return ":" + zip.getName() + "(" + 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 (ZipException ze) {
+			throw new ZipException("The JAR/ZIP file (" + file.getAbsolutePath() + ") seems corrupted, error: "
+					+ ze.getMessage());
+		}
+		catch (FileNotFoundException e) {
+			throw new IllegalArgumentException("Problem opening JAR: " + file.getAbsolutePath());
+		}
+	}
+
+	public void write(OutputStream out) throws Exception {
+		FileResource.copy(this, out);
+	}
+
+	public long lastModified() {
+		return lastModified;
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public void setExtra(String extra) {
+		this.extra = extra;
+	}
+
+	public long size() {
+		return entry.getSize();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/eclipse/EclipseClasspath.java b/bundleplugin/src/main/java/aQute/bnd/osgi/eclipse/EclipseClasspath.java
new file mode 100755
index 0000000..5415985
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/eclipse/EclipseClasspath.java
@@ -0,0 +1,238 @@
+package aQute.bnd.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.service.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$
+ */
+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, @SuppressWarnings("unused") 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 = properties.get(var);
+			if (base != null) {
+				File b = new File(base);
+				File f = new File(b, remainder.replace('/', File.separatorChar));
+				return f;
+			}
+			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;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/packageinfo b/bundleplugin/src/main/java/aQute/bnd/osgi/packageinfo
new file mode 100644
index 0000000..084a0d4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/packageinfo
@@ -0,0 +1 @@
+version 2.0.0
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReq.java b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReq.java
new file mode 100644
index 0000000..d5fe2c6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReq.java
@@ -0,0 +1,103 @@
+package aQute.bnd.osgi.resource;
+
+import java.util.*;
+
+import org.osgi.resource.*;
+
+class CapReq implements Capability, Requirement {
+	
+	static enum MODE { Capability, Requirement }
+	
+	private final MODE mode;
+	private final String	namespace;
+	private final Resource	resource;
+	private final Map<String,String>	directives;
+	private final Map<String,Object>	attributes;
+
+	CapReq(MODE mode, String namespace, Resource resource, Map<String, String> directives, Map<String, Object> attributes) {
+		this.mode = mode;
+		this.namespace = namespace;
+		this.resource = resource;
+		this.directives = new HashMap<String,String>(directives);
+		this.attributes = new HashMap<String,Object>(attributes);
+	}
+
+	public String getNamespace() {
+		return namespace;
+	}
+
+	public Map<String,String> getDirectives() {
+		return Collections.unmodifiableMap(directives);
+	}
+
+	public Map<String,Object> getAttributes() {
+		return Collections.unmodifiableMap(attributes);
+	}
+
+	public Resource getResource() {
+		return resource;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
+		result = prime * result + ((directives == null) ? 0 : directives.hashCode());
+		result = prime * result + ((mode == null) ? 0 : mode.hashCode());
+		result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
+		result = prime * result + ((resource == null) ? 0 : resource.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		CapReq other = (CapReq) obj;
+		if (attributes == null) {
+			if (other.attributes != null)
+				return false;
+		} else if (!attributes.equals(other.attributes))
+			return false;
+		if (directives == null) {
+			if (other.directives != null)
+				return false;
+		} else if (!directives.equals(other.directives))
+			return false;
+		if (mode != other.mode)
+			return false;
+		if (namespace == null) {
+			if (other.namespace != null)
+				return false;
+		} else if (!namespace.equals(other.namespace))
+			return false;
+		if (resource == null) {
+			if (other.resource != null)
+				return false;
+		} else if (!resource.equals(other.resource))
+			return false;
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		if (mode == MODE.Capability) {
+			Object value = attributes.get(namespace);
+			builder.append(namespace).append('=').append(value);
+		} else {
+			String filter = directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+			builder.append(filter);
+			if (Namespace.RESOLUTION_OPTIONAL.equals(directives.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) {
+				builder.append("%OPT");
+			}
+		}
+		return builder.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReqBuilder.java b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReqBuilder.java
new file mode 100644
index 0000000..b06f37a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/CapReqBuilder.java
@@ -0,0 +1,91 @@
+package aQute.bnd.osgi.resource;
+
+import java.util.*;
+
+import org.osgi.framework.namespace.*;
+import org.osgi.resource.*;
+
+import aQute.bnd.osgi.resource.CapReq.MODE;
+import aQute.libg.filters.*;
+
+public class CapReqBuilder {
+
+	private final String				namespace;
+	private Resource					resource;
+	private final Map<String,Object>	attributes	= new HashMap<String,Object>();
+	private final Map<String,String>	directives	= new HashMap<String,String>();
+
+	public CapReqBuilder(String namespace) {
+		this.namespace = namespace;
+	}
+	
+	public static CapReqBuilder clone(Capability capability) {
+		CapReqBuilder builder = new CapReqBuilder(capability.getNamespace());
+		builder.addAttributes(capability.getAttributes());
+		builder.addDirectives(capability.getDirectives());
+		return builder;
+	}
+	
+	public static CapReqBuilder clone(Requirement requirement) {
+		CapReqBuilder builder = new CapReqBuilder(requirement.getNamespace());
+		builder.addAttributes(requirement.getAttributes());
+		builder.addDirectives(requirement.getDirectives());
+		return builder;
+	}
+	
+	public String getNamespace() {
+		return namespace;
+	}
+	
+	public CapReqBuilder setResource(Resource resource) {
+		this.resource = resource;
+		return this;
+	}
+
+	public CapReqBuilder addAttribute(String name, Object value) {
+		attributes.put(name, value);
+		return this;
+	}
+	
+	public CapReqBuilder addAttributes(Map<? extends String, ? extends Object> attributes) {
+		this.attributes.putAll(attributes);
+		return this;
+	}
+
+	public CapReqBuilder addDirective(String name, String value) {
+		directives.put(name, value);
+		return this;
+	}
+	
+	public CapReqBuilder addDirectives(Map<? extends String, ? extends String> directives) {
+		this.directives.putAll(directives);
+		return this;
+	}
+	
+	public Capability buildCapability() {
+		// TODO check the thrown exception
+		if (resource == null) throw new IllegalStateException("Cannot build Capability with null Resource.");
+		return new CapReq(MODE.Capability, namespace, resource, directives, attributes);
+	}
+	
+	public Requirement buildRequirement() {
+		// TODO check the thrown exception
+		if (resource == null) throw new IllegalStateException("Cannot build Requirement with null Resource.");
+		return new CapReq(MODE.Requirement, namespace, resource, directives, attributes);
+	}
+
+	public Requirement buildSyntheticRequirement() {
+		return new CapReq(MODE.Requirement, namespace, null, directives, attributes);
+	}
+	
+	public static final CapReqBuilder createPackageRequirement(String pkgName, String range) {
+		Filter filter;
+		SimpleFilter pkgNameFilter = new SimpleFilter(PackageNamespace.PACKAGE_NAMESPACE, pkgName);
+		if (range != null)
+			filter = new AndFilter().addChild(pkgNameFilter).addChild(new LiteralFilter(Filters.fromVersionRange(range)));
+		else
+			filter = pkgNameFilter;
+		
+		return new CapReqBuilder(PackageNamespace.PACKAGE_NAMESPACE).addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/Filters.java b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/Filters.java
new file mode 100644
index 0000000..3194146
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/Filters.java
@@ -0,0 +1,64 @@
+package aQute.bnd.osgi.resource;
+
+import org.osgi.framework.namespace.*;
+
+import aQute.bnd.osgi.*;
+import aQute.libg.filters.*;
+
+public class Filters {
+	
+	public static final String DEFAULT_VERSION_ATTR = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
+	
+	/**
+	 * Generate an LDAP-style version filter from a version range, e.g.
+	 * {@code [1.0,2.0)} generates {@code (&(version>=1.0)(!(version>=2.0))}
+	 * 
+	 * @param range
+	 * @return The generated filter.
+	 * @throws IllegalArgumentException
+	 *             If the supplied range is invalid.
+	 */
+	public static String fromVersionRange(String range) throws IllegalArgumentException {
+		return fromVersionRange(range, DEFAULT_VERSION_ATTR);
+	}
+
+	/**
+	 * Generate an LDAP-style version filter from a version range, using a
+	 * specific attribute name for the version; for example can be used to
+	 * generate a range using the {@code bundle-version} attribute such as
+	 * {@code (&(bundle-version>=1.0)(!(bundle-version>=2.0))}.
+	 * 
+	 * @param range
+	 * @param versionAttr
+	 * @return The generated filter
+	 * @throws IllegalArgumentException
+	 *             If the supplied range is invalid.
+	 */
+	public static String fromVersionRange(String range, String versionAttr) throws IllegalArgumentException {
+		if (range == null)
+			return null;
+		VersionRange parsedRange = new VersionRange(range);
+		
+		Filter left;
+		if (parsedRange.includeLow())
+			left = new SimpleFilter(versionAttr, Operator.GreaterThanOrEqual, parsedRange.getLow().toString());
+		else
+			left = new NotFilter(new SimpleFilter(versionAttr, Operator.LessThanOrEqual, parsedRange.getLow().toString()));
+		
+		Filter right;
+		if (!parsedRange.isRange())
+			right = null;
+		else if (parsedRange.includeHigh())
+			right = new SimpleFilter(versionAttr, Operator.LessThanOrEqual, parsedRange.getHigh().toString());
+		else
+			right = new NotFilter(new SimpleFilter(versionAttr, Operator.GreaterThanOrEqual, parsedRange.getHigh().toString()));
+		
+		Filter result;
+		if (right != null)
+			result = new AndFilter().addChild(left).addChild(right);
+		else
+			result = left;
+		
+		return result.toString();
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceBuilder.java b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceBuilder.java
new file mode 100644
index 0000000..69e95b0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceBuilder.java
@@ -0,0 +1,55 @@
+package aQute.bnd.osgi.resource;
+
+import java.util.*;
+
+import org.osgi.resource.*;
+
+public class ResourceBuilder {
+
+	private final ResourceImpl		resource		= new ResourceImpl();
+	private final List<Capability>	capabilities	= new LinkedList<Capability>();
+	private final List<Requirement>	requirements	= new LinkedList<Requirement>();
+
+	private boolean					built			= false;
+
+	public ResourceBuilder addCapability(Capability capability) {
+		CapReqBuilder builder = CapReqBuilder.clone(capability);
+		return addCapability(builder);
+	}
+	
+	public ResourceBuilder addCapability(CapReqBuilder builder) {
+		if (built)
+			throw new IllegalStateException("Resource already built");
+
+		Capability cap = builder.setResource(resource).buildCapability();
+		capabilities.add(cap);
+
+		return this;
+	}
+	
+	public ResourceBuilder addRequirement(Requirement requirement) {
+		CapReqBuilder builder = CapReqBuilder.clone(requirement);
+		return addRequirement(builder);
+	}
+	
+	public ResourceBuilder addRequirement(CapReqBuilder builder) {
+		if (built)
+			throw new IllegalStateException("Resource already built");
+
+		Requirement req = builder.setResource(resource).buildRequirement();
+		requirements.add(req);
+
+		return this;
+	}
+
+	public Resource build() {
+		if (built)
+			throw new IllegalStateException("Resource already built");
+		built = true;
+
+		resource.setCapabilities(capabilities);
+		resource.setRequirements(requirements);
+		return resource;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceImpl.java b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceImpl.java
new file mode 100644
index 0000000..f8c3a84
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/ResourceImpl.java
@@ -0,0 +1,73 @@
+package aQute.bnd.osgi.resource;
+
+import java.util.*;
+
+import org.osgi.framework.namespace.*;
+import org.osgi.resource.*;
+
+class ResourceImpl implements Resource {
+
+	private List<Capability>				allCapabilities;
+	private Map<String,List<Capability>>	capabilityMap;
+
+	private List<Requirement>				allRequirements;
+	private Map<String,List<Requirement>>	requirementMap;
+
+	void setCapabilities(List<Capability> capabilities) {
+		allCapabilities = capabilities;
+
+		capabilityMap = new HashMap<String,List<Capability>>();
+		for (Capability capability : capabilities) {
+			List<Capability> list = capabilityMap.get(capability.getNamespace());
+			if (list == null) {
+				list = new LinkedList<Capability>();
+				capabilityMap.put(capability.getNamespace(), list);
+			}
+			list.add(capability);
+		}
+	}
+
+	public List<Capability> getCapabilities(String namespace) {
+		return namespace == null ? allCapabilities : capabilityMap.get(namespace);
+	}
+
+	void setRequirements(List<Requirement> requirements) {
+		allRequirements = requirements;
+
+		requirementMap = new HashMap<String,List<Requirement>>();
+		for (Requirement requirement : requirements) {
+			List<Requirement> list = requirementMap.get(requirement.getNamespace());
+			if (list == null) {
+				list = new LinkedList<Requirement>();
+				requirementMap.put(requirement.getNamespace(), list);
+			}
+			list.add(requirement);
+		}
+	}
+
+	public List<Requirement> getRequirements(String namespace) {
+		return namespace == null ? allRequirements : requirementMap.get(namespace);
+	}
+
+	@Override
+	public String toString() {
+		final StringBuilder builder = new StringBuilder();
+		List<Capability> identities = getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+		if (identities != null && identities.size() == 1) {
+			Capability idCap = identities.get(0);
+			Object id = idCap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
+			Object version = idCap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+			
+			builder.append(id).append(" ver=").append(version);
+		} else {
+			// Generic toString
+			builder.append("ResourceImpl [caps=");
+			builder.append(allCapabilities);
+			builder.append(", reqs=");
+			builder.append(allRequirements);
+			builder.append("]");
+		}
+		return builder.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/osgi/resource/packageinfo b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/osgi/resource/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file