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 ( '<' references '>' )? ';' | '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