You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by mc...@apache.org on 2011/10/17 12:31:51 UTC

svn commit: r1185095 [10/15] - in /felix/trunk/bundleplugin: ./ src/main/java/aQute/ src/main/java/aQute/bnd/ src/main/java/aQute/bnd/annotation/ src/main/java/aQute/bnd/annotation/component/ src/main/java/aQute/bnd/annotation/metatype/ src/main/java/a...

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,1217 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+import aQute.bnd.component.*;
+import aQute.bnd.make.*;
+import aQute.bnd.make.component.*;
+import aQute.bnd.make.metatype.*;
+import aQute.bnd.maven.*;
+import aQute.bnd.service.*;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ * 
+ * Private-Package: package-decl ( ',' package-decl )*
+ * 
+ * Export-Package: package-decl ( ',' package-decl )*
+ * 
+ * Import-Package: package-decl ( ',' package-decl )*
+ * 
+ * @version $Revision$
+ */
+public class Builder extends Analyzer {
+	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;
+
+	List<File>					sourcePath			= new ArrayList<File>();
+
+	Make						make				= new Make(this);
+
+	public Builder(Processor parent) {
+		super(parent);
+	}
+
+	public Builder() {
+	}
+
+	public Jar build() throws Exception {
+		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");
+
+		dot = new Jar("dot");
+		addClose(dot);
+		try {
+			long modified = Long.parseLong(getProperty("base.modified"));
+			dot.updateModified(modified, "Base modified");
+		} catch (Exception e) {
+		}
+
+		doExpand(dot);
+		doIncludeResources(dot);
+		doConditional(dot);
+		dot = doWab(dot);
+
+		// NEW!
+		// Check if we override the calculation of the
+		// manifest. We still need to calculated it because
+		// we need to have analyzed the classpath.
+
+		Manifest manifest = calcManifest();
+
+		String mf = getProperty(MANIFEST);
+		if (mf != null) {
+			File mff = getFile(mf);
+			if (mff.isFile()) {
+				try {
+					InputStream in = new FileInputStream(mff);
+					manifest = new Manifest(in);
+					in.close();
+				} catch (Exception e) {
+					error(MANIFEST + " while reading manifest file", e);
+				}
+			} else {
+				error(MANIFEST + ", no such file " + mf);
+			}
+		}
+
+		if (getProperty(NOMANIFEST) == null)
+			dot.setManifest(manifest);
+		else
+			dot.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())
+			error("The JAR is empty: " + dot.getName());
+
+		dot.updateModified(lastModified(), "Last Modified Processor");
+		dot.setName(getBsn());
+
+		sign(dot);
+
+		doSaveManifest(dot);
+		return dot;
+	}
+
+	/**
+	 * Allow any local initialization by subclasses before we build.
+	 */
+	public void init() throws Exception {
+		begin();
+		doRequireBnd();
+	}
+
+	/**
+	 * 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;
+
+		setProperty(BUNDLE_CLASSPATH, append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
+
+		Jar next = new Jar(dot.getName());
+		addClose(next);
+
+		for (Map.Entry<String, Resource> entry : dot.getResources().entrySet()) {
+			String path = entry.getKey();
+			if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+				trace("wab: moving: %s", path);
+				next.putResource("WEB-INF/classes/" + path, entry.getValue());
+			} else {
+				trace("wab: not moving: %s", path);
+				next.putResource(path, entry.getValue());
+			}
+		}
+
+		Map<String, Map<String, String>> clauses = parseHeader(getProperty(WABLIB));
+		for (String key : clauses.keySet()) {
+			File f = getFile(key);
+			addWabLib(next, f);
+		}
+		doIncludeResource(next, wab);
+		return next;
+	}
+
+	/**
+	 * 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(File f) {
+	}
+
+	/**
+	 * Sign the jar file.
+	 * 
+	 * -sign : <alias> [ ';' 'password:=' <password> ] [ ';' 'keystore:='
+	 * <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
+	 * 
+	 * @return
+	 */
+
+	void sign(Jar jar) throws Exception {
+		String signing = getProperty("-sign");
+		if (signing == null)
+			return;
+
+		trace("Signing %s, with %s", getBsn(), signing);
+		List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
+
+		Map<String, Map<String, String>> infos = parseHeader(signing);
+		for (Map.Entry<String, Map<String, String>> entry : infos.entrySet()) {
+			for (SignerPlugin signer : signers) {
+				signer.sign(this, entry.getKey());
+			}
+		}
+	}
+
+	public boolean hasSources() {
+		return isTrue(getProperty(SOURCES));
+	}
+
+	protected String getImportPackages() {
+		String ip = super.getImportPackages();
+		if (ip != null)
+			return ip;
+
+		return "*";
+	}
+
+	private void doConditional(Jar dot) throws Exception {
+		Map<String, Map<String, String>> conditionals = getHeader(CONDITIONAL_PACKAGE);
+		if (conditionals.isEmpty())
+			return;
+
+		while (true) {
+			analyze();
+			Map<String, Map<String, String>> imports = getImports();
+
+			// Match the packages specified in conditionals
+			// against the imports. Any match must become a
+			// Private-Package
+			Map<String, Map<String, String>> filtered = merge(CONDITIONAL_PACKAGE, conditionals,
+					imports, new HashSet<String>(), null);
+
+			// Imports can also specify a private import. These
+			// packages must also be copied to the bundle
+			for (Map.Entry<String, Map<String, String>> entry : getImports().entrySet()) {
+				String type = entry.getValue().get(IMPORT_DIRECTIVE);
+				if (type != null && type.equals(PRIVATE_DIRECTIVE))
+					filtered.put(entry.getKey(), entry.getValue());
+			}
+
+			// remove existing packages to prevent merge errors
+			filtered.keySet().removeAll(dot.getPackages());
+			if (filtered.size() == 0)
+				break;
+
+			int size = dot.getResources().size();
+			doExpand(dot, CONDITIONAL_PACKAGE + " Private imports",
+					Instruction.replaceWithInstruction(filtered), false);
+
+			// Were there any expansions?
+			if (size == dot.getResources().size())
+				break;
+
+			analyzed = false;
+		}
+	}
+
+	/**
+	 * 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(imports, null);
+		cleanupVersion(exports, 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(Map<String, Map<String, String>> mapOfMap, String defaultVersion) {
+		for (Iterator<Map.Entry<String, Map<String, String>>> e = mapOfMap.entrySet().iterator(); e
+				.hasNext();) {
+			Map.Entry<String, Map<String, String>> entry = e.next();
+			Map<String, String> attributes = entry.getValue();
+			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<String> packages = new HashSet<String>();
+
+		try {
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			getProperties().store(out, "Generated by BND, at " + new Date());
+			dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out.toByteArray(), 0));
+			out.close();
+		} catch (Exception e) {
+			error("Can not embed bnd file in JAR: " + e);
+		}
+
+		for (Iterator<String> cpe = classspace.keySet().iterator(); cpe.hasNext();) {
+			String path = cpe.next();
+			path = path.substring(0, path.length() - ".class".length()) + ".java";
+			String pack = getPackage(path).replace('.', '/');
+			if (pack.length() > 1)
+				pack = pack + "/";
+			boolean found = false;
+			String[] fixed = { "packageinfo", "package.html", "module-info.java",
+					"package-info.java" };
+			for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
+				File root = i.next();
+				File f = getFile(root, path);
+				if (f.exists()) {
+					found = true;
+					if (!packages.contains(pack)) {
+						packages.add(pack);
+						File bdir = getFile(root, pack);
+						for (int j = 0; j < fixed.length; j++) {
+							File ff = getFile(bdir, fixed[j]);
+							if (ff.isFile()) {
+								dot.putResource("OSGI-OPT/src/" + pack + fixed[j],
+										new FileResource(ff));
+							}
+						}
+					}
+					dot.putResource("OSGI-OPT/src/" + path, new FileResource(f));
+				}
+			}
+			if (!found) {
+				for (Jar jar : classpath) {
+					Resource resource = jar.getResource(path);
+					if (resource != null) {
+						dot.putResource("OSGI-OPT/src/"+path, resource);
+					} else {
+						resource = jar.getResource("OSGI-OPT/src/" + path);
+						if (resource != null) {
+							dot.putResource("OSGI-OPT/src/"+path, resource);
+						}
+					}
+				}
+			}
+			if (getSourcePath().isEmpty())
+				warning("Including sources but " + SOURCEPATH
+						+ " does not contain any source directories ");
+			// TODO copy from the jars where they came from
+		}
+	}
+
+	boolean	firstUse	= true;
+
+	public Collection<File> getSourcePath() {
+		if (firstUse) {
+			firstUse = false;
+			String sp = getProperty(SOURCEPATH);
+			if (sp != null) {
+				Map<String, Map<String, String>> map = parseHeader(sp);
+				for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+					String file = i.next();
+					if (!isDuplicate(file)) {
+						File f = getFile(file);
+						if (!f.isDirectory()) {
+							error("Adding a sourcepath that is not a directory: " + f);
+						} else {
+							sourcePath.add(f);
+						}
+					}
+				}
+			}
+		}
+		return sourcePath;
+	}
+
+	private void doVerify(Jar dot) throws Exception {
+		Verifier verifier = new Verifier(dot, getProperties());
+		verifier.setPedantic(isPedantic());
+
+		// Give the verifier the benefit of our analysis
+		// prevents parsing the files twice
+		verifier.setClassSpace(classspace, contained, referred, uses);
+		verifier.verify();
+		getInfo(verifier);
+	}
+
+	private void doExpand(Jar jar) throws IOException {
+		if (getClasspath().size() == 0
+				&& (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
+			warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
+
+		Map<Instruction, Map<String, String>> privateMap = Instruction
+				.replaceWithInstruction(getHeader(PRIVATE_PACKAGE));
+		Map<Instruction, Map<String, String>> exportMap = Instruction
+				.replaceWithInstruction(getHeader(EXPORT_PACKAGE));
+
+		if (isTrue(getProperty(Constants.UNDERTEST))) {
+			privateMap.putAll(Instruction.replaceWithInstruction(parseHeader(getProperty(
+					Constants.TESTPACKAGES, "test;presence:=optional"))));
+		}
+		if (!privateMap.isEmpty())
+			doExpand(jar, "Private-Package, or -testpackages", privateMap, true);
+
+		if (!exportMap.isEmpty()) {
+			Jar exports = new Jar("exports");
+			doExpand(exports, EXPORT_PACKAGE, exportMap, true);
+			jar.addAll(exports);
+			exports.close();
+		}
+
+		if (!isNoBundle()) {
+			if (privateMap.isEmpty() && exportMap.isEmpty() && !isResourceOnly()
+					&& getProperty(EXPORT_CONTENTS) == null) {
+				warning("None of Export-Package, Provide-Package, Private-Package, -testpackages, or -exportcontents is set, therefore no packages will be included");
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @param jar
+	 * @param name
+	 * @param instructions
+	 */
+	private void doExpand(Jar jar, String name, Map<Instruction, Map<String, String>> instructions,
+			boolean mandatory) {
+		Set<Instruction> superfluous = removeMarkedDuplicates(instructions.keySet());
+
+		for (Iterator<Jar> c = getClasspath().iterator(); c.hasNext();) {
+			Jar now = c.next();
+			doExpand(jar, instructions, now, superfluous);
+		}
+
+		if (mandatory && superfluous.size() > 0) {
+			StringBuilder sb = new StringBuilder();
+			String del = "Instructions in " + name + " that are never used: ";
+			for (Iterator<Instruction> i = superfluous.iterator(); i.hasNext();) {
+				Instruction p = i.next();
+				sb.append(del);
+				sb.append(p.toString());
+				del = "\n                ";
+			}
+			sb.append("\nClasspath: ");
+			sb.append(Processor.join(getClasspath()));
+			sb.append("\n");
+
+			warning(sb.toString());
+			if (isPedantic())
+				diagnostics = true;
+		}
+	}
+
+	/**
+	 * Iterate over each directory in the class path entry and check if that
+	 * directory is a desired package.
+	 * 
+	 * @param included
+	 * @param classpathEntry
+	 */
+	private void doExpand(Jar jar, Map<Instruction, Map<String, String>> included,
+			Jar classpathEntry, Set<Instruction> superfluous) {
+
+		loop: for (Map.Entry<String, Map<String, Resource>> directory : classpathEntry
+				.getDirectories().entrySet()) {
+			String path = directory.getKey();
+
+			if (doNotCopy(getName(path)))
+				continue;
+
+			if (directory.getValue() == null)
+				continue;
+
+			String pack = path.replace('/', '.');
+			Instruction instr = matches(included, pack, superfluous, classpathEntry.getName());
+			if (instr != null) {
+				// System.out.println("Pattern match: " + pack + " " +
+				// instr.getPattern() + " " + instr.isNegated());
+				if (!instr.isNegated()) {
+					Map<String, Resource> contents = directory.getValue();
+
+					// What to do with split packages? Well if this
+					// directory already exists, we will check the strategy
+					// and react accordingly.
+					boolean overwriteResource = true;
+					if (jar.hasDirectory(path)) {
+						Map<String, String> directives = included.get(instr);
+
+						switch (getSplitStrategy((String) directives.get(SPLIT_PACKAGE_DIRECTIVE))) {
+						case SPLIT_MERGE_LAST:
+							overwriteResource = true;
+							break;
+
+						case SPLIT_MERGE_FIRST:
+							overwriteResource = false;
+							break;
+
+						case SPLIT_ERROR:
+							error(diagnostic(pack, getClasspath(), classpathEntry.source));
+							continue loop;
+
+						case SPLIT_FIRST:
+							continue loop;
+
+						default:
+							warning(diagnostic(pack, getClasspath(), classpathEntry.source));
+							overwriteResource = false;
+							break;
+						}
+					}
+
+					jar.addDirectory(contents, overwriteResource);
+
+					String key = path + "/bnd.info";
+					Resource r = jar.getResource(key);
+					if (r != null)
+						jar.putResource(key, new PreprocessResource(this, r));
+
+					if (hasSources()) {
+						String srcPath = "OSGI-OPT/src/" + path;
+						Map<String, Resource> srcContents = classpathEntry.getDirectories().get(
+								srcPath);
+						if (srcContents != null) {
+							jar.addDirectory(srcContents, overwriteResource);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Analyze the classpath for a split package
+	 * 
+	 * @param pack
+	 * @param classpath
+	 * @param source
+	 * @return
+	 */
+	private String diagnostic(String pack, List<Jar> classpath, File source) {
+		// Default is like merge-first, but with a warning
+		// Find the culprits
+		pack = pack.replace('.', '/');
+		List<Jar> culprits = new ArrayList<Jar>();
+		for (Iterator<Jar> i = classpath.iterator(); i.hasNext();) {
+			Jar culprit = (Jar) i.next();
+			if (culprit.getDirectories().containsKey(pack)) {
+				culprits.add(culprit);
+			}
+		}
+		return "Split package "
+				+ pack
+				+ "\nUse directive -split-package:=(merge-first|merge-last|error|first) on Export/Private Package instruction to get rid of this warning\n"
+				+ "Package found in   " + culprits + "\n" + "Reference from     " + source + "\n"
+				+ "Classpath          " + classpath;
+	}
+
+	private int getSplitStrategy(String type) {
+		if (type == null)
+			return SPLIT_DEFAULT;
+
+		if (type.equals("merge-last"))
+			return SPLIT_MERGE_LAST;
+
+		if (type.equals("merge-first"))
+			return SPLIT_MERGE_FIRST;
+
+		if (type.equals("error"))
+			return SPLIT_ERROR;
+
+		if (type.equals("first"))
+			return SPLIT_FIRST;
+
+		error("Invalid strategy for split-package: " + type);
+		return SPLIT_DEFAULT;
+	}
+
+	/**
+	 * Matches the instructions against a package.
+	 * 
+	 * @param instructions
+	 *            The list of instructions
+	 * @param pack
+	 *            The name of the package
+	 * @param superfluousPatterns
+	 *            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(Map<Instruction, Map<String, String>> instructions, String pack,
+			Set<Instruction> superfluousPatterns, String source) {
+		for (Map.Entry<Instruction, Map<String, String>> 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 = Instruction.getPattern(from);
+				if (!f.matches(source) || f.isNegated())
+					return null;
+			}
+
+			// Now do the normal
+			// matching
+			if (pattern.matches(pack)) {
+				if (superfluousPatterns != null)
+					superfluousPatterns.remove(pattern);
+				return pattern;
+			}
+		}
+		return null;
+	}
+
+	private Map<String, Map<String, String>> getHeader(String string) {
+		if (string == null)
+			return Collections.emptyMap();
+		return parseHeader(getProperty(string));
+	}
+
+	/**
+	 * Parse the Bundle-Includes header. Files in the bundles Include header are
+	 * included in the jar. The source can be a directory or a file.
+	 * 
+	 * @throws IOException
+	 * @throws FileNotFoundException
+	 */
+	private void doIncludeResources(Jar jar) throws Exception {
+		String includes = getProperty("Bundle-Includes");
+		if (includes == null) {
+			includes = getProperty(INCLUDERESOURCE);
+			if (includes == null || includes.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 {
+		Map<String, Map<String, String>> clauses = parseHeader(includes);
+		doIncludeResource(jar, clauses);
+	}
+
+	private void doIncludeResource(Jar jar, Map<String, Map<String, String>> clauses)
+			throws ZipException, IOException, Exception {
+		for (Map.Entry<String, Map<String, String>> 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;
+		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("@")) {
+			extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination);
+		} else if (extra.containsKey("literal")) {
+			String literal = (String) extra.get("literal");
+			Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
+			String x = (String) extra.get("extra");
+			if (x != null)
+				r.setExtra(x);
+			jar.putResource(name, r);
+		} else {
+			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()) {
+				noSuchFile(jar, name, extra, source, destinationPath);
+			} else
+				copy(jar, destinationPath, sourceFile, preprocess, extra);
+		}
+	}
+
+	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);
+		}
+
+		InstructionFilter iFilter = null;
+		if (filter != null) {
+			iFilter = new InstructionFilter(Instruction.getPattern(filter), recursive,
+					getDoNotCopy());
+		} else {
+			iFilter = new InstructionFilter(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, String clause, Map<String, String> extra, String source,
+			String destinationPath) throws Exception {
+		Jar src = getJarFromName(source, "Include-Resource " + source);
+		if (src != null) {
+			JarResource jarResource = new JarResource(src);
+			jar.putResource(destinationPath, jarResource);
+		} else {
+			Resource lastChance = make.process(source);
+			if (lastChance != null) {
+				String x = extra.get("extra");
+				if (x != null)
+					lastChance.setExtra(x);
+				jar.putResource(destinationPath, lastChance);
+			} else
+				error("Input file does not exist: " + source);
+		}
+	}
+
+	/**
+	 * Extra resources from a Jar and add them to the given jar. The clause is
+	 * the
+	 * 
+	 * @param jar
+	 * @param clauses
+	 * @param i
+	 * @throws ZipException
+	 * @throws IOException
+	 */
+	private void extractFromJar(Jar jar, String source, String destination) 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 = Instruction.getPattern(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)
+			error("Can not find JAR file " + source);
+		else {
+			jar.addAll(sub, instr, destination);
+		}
+	}
+
+	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);
+			}
+		}
+	}
+
+	private String getName(String where) {
+		int n = where.lastIndexOf('/');
+		if (n < 0)
+			return where;
+
+		return where.substring(n + 1);
+	}
+
+	public void setSourcepath(File[] files) {
+		for (int i = 0; i < files.length; i++)
+			addSourcepath(files[i]);
+	}
+
+	public void addSourcepath(File cp) {
+		if (!cp.exists())
+			warning("File on sourcepath that does not exist: " + cp);
+
+		sourcePath.add(cp);
+	}
+
+	public void close() {
+		super.close();
+	}
+
+	/**
+	 * Build Multiple jars. If the -sub command is set, we filter the file with
+	 * the given patterns.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	public Jar[] builds() throws Exception {
+		begin();
+
+		// Are we acting as a conduit for another JAR?
+		String conduit = getProperty(CONDUIT);
+		if (conduit != null) {
+			Map<String, Map<String, String>> map = parseHeader(conduit);
+			Jar[] result = new Jar[map.size()];
+			int n = 0;
+			for (String file : map.keySet()) {
+				Jar c = new Jar(getFile(file));
+				addClose(c);
+				String name = map.get(file).get("name");
+				if (name != null)
+					c.setName(name);
+
+				result[n++] = c;
+			}
+			return result;
+		}
+
+		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 = (String) 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;
+
+		Map<String, Map<String, String>> 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();
+			}
+		}
+
+		Set<Instruction> subs = Instruction.replaceWithInstruction(subsMap).keySet();
+
+		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 = subs.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[]) throws IOException {
+		StringBuilder sb = new StringBuilder();
+
+		for (String arg : args) {
+			if ("packages".equals(arg) || "all".equals(arg)) {
+				for (String imp : getImports().keySet()) {
+					if (!imp.startsWith("java.")) {
+						sb.append("(org.osgi.framework.PackagePermission \"");
+						sb.append(imp);
+						sb.append("\" \"import\")\r\n");
+					}
+				}
+				for (String exp : getExports().keySet()) {
+					sb.append("(org.osgi.framework.PackagePermission \"");
+					sb.append(exp);
+					sb.append("\" \"export\")\r\n");
+				}
+			} else if ("admin".equals(arg) || "all".equals(arg)) {
+				sb.append("(org.osgi.framework.AdminPermission)");
+			} else if ("permissions".equals(arg))
+				;
+			else
+				error("Invalid option in ${permissions}: %s", arg);
+		}
+		return sb.toString();
+	}
+
+	/**
+     * 
+     */
+	public void removeBundleSpecificHeaders() {
+		Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
+		setForceLocal(set);
+	}
+
+	/**
+	 * 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-Pacakge or Private-Package
+	 * include this resource.
+	 * 
+	 * For now, include resources are skipped.
+	 * 
+	 * @param f
+	 * @return
+	 */
+	public boolean isInScope(Collection<File> resources) throws Exception {
+		Map<String, Map<String, String>> 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")));
+		}
+		Map<Instruction, Map<String, String>> instructions = Instruction
+				.replaceWithInstruction(clauses);
+
+		for (File r : resources) {
+			String cpEntry = getClasspathEntrySuffix(r);
+			if (cpEntry != null) {
+				String pack = Clazz.getPackage(cpEntry);
+				Instruction i = matches(instructions, pack, null, r.getName());
+				if (i != null)
+					return !i.isNegated();
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Answer the string of the resource that it has in the container.
+	 * 
+	 * @param resource
+	 *            The resource to look for
+	 * @return
+	 * @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 (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);
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Builder.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,88 @@
+package aQute.lib.osgi;
+
+public class ClassDataCollector {
+    public void classBegin(int access, String name) {
+    }
+
+    public boolean classStart(int access, String name) {
+        classBegin(access,name);
+        return true;
+    }
+
+    public void extendsClass(String name) {
+    }
+
+    public void implementsInterfaces(String name[]) {
+    }
+
+    public void addReference(String token) {
+    }
+
+    public void annotation(Annotation annotation) {
+    }
+
+    public void parameter(int p) {
+    }
+
+    public void method(Clazz.MethodDef defined) {
+        if (defined.isConstructor())
+            constructor(defined.access, defined.descriptor);
+        else
+            method(defined.access, defined.name, defined.descriptor);
+    }
+
+    public void field(Clazz.FieldDef defined) {
+        field(defined.access, defined.name, defined.descriptor);
+    }
+
+    public void reference(Clazz.MethodDef referenced) {
+    }
+
+    public void reference(Clazz.FieldDef referenced) {
+    }
+
+    public void classEnd() {
+    }
+
+    @Deprecated // Will really be removed!
+    public void field(int access, String name, String descriptor) {
+    }
+
+    @Deprecated // Will really be removed!
+    public void constructor(int access, String descriptor) {
+    }
+
+    @Deprecated // Will really be removed!
+    public void method(int access, String name, String descriptor) {
+    }
+
+    /**
+     * 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(String 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 
+	 */
+	public void innerClass(String innerClass, String outerClass, String innerName,
+			int innerClassAccessFlags) {		
+	}
+
+	public void signature(String signature) {
+	}
+
+	public void constant(Object object) {
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ClassDataCollector.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,1608 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.nio.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.annotation.*;
+import aQute.libg.generics.*;
+
+public class Clazz {
+
+	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 {
+		UNKNOWN(Integer.MAX_VALUE), OpenJDK7(51), J2S6(50), J2SE5(49), JDK1_4(48), JDK1_3(47), JDK1_2(
+				46), JDK1_1(45);
+
+		final int	major;
+
+		JAVA(int major) {
+			this.major = major;
+		}
+
+		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 enum QUERY {
+		IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATION, RUNTIMEANNOTATIONS, CLASSANNOTATIONS
+	};
+
+	public static EnumSet<QUERY>	HAS_ARGUMENT	= EnumSet.of(QUERY.IMPLEMENTS, QUERY.EXTENDS,
+															QUERY.IMPORTS, QUERY.NAMED,
+															QUERY.VERSION, QUERY.ANNOTATION);
+
+	/**
+	 * <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
+
+	// abstract;
+	// may
+	// not
+	// be
+
+	// instantiated.
+
+	final static int				ACC_ENUM		= 0x04000;
+
+	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;
+	}
+
+	static public class FieldDef implements Comparable<FieldDef> {
+		public FieldDef(int access, String clazz, String name, String descriptor) {
+			this.access = access;
+			this.clazz = clazz.replace('/', '.');
+			this.name = name;
+			this.descriptor = descriptor;
+		}
+
+		final public int	access;
+		final public String	clazz;
+		final public String	name;
+		final public String	descriptor;
+		public String		signature;
+		public Object		constant;
+
+		public boolean equals(Object other) {
+			if (!(other instanceof MethodDef))
+				return false;
+
+			FieldDef m = (FieldDef) other;
+			return clazz.equals(m.clazz) && name.equals(m.name) && descriptor.equals(m.descriptor);
+		}
+
+		public int hashCode() {
+			return clazz.hashCode() ^ name.hashCode() ^ descriptor.hashCode();
+		}
+
+		public int compareTo(FieldDef o) {
+			int result = clazz.compareTo(o.clazz);
+			if (result == 0) {
+				result = name.compareTo(o.name);
+				if (result == 0) {
+					result = descriptor.compareTo(o.descriptor);
+				}
+			}
+			return result;
+		}
+
+		public String getPretty() {
+			return name;
+		}
+
+		public String toString() {
+			return getPretty();
+		}
+
+		public boolean isEnum() {
+			return (access & ACC_ENUM) != 0;
+		}
+	}
+
+	static public class MethodDef extends FieldDef {
+		Pattern	METHOD_DESCRIPTOR	= Pattern.compile("\\((.*)\\)(.+)");
+
+		public MethodDef(int access, String clazz, String method, String descriptor) {
+			super(access, clazz, method, descriptor);
+		}
+
+		public boolean isConstructor() {
+			return name.equals("<init>") || name.equals("<clinit>");
+		}
+
+		public String getReturnType() {
+			String use = descriptor;
+			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 getPretty() {
+
+			StringBuilder sb = new StringBuilder();
+			sb.append(descriptor.charAt(0));
+			int index = 1;
+			String del = "";
+			while (index < descriptor.length() && descriptor.charAt(index) != ')') {
+				sb.append(del);
+				index = printParameter(sb, descriptor, index);
+				del = ",";
+			}
+			sb.append(descriptor.charAt(index++));
+			StringBuilder sb2 = new StringBuilder();
+			if (isConstructor()) {
+				sb2.append(getShortName(clazz));
+				index++; // skip the V
+			} else {
+				printParameter(sb2, descriptor, index);
+				sb2.append(" ");
+				sb2.append(getShortName(clazz));
+				sb2.append(".");
+				sb2.append(name);
+			}
+			sb2.append(sb);
+			return sb2.toString();
+		}
+
+		private int printParameter(StringBuilder sb, CharSequence descriptor, int index) {
+			char c = descriptor.charAt(index++);
+			switch (c) {
+			case 'B':
+				sb.append("byte");
+				break;
+			case 'C':
+				sb.append("char");
+				break;
+			case 'D':
+				sb.append("double");
+				break;
+			case 'F':
+				sb.append("float");
+				break;
+			case 'I':
+				sb.append("int");
+				break;
+			case 'J':
+				sb.append("long");
+				break;
+			case 'S':
+				sb.append("short");
+				break;
+			case 'Z':
+				sb.append("boolean");
+				break;
+			case 'V':
+				sb.append("void");
+				break;
+			case 'L':
+				index = reference(sb, descriptor, index);
+				break;
+
+			case '[':
+				index = array(sb, descriptor, index);
+				break;
+			}
+			return index;
+		}
+
+		private int reference(StringBuilder sb, CharSequence descriptor, int index) {
+			int n = sb.length();
+			int lastSlash = n;
+			while (index < descriptor.length() && descriptor.charAt(index) != ';') {
+				char c = descriptor.charAt(index++);
+				if (c == '/') {
+					c = '.';
+					lastSlash = sb.length() + 1;
+				}
+				sb.append(c);
+			}
+			if (lastSlash != n) {
+				sb.delete(n, lastSlash);
+			}
+			return ++index;
+		}
+
+		private int array(StringBuilder sb, CharSequence descriptor, int index) {
+			int n = 1;
+			while (index < descriptor.length() && descriptor.charAt(index) == '[') {
+				index++;
+			}
+			index = printParameter(sb, descriptor, index);
+			while (n-- > 0) {
+				sb.append("[]");
+			}
+			return index;
+		}
+	}
+
+	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
+									};
+
+	boolean				isAbstract;
+	boolean				isPublic;
+	boolean				isEnum;
+	boolean				hasRuntimeAnnotations;
+	boolean				hasClassAnnotations;
+
+	String				className;
+	Object				pool[];
+	int					intPool[];
+	Set<String>			imports		= Create.set();
+	String				path;
+	int					minor		= 0;
+	int					major		= 0;
+	int					access		= 0;
+	String				sourceFile;
+	Set<String>			xref;
+	Set<Integer>		classes;
+	Set<Integer>		descriptors;
+	Set<String>			annotations;
+	int					forName		= 0;
+	int					class$		= 0;
+	String[]			interfaces;
+	String				zuper;
+	ClassDataCollector	cd			= null;
+	Resource			resource;
+	FieldDef			last		= null;
+
+	public Clazz(String path, Resource resource) {
+		this.path = path;
+		this.resource = resource;
+	}
+
+	public Set<String> parseClassFile() throws Exception {
+		return parseClassFileWithCollector(null);
+	}
+
+	public Set<String> parseClassFile(InputStream in) throws IOException {
+		return parseClassFile(in, null);
+	}
+
+	public Set<String> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
+		InputStream in = resource.openInputStream();
+		try {
+			return parseClassFile(in, cd);
+		} finally {
+			in.close();
+		}
+	}
+
+	public Set<String> parseClassFile(InputStream in, ClassDataCollector cd) throws IOException {
+		DataInputStream din = new DataInputStream(in);
+		try {
+			this.cd = cd;
+			return parseClassFile(din);
+		} finally {
+			cd = null;
+			din.close();
+		}
+	}
+
+	Set<String> parseClassFile(DataInputStream in) throws IOException {
+
+		xref = new HashSet<String>();
+		classes = new HashSet<Integer>();
+		descriptors = new HashSet<Integer>();
+
+		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
+		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);
+		/*
+		 * Parse after the constant pool, code thanks to Hans Christian
+		 * Falkenberg
+		 */
+
+		int access_flags = in.readUnsignedShort(); // access
+		isAbstract = (access_flags & ACC_ABSTRACT) != 0;
+		isPublic = (access_flags & ACC_PUBLIC) != 0;
+		isEnum = (access_flags & ACC_ENUM) != 0;
+
+		int this_class = in.readUnsignedShort();
+		className = (String) pool[intPool[this_class]];
+
+		try {
+
+			if (cd != null) {
+				if (!cd.classStart(access_flags, className))
+					return null;
+			}
+
+			int super_class = in.readUnsignedShort();
+			zuper = (String) pool[intPool[super_class]];
+			if (zuper != null) {
+				String pack = getPackage(zuper);
+				packageReference(pack);
+				if (cd != null)
+					cd.extendsClass(zuper);
+			}
+
+			int interfacesCount = in.readUnsignedShort();
+			if (interfacesCount > 0) {
+				interfaces = new String[interfacesCount];
+				for (int i = 0; i < interfacesCount; i++)
+					interfaces[i] = (String) pool[intPool[in.readUnsignedShort()]];
+				if (cd != null)
+					cd.implementsInterfaces(interfaces);
+			}
+
+			int fieldsCount = in.readUnsignedShort();
+			for (int i = 0; i < fieldsCount; i++) {
+				access_flags = in.readUnsignedShort(); // skip access flags
+				int name_index = in.readUnsignedShort();
+				int descriptor_index = in.readUnsignedShort();
+
+				// Java prior to 1.5 used a weird
+				// static variable to hold the com.X.class
+				// result construct. If it did not find it
+				// it would create a variable class$com$X
+				// that would be used to hold the class
+				// object gotten with Class.forName ...
+				// Stupidly, they did not actively use the
+				// class name for the field type, so bnd
+				// would not see a reference. We detect
+				// this case and add an artificial descriptor
+				String name = pool[name_index].toString(); // name_index
+				if (name.startsWith("class$")) {
+					crawl = true;
+				}
+				if (cd != null)
+					cd.field(last = new FieldDef(access_flags, className, name,
+							pool[descriptor_index].toString()));
+				descriptors.add(new Integer(descriptor_index));
+				doAttributes(in, ElementType.FIELD, false);
+			}
+
+			//
+			// Check if we have to crawl the code to find
+			// the ldc(_w) <string constant> invokestatic Class.forName
+			// if so, calculate the method ref index so we
+			// can do this efficiently
+			//
+			if (crawl) {
+				forName = findMethodReference("java/lang/Class", "forName",
+						"(Ljava/lang/String;)Ljava/lang/Class;");
+				class$ = findMethodReference(className, "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, "class$",
+							"(Ljava/lang/String;)Ljava/lang/Class;");
+				}
+			}
+
+			//
+			// Handle the methods
+			//
+			int methodCount = in.readUnsignedShort();
+			for (int i = 0; i < methodCount; i++) {
+				access_flags = in.readUnsignedShort();
+				int name_index = in.readUnsignedShort();
+				int descriptor_index = in.readUnsignedShort();
+				descriptors.add(new Integer(descriptor_index));
+				String name = pool[name_index].toString();
+				String descriptor = pool[descriptor_index].toString();
+				if (cd != null) {
+					MethodDef mdef = new MethodDef(access_flags, className, name, descriptor);
+					last = mdef;
+					cd.method(mdef);
+				}
+
+				if ("<init>".equals(name)) {
+					doAttributes(in, ElementType.CONSTRUCTOR, crawl);
+				} else {
+					doAttributes(in, ElementType.METHOD, crawl);
+				}
+			}
+
+			doAttributes(in, ElementType.TYPE, false);
+
+			//
+			// Now iterate over all classes we found and
+			// parse those as well. We skip duplicates
+			//
+
+			for (int n : classes) {
+				String clazz = (String) pool[n];
+				if (clazz.endsWith(";") || clazz.startsWith("["))
+					parseReference(clazz, 0);
+				else {
+
+					String pack = getPackage(clazz);
+					packageReference(pack);
+				}
+			}
+
+			//
+			// Parse all the descriptors we found
+			//
+
+			for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
+				Integer index = e.next();
+				String prototype = (String) pool[index.intValue()];
+				if (prototype != null)
+					parseDescriptor(prototype);
+				else
+					System.err.println("Unrecognized descriptor: " + index);
+			}
+			Set<String> xref = this.xref;
+			reset();
+			return xref;
+		} finally {
+			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(Object[] pool, int[] intPool) {
+	}
+
+	/**
+	 * @param in
+	 * @param poolIndex
+	 * @param tag
+	 * @throws IOException
+	 */
+	protected void nameAndType(DataInputStream in, int poolIndex, byte tag) throws IOException {
+		int name_index = in.readUnsignedShort();
+		int descriptor_index = in.readUnsignedShort();
+		descriptors.add(new Integer(descriptor_index));
+		pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
+	}
+
+	/**
+	 * @param in
+	 * @param poolIndex
+	 * @param tag
+	 * @throws IOException
+	 */
+	private void methodRef(DataInputStream in, int poolIndex) throws IOException {
+		int class_index = in.readUnsignedShort();
+		int name_and_type_index = in.readUnsignedShort();
+		pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
+	}
+
+	/**
+	 * @param in
+	 * @param poolIndex
+	 * @throws IOException
+	 */
+	private void constantString(DataInputStream in, int poolIndex) throws IOException {
+		int string_index = in.readUnsignedShort();
+		intPool[poolIndex] = string_index;
+	}
+
+	/**
+	 * @param in
+	 * @param poolIndex
+	 * @throws IOException
+	 */
+	protected void constantClass(DataInputStream in, int poolIndex) throws IOException {
+		int class_index = in.readUnsignedShort();
+		classes.add(new Integer(class_index));
+		intPool[poolIndex] = class_index;
+		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();
+		xref.add(name);
+		pool[poolIndex] = name;
+	}
+
+	/**
+	 * Find a method reference in the pool that points to the given class,
+	 * methodname and descriptor.
+	 * 
+	 * @param clazz
+	 * @param methodname
+	 * @param descriptor
+	 * @return index in constant pool
+	 */
+	private int 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
+	 * @throws IOException
+	 */
+	private void doAttributes(DataInputStream in, ElementType member, boolean crawl)
+			throws IOException {
+		int attributesCount = in.readUnsignedShort();
+		for (int j = 0; j < attributesCount; j++) {
+			// skip name CONSTANT_Utf8 pointer
+			doAttribute(in, member, crawl);
+		}
+	}
+
+	/**
+	 * Process a single attribute, if not recognized, skip it.
+	 * 
+	 * @param in
+	 *            the data stream
+	 * @throws IOException
+	 */
+	private void doAttribute(DataInputStream in, ElementType member, boolean crawl)
+			throws IOException {
+		int attribute_name_index = in.readUnsignedShort();
+		String attributeName = (String) pool[attribute_name_index];
+		long attribute_length = in.readInt();
+		attribute_length &= 0xFFFFFFFF;
+		if ("RuntimeVisibleAnnotations".equals(attributeName))
+			doAnnotations(in, 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);
+		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];
+			String cName = (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 IOException
+	 */
+	private void doInnerClasses(DataInputStream in) throws IOException {
+		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) {
+				String innerClass = null;
+				String outerClass = null;
+				String innerName = null;
+
+				if (inner_class_info_index != 0) {
+					int nameIndex = intPool[inner_class_info_index];
+					innerClass = (String) pool[nameIndex];
+				}
+
+				if (outer_class_info_index != 0) {
+					int nameIndex = intPool[outer_class_info_index];
+					outerClass = (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
+	 */
+
+	void doSignature(DataInputStream in, ElementType member) throws IOException {
+		int signature_index = in.readUnsignedShort();
+		String signature = (String) pool[signature_index];
+
+		// System.out.println("Signature " + signature );
+
+		// The type signature is kind of weird,
+		// lets skip it for now. Seems to be some kind of
+		// type variable name index but it does not seem to
+		// conform to the language specification.
+		if (member != ElementType.TYPE)
+			parseDescriptor(signature);
+
+		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 IOException
+	 */
+	private void doCode(DataInputStream in) throws IOException {
+		/* int max_stack = */in.readUnsignedShort();
+		/* int max_locals = */in.readUnsignedShort();
+		int code_length = in.readInt();
+		byte code[] = new byte[code_length];
+		in.readFully(code);
+		crawl(code);
+		int exception_table_length = in.readUnsignedShort();
+		in.skipBytes(exception_table_length * 8);
+		doAttributes(in, ElementType.METHOD, false);
+	}
+
+	/**
+	 * We must find Class.forName references ...
+	 * 
+	 * @param code
+	 */
+	protected void crawl(byte[] code) {
+		ByteBuffer bb = ByteBuffer.wrap(code);
+		bb.order(ByteOrder.BIG_ENDIAN);
+		int lastReference = -1;
+
+		while (bb.remaining() > 0) {
+			int instruction = 0xFF & bb.get();
+			switch (instruction) {
+			case OpCodes.ldc:
+				lastReference = 0xFF & bb.get();
+				break;
+
+			case OpCodes.ldc_w:
+				lastReference = 0xFFFF & bb.getShort();
+				break;
+
+			case OpCodes.invokespecial: {
+				int mref = 0xFFFF & bb.getShort();
+				if (cd != null)
+					cd.reference(getMethodDef(0, mref));
+				break;
+			}
+
+			case OpCodes.invokevirtual: {
+				int mref = 0xFFFF & bb.getShort();
+				if (cd != null)
+					cd.reference(getMethodDef(0, mref));
+				break;
+			}
+
+			case OpCodes.invokeinterface: {
+				int mref = 0xFFFF & bb.getShort();
+				if (cd != null)
+					cd.reference(getMethodDef(0, mref));
+				break;
+			}
+
+			case OpCodes.invokestatic: {
+				int methodref = 0xFFFF & bb.getShort();
+				if (cd != null)
+					cd.reference(getMethodDef(0, methodref));
+
+				if ((methodref == forName || methodref == class$) && lastReference != -1
+						&& pool[intPool[lastReference]] instanceof String) {
+					String clazz = (String) pool[intPool[lastReference]];
+					if (clazz.startsWith("[") || clazz.endsWith(";"))
+						parseReference(clazz, 0);
+					else {
+						int n = clazz.lastIndexOf('.');
+						if (n > 0)
+							packageReference(clazz.substring(0, n));
+					}
+				}
+				break;
+			}
+
+			case OpCodes.tableswitch:
+				// Skip to place divisible by 4
+				while ((bb.position() & 0x3) != 0)
+					bb.get();
+				/* int deflt = */
+				bb.getInt();
+				int low = bb.getInt();
+				int high = bb.getInt();
+				bb.position(bb.position() + (high - low + 1) * 4);
+				lastReference = -1;
+				break;
+
+			case OpCodes.lookupswitch:
+				// Skip to place divisible by 4
+				while ((bb.position() & 0x3) != 0)
+					bb.get();
+				/* deflt = */
+				bb.getInt();
+				int npairs = bb.getInt();
+				bb.position(bb.position() + npairs * 8);
+				lastReference = -1;
+				break;
+
+			default:
+				lastReference = -1;
+				bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
+			}
+		}
+	}
+
+	private void doSourceFile(DataInputStream in) throws IOException {
+		int sourcefile_index = in.readUnsignedShort();
+		this.sourceFile = pool[sourcefile_index].toString();
+	}
+
+	private void doParameterAnnotations(DataInputStream in, 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<String>();
+
+		annotations.add(pool[type_index].toString());
+
+		if (policy == RetentionPolicy.RUNTIME) {
+			descriptors.add(new Integer(type_index));
+			hasRuntimeAnnotations = true;
+		} else {
+			hasClassAnnotations = true;
+		}
+		String name = (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);
+		else
+			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)
+				descriptors.add(new Integer(type_name_index));
+			int const_name_index = in.readUnsignedShort();
+			return pool[const_name_index];
+
+		case 'c': // Class
+			int class_info_index = in.readUnsignedShort();
+			if (policy == RetentionPolicy.RUNTIME)
+				descriptors.add(new Integer(class_info_index));
+			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 pack
+	 *            A '.' delimited package name
+	 */
+	void packageReference(String pack) {
+		imports.add(pack);
+	}
+
+	/**
+	 * 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) {
+		// Some descriptors are weird, they start with a generic
+		// declaration that contains ':', not sure what they mean ...
+		if (descriptor.charAt(0) == '<')
+			return;
+
+		int rover = 0;
+		if (descriptor.charAt(rover) == '(') {
+			rover = parseReferences(descriptor, rover + 1, ')');
+			rover++;
+		}
+		parseReferences(descriptor, rover, (char) 0);
+	}
+
+	/**
+	 * 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) {
+		while (rover < descriptor.length() && descriptor.charAt(rover) != delimiter) {
+			rover = parseReference(descriptor, rover);
+		}
+		return rover;
+	}
+
+	/**
+	 * 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) {
+
+		char c = descriptor.charAt(rover);
+		while (c == '[')
+			c = descriptor.charAt(++rover);
+
+		if (c == '<') {
+			rover = parseReferences(descriptor, rover + 1, '>');
+		} else if (c == 'T') {
+			// Type variable name
+			rover++;
+			while (descriptor.charAt(rover) != ';')
+				rover++;
+		} else if (c == 'L') {
+			StringBuilder sb = new StringBuilder();
+			rover++;
+			int lastSlash = -1;
+			while ((c = descriptor.charAt(rover)) != ';') {
+				if (c == '<') {
+					rover = parseReferences(descriptor, rover + 1, '>');
+				} else if (c == '/') {
+					lastSlash = sb.length();
+					sb.append('.');
+				} else
+					sb.append(c);
+				rover++;
+			}
+			if (cd != null)
+				cd.addReference(sb.toString());
+
+			if (lastSlash > 0)
+				packageReference(sb.substring(0, lastSlash));
+		} else {
+			if ("+-*BCDFIJSZV".indexOf(c) < 0)
+				;// System.out.println("Should not skip: " + c);
+		}
+
+		// this skips a lot of characters
+		// [, *, +, -, B, etc.
+
+		return rover + 1;
+	}
+
+	public static String getPackage(String clazz) {
+		int n = clazz.lastIndexOf('/');
+		if (n < 0) {
+			n = clazz.lastIndexOf('.');
+			if (n < 0)
+				return ".";
+		}
+		return clazz.substring(0, n).replace('/', '.');
+	}
+
+	public Set<String> getReferred() {
+		return imports;
+	}
+
+	String getClassName() {
+		if (className == null)
+			return "NOCLASSNAME";
+		return className;
+	}
+
+	public String getPath() {
+		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;
+		classes = null;
+		descriptors = null;
+	}
+
+	public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
+		switch (query) {
+		case ANY:
+			return true;
+
+		case NAMED:
+			if (instr.matches(getClassName()))
+				return !instr.isNegated();
+			return false;
+
+		case VERSION:
+			String v = major + "/" + minor;
+			if (instr.matches(v))
+				return !instr.isNegated();
+			return false;
+
+		case IMPLEMENTS:
+			for (int i = 0; interfaces != null && i < interfaces.length; i++) {
+				if (instr.matches(interfaces[i]))
+					return !instr.isNegated();
+			}
+			break;
+
+		case EXTENDS:
+			if (zuper == null)
+				return false;
+
+			if (instr.matches(zuper))
+				return !instr.isNegated();
+			break;
+
+		case PUBLIC:
+			return !isPublic;
+
+		case CONCRETE:
+			return !isAbstract;
+
+		case ANNOTATION:
+			if (annotations == null)
+				return false;
+
+			if (annotations.contains(instr.getPattern()))
+				return true;
+
+			for (String annotation : annotations) {
+				if (instr.matches(annotation))
+					return !instr.isNegated();
+			}
+
+			return false;
+
+		case RUNTIMEANNOTATIONS:
+			return hasClassAnnotations;
+		case CLASSANNOTATIONS:
+			return hasClassAnnotations;
+
+		case ABSTRACT:
+			return isAbstract;
+
+		case IMPORTS:
+			for (String imp : imports) {
+				if (instr.matches(imp.replace('.', '/')))
+					return !instr.isNegated();
+			}
+		}
+
+		if (zuper == null)
+			return false;
+
+		Clazz clazz = analyzer.findClass(zuper + ".class");
+		if (clazz == null)
+			return false;
+
+		return clazz.is(query, instr, analyzer);
+	}
+
+	public String toString() {
+		return getFQN();
+	}
+
+	public String getFQN() {
+		String s = getClassName().replace('/', '.');
+		return s;
+	}
+
+	/**
+	 * Return a list of packages implemented by this class.
+	 * 
+	 * @param implemented
+	 * @param classspace
+	 * @param clazz
+	 * @throws Exception
+	 */
+	@SuppressWarnings("deprecation") final static String	USEPOLICY		= toDescriptor(UsePolicy.class);
+	final static String										PROVIDERPOLICY	= toDescriptor(ProviderType.class);
+
+	public static void getImplementedPackages(Set<String> implemented, Analyzer analyzer,
+			Clazz clazz) throws Exception {
+		if (clazz.interfaces != null) {
+			for (String interf : clazz.interfaces) {
+				interf = interf + ".class";
+				Clazz c = analyzer.getClassspace().get(interf);
+
+				// If not found, actually parse the imported
+				// class file to check for implementation policy.
+				if (c == null)
+					c = analyzer.findClass(interf);
+
+				if (c != null) {
+					boolean consumer = false;
+					Set<String> annotations = c.annotations;
+					if (annotations != null)
+						// Override if we marked the interface as a consumer
+						// interface
+						consumer = annotations.contains(USEPOLICY)
+								|| annotations.contains(PROVIDERPOLICY);
+
+					if (!consumer)
+						implemented.add(getPackage(interf));
+					getImplementedPackages(implemented, analyzer, c);
+				} else
+					implemented.add(getPackage(interf));
+
+			}
+		}
+		if (clazz.zuper != null) {
+			Clazz c = analyzer.getClassspace().get(clazz.zuper);
+			if (c != null) {
+				getImplementedPackages(implemented, analyzer, c);
+			}
+		}
+
+	}
+
+	// String RNAME = "LaQute/bnd/annotation/UsePolicy;";
+
+	public static String toDescriptor(Class<?> clazz) {
+		StringBuilder sb = new StringBuilder();
+		sb.append('L');
+		sb.append(clazz.getName().replace('.', '/'));
+		sb.append(';');
+		return sb.toString();
+	}
+
+	MethodDef getMethodDef(int access, int methodRefPoolIndex) {
+		Object o = pool[methodRefPoolIndex];
+		if (o != null && o instanceof Assoc) {
+			Assoc assoc = (Assoc) o;
+			if (assoc.tag == 10) {
+				int string_index = intPool[assoc.a];
+				String className = (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];
+					return new MethodDef(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 static String getShortName(String cname) {
+		int n = cname.lastIndexOf('.');
+		if (n < 0)
+			return cname;
+		return cname.substring(n + 1, cname.length());
+	}
+
+	public static String fqnToPath(String dotted) {
+		return dotted.replace('.', '/') + ".class";
+	}
+
+	public static String fqnToBinary(String dotted) {
+		return "L" + dotted.replace('.', '/') + ";";
+	}
+
+	public static String pathToFqn(String path) {
+		return path.replace('/', '.').substring(0, path.length() - 6);
+	}
+
+	public boolean isPublic() {
+		return isPublic;
+	}
+
+	public boolean isEnum() {
+		return isEnum;
+	}
+
+	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 internalToFqn(String string) {
+		return string.replace('/', '.');
+	}
+
+	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();
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Clazz.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision