You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ca...@apache.org on 2007/02/08 01:54:28 UTC
svn commit: r504757 - in
/maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi:
Analyzer.java Builder.java
Author: carlos
Date: Wed Feb 7 16:54:27 2007
New Revision: 504757
URL: http://svn.apache.org/viewvc?view=rev&rev=504757
Log:
Add patched sources of Analyzer and Builder until patched in BND
Added:
maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java (with props)
maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java (with props)
Added: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java
URL: http://svn.apache.org/viewvc/maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java?view=auto&rev=504757
==============================================================================
--- maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java (added)
+++ maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java Wed Feb 7 16:54:27 2007
@@ -0,0 +1,1107 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+/**
+ * This class can calculate the required headers for a (potential) JAR file. It
+ * analyzes a directory or JAR for the packages that are contained and that are
+ * referred to by the bytecodes. The user can the use regular expressions to
+ * define the attributes and directives. The matching is not fully regex for
+ * convenience. A * and ? get a . prefixed and dots are escaped.
+ *
+ * <pre>
+ * *;auto=true any
+ * org.acme.*;auto=true org.acme.xyz
+ * org.[abc]*;auto=true org.acme.xyz
+ * </pre>
+ *
+ * Additional, the package instruction can start with a '=' or a '!'. The '!'
+ * indicates negation. Any matching package is removed. The '=' is literal, the
+ * expression will be copied verbatim and no matching will take place.
+ *
+ * Any headers in the given properties are used in the output properties.
+ */
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+public class Analyzer extends Processor {
+ public final static String BUNDLE_CLASSPATH = "Bundle-ClassPath";
+ public final static String BUNDLE_COPYRIGHT = "Bundle-Copyright";
+ public final static String BUNDLE_DESCRIPTION = "Bundle-Description";
+ public final static String BUNDLE_NAME = "Bundle-Name";
+ public final static String BUNDLE_NATIVECODE = "Bundle-NativeCode";
+ public final static String EXPORT_PACKAGE = "Export-Package";
+ public final static String EXPORT_SERVICE = "Export-Service";
+ public final static String IMPORT_PACKAGE = "Import-Package";
+ public final static String DYNAMICIMPORT_PACKAGE = "DynamicImport-Package";
+ public final static String IMPORT_SERVICE = "Import-Service";
+ public final static String BUNDLE_VENDOR = "Bundle-Vendor";
+ public final static String BUNDLE_VERSION = "Bundle-Version";
+ public final static String BUNDLE_DOCURL = "Bundle-DocURL";
+ public final static String BUNDLE_CONTACTADDRESS = "Bundle-ContactAddress";
+ public final static String BUNDLE_ACTIVATOR = "Bundle-Activator";
+ public final static String BUNDLE_REQUIREDEXECUTIONENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+ public final static String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
+ public final static String BUNDLE_LOCALIZATION = "Bundle-Localization";
+ public final static String REQUIRE_BUNDLE = "Require-Bundle";
+ public final static String FRAGMENT_HOST = "Fragment-Host";
+ public final static String BUNDLE_MANIFESTVERSION = "Bundle-ManifestVersion";
+ public final static String SERVICE_COMPONENT = "Service-Component";
+ public final static String BUNDLE_LICENSE = "Bundle-License";
+ public static final String PRIVATE_PACKAGE = "Private-Package";
+ public static final String IGNORE_PACKAGE = "Ignore-Package";
+ public static final String INCLUDE_RESOURCE = "Include-Resource";
+
+ public final static String headers[] = {
+ BUNDLE_ACTIVATOR, BUNDLE_CONTACTADDRESS,
+ BUNDLE_COPYRIGHT, BUNDLE_DOCURL, BUNDLE_LOCALIZATION,
+ BUNDLE_NATIVECODE, BUNDLE_VENDOR, BUNDLE_VERSION,
+ BUNDLE_LICENSE };
+
+ static final Pattern VALID_PROPERTY_TYPES = Pattern
+ .compile("(String|Long|Double|Float|Integer|Byte|Character|Boolean|Short)");
+
+ private Properties properties /* String->String */= new Properties();
+ File base = new File(
+ "")
+ .getAbsoluteFile();
+ Map contained /* String->Map */= new HashMap(); // package
+ Map referred /* String->Map */= new HashMap(); // package
+ Map uses /* String->Map */= new HashMap(); // package
+ Map classspace;
+ boolean analyzed;
+ Map exports;
+ Map imports;
+ Map bundleClasspath; // Bundle
+ Map ignored /* String -> Map */= new HashMap(); // Ignored
+ // packages
+ Jar dot; // The
+ Map cpExports = new HashMap();
+ String activator;
+ List classpath = new ArrayList();
+ /** {@link List}$lt;{@link Jar}> with jars that may be expanded, if null all jars will be expanded */
+ List classpathForExpansion = null;
+ static Pattern doNotCopy = Pattern
+ .compile("CVS|.svn");
+ static String version;
+ Macro replacer = new Macro(
+ this);
+
+ /**
+ * Get the version from the manifest, a lot of work!
+ *
+ * @return version or unknown.
+ */
+ public String getVersion() {
+ if (version == null) {
+ version = "unknown";
+ try {
+ Enumeration e = getClass().getClassLoader()
+ .getResources("META-INF/MANIFEST.MF");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ InputStream in = url.openStream();
+
+ Manifest manifest = new Manifest(in);
+ in.close();
+ String bsn = manifest.getMainAttributes()
+ .getValue(BUNDLE_SYMBOLICNAME);
+ if (bsn != null
+ && bsn.indexOf("biz.aQute.bnd") >= 0) {
+ version = manifest.getMainAttributes()
+ .getValue(BUNDLE_VERSION);
+ return version;
+ }
+ }
+ } catch (IOException e) {
+ // Well, too bad
+ warning("bnd jar file is corrupted, can not find manifest "
+ + e);
+ }
+ }
+ return version;
+ }
+
+ public Jar setJar(Jar jar) {
+ this.dot = jar;
+ return jar;
+ }
+
+ public Jar setJar(File jar) throws IOException {
+ return setJar(buildJar(jar));
+ }
+
+ public void setClasspath(File[] classpath)
+ throws IOException {
+ List list = new ArrayList();
+ for (int i = 0; i < classpath.length; i++) {
+ Jar current = buildJar(classpath[i]);
+ if (current != null) list.add(current);
+ }
+ Jar[] cp = (Jar[]) list.toArray(new Jar[list.size()]);
+ setClasspath(cp);
+ }
+
+ public void setProperties(File propertiesFile)
+ throws FileNotFoundException, IOException {
+ this.base = propertiesFile.getAbsoluteFile()
+ .getParentFile();
+ Properties local = getProperties(propertiesFile,
+ new HashSet());
+ local.put("project.file", propertiesFile
+ .getAbsolutePath());
+ local.put("project.name", propertiesFile.getName());
+ local.put("project", stem(propertiesFile.getName()));
+
+ if (!local.containsKey(IMPORT_PACKAGE)) local.put(
+ IMPORT_PACKAGE, "*");
+ setProperties(local);
+ }
+
+ public void setProperty(String key, String value) {
+ checkheader: for (int i = 0; i < headers.length; i++) {
+ if (headers[i].equalsIgnoreCase(value)) {
+ value = headers[i];
+ break checkheader;
+ }
+ }
+ properties.put(key, value);
+ }
+
+ public void setBase(File file) {
+ base = file;
+ properties.put("project.dir", base.getAbsolutePath());
+ }
+
+ private String stem(String name) {
+ int n = name.lastIndexOf('.');
+ if (n > 0) return name.substring(0, n);
+ else return name;
+ }
+
+ Properties getProperties(File file, Set done)
+ throws IOException {
+ Properties p = new Properties();
+ InputStream in = new FileInputStream(file);
+ p.load(in);
+ in.close();
+ String includes = p.getProperty("-include");
+ if (includes != null) {
+ String parts[] = includes.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ File next = new File(file.getParentFile(), parts[i]);
+ if (next.exists() && !next.isDirectory()
+ && !done.contains(next)) {
+ done.add(next);
+ Properties level = getProperties(next, done);
+ level.putAll(p);
+ p = level;
+ }
+ }
+ }
+ return p;
+ }
+
+ public void setClasspath(Jar[] classpath) {
+ this.classpath.addAll(Arrays.asList(classpath));
+ }
+
+ /**
+ * {@link Analyzer#EXPORT_PACKAGE} will be applied only to the jars in this classpath.
+ * If null all jars will be expanded.
+ *
+ * @param classpathForExpansion {@link List}$lt;{@link Jar}>
+ */
+ public void setClasspathForExpansion(Jar[] classpathForExpansion) {
+ if (classpathForExpansion != null) {
+ this.classpathForExpansion = Arrays.asList(classpathForExpansion);
+ }
+ }
+
+ void analyzeClasspath() throws IOException {
+ cpExports = new HashMap();
+ for (Iterator c = classpath.iterator(); c.hasNext();) {
+ Jar current = (Jar) c.next();
+ checkManifest(current);
+ for (Iterator j = current.getDirectories().keySet()
+ .iterator(); j.hasNext();) {
+ String dir = (String) j.next();
+ Resource resource = current.getResource(dir
+ + "/packageinfo");
+ if (resource != null) {
+ String version = parsePackageInfo(resource
+ .openInputStream());
+ setPackageInfo(dir, "version", version);
+ }
+ }
+ }
+ }
+
+ void setPackageInfo(String dir, String key, String value) {
+ if (value != null) {
+ String pack = dir.replace('/', '.');
+ Map map = (Map) cpExports.get(pack);
+ if (map == null) {
+ map = new HashMap();
+ cpExports.put(pack, map);
+ }
+ map.put(key, value);
+ }
+ }
+
+ private void checkManifest(Jar jar) {
+ try {
+ Manifest m = jar.getManifest();
+ if (m != null) {
+ String exportHeader = m.getMainAttributes()
+ .getValue(EXPORT_PACKAGE);
+ if (exportHeader != null) {
+ Map exported = (Map) parseHeader(exportHeader);
+ if (exported != null)
+ // Hmm, moet mischien gemerged worden?
+ cpExports.putAll(exported);
+ }
+ }
+ } catch (Exception e) {
+ warning("Erroneous Manifest for " + jar + " " + e);
+ }
+ }
+
+ public String getProperty(String headerName) {
+ String value = properties.getProperty(headerName);
+ if (value != null) return replacer.process(value);
+ else return null;
+ }
+
+ String getProperty(String headerName, String deflt) {
+ String v = getProperty(headerName);
+ return v == null ? deflt : v;
+ }
+
+ public void setProperties(Properties properties) {
+ this.properties = properties;
+ replacer = new Macro(properties, this);
+
+ String doNotCopy = getProperty("-donotcopy");
+ if (doNotCopy != null) Analyzer.doNotCopy = Pattern
+ .compile(doNotCopy);
+
+ String cp = properties.getProperty("-classpath");
+ if (cp != null) {
+ String parts[] = cp.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ File file = new File(base, parts[i]);
+ if (file.exists()) try {
+ classpath.add(new Jar(file.getName(), file));
+ } catch (Exception e) {
+ error("Classpath from properties had errors: "
+ + parts[i] + " " + e);
+ }
+ else {
+ error("Trying to add non existent entry to the classpath from properties: "
+ + parts[i]);
+ }
+ }
+ }
+ }
+
+// public Signer getSigner() {
+// String sign = getProperty("-sign");
+// if (sign == null) return null;
+//
+// Map parsed = parseHeader(sign);
+// Signer signer = new Signer();
+// String password = (String) parsed.get("password");
+// if (password != null) {
+// signer.setPassword(password);
+// }
+//
+// String keystore = (String) parsed.get("keystore");
+// if (keystore != null) {
+// File f = new File(keystore);
+// if (!f.isAbsolute()) f = new File(base, keystore);
+// signer.setKeystore(f);
+// } else {
+// error("Signing requires a keystore");
+// return null;
+// }
+//
+// String alias = (String) parsed.get("alias");
+// if (alias != null) {
+// signer.setAlias(alias);
+// } else {
+// error("Signing requires an alias for the private key");
+// return null;
+// }
+// return signer;
+// }
+
+ Jar buildJar(File file) throws IOException {
+ if (!file.exists()) {
+ error("File to build jar from does not exist: "
+ + file);
+ return null;
+ }
+ Jar jar = new Jar(file.getName());
+ try {
+ if (file.isDirectory()) FileResource.build(jar, file,
+ doNotCopy);
+ else if (file.exists()) {
+ ZipResource.build(jar, file);
+ } else {
+ error("No such file: " + file.getAbsolutePath());
+ }
+ } catch (Exception e) {
+ error("Failed to construct ZIP file: "
+ + file.getAbsolutePath());
+ }
+ return jar;
+
+ }
+
+ public void analyze() throws IOException {
+ if (!analyzed) {
+ analyzed = true;
+ cpExports = new HashMap();
+ analyzeClasspath();
+
+ activator = getProperty(BUNDLE_ACTIVATOR);
+ bundleClasspath = parseHeader(getProperty(BUNDLE_CLASSPATH));
+
+ classspace = analyzeBundleClasspath(dot,
+ bundleClasspath, contained, referred, uses);
+
+ referred.keySet().removeAll(contained.keySet());
+
+ Map exportInstructions = parseHeader(getProperty(EXPORT_PACKAGE));
+ Map importInstructions = parseHeader(getProperty(IMPORT_PACKAGE) );
+ Map dynamicImports = parseHeader(getProperty(DYNAMICIMPORT_PACKAGE));
+
+ if (dynamicImports != null) {
+ // Remove any dynamic imports from the referred set.
+ referred.keySet()
+ .removeAll(dynamicImports.keySet());
+ }
+
+ Set superfluous = new TreeSet();
+ // Tricky!
+ for (Iterator i = exportInstructions.keySet()
+ .iterator(); i.hasNext();) {
+ String instr = (String) i.next();
+ if (!instr.startsWith("!")) superfluous.add(instr);
+ }
+
+ exports = merge("export-package", exportInstructions,
+ contained, superfluous);
+ if (!superfluous.isEmpty()) {
+ warnings
+ .add("Superfluous export-package instructions: "
+ + superfluous);
+ }
+
+ // Add all exports that do not have an -noimport: directive
+ // to the imports.
+ Map referredAndExported = new HashMap(referred);
+ referredAndExported
+ .putAll(addExportsToImports(exports));
+
+ // match the imports to the referred and exported packages,
+ // merge the info for matching packages
+ Set extra = new TreeSet(importInstructions.keySet());
+ imports = merge("import-package", importInstructions,
+ referredAndExported, extra);
+
+ // Instructions that have not been used could be superfluous
+ // or if they do not contain wildcards, should be added
+ // as extra imports, the user knows best.
+ for (Iterator i = extra.iterator(); i.hasNext();) {
+ String p = (String) i.next();
+ if (p.startsWith("!") || p.indexOf('*') > 0
+ || p.indexOf('?') > 0 || p.indexOf('[') > 0) {
+ warning("Did not find matching referal for " + p);
+ } else {
+ Map map = (Map) importInstructions.get(p);
+ imports.put(p, map);
+ }
+ }
+
+ // See what information we can find to augment the
+ // imports. I.e. look on the classpath
+ augmentImports();
+
+ // Add the uses clause to the exports
+ doUses(exports, uses);
+ }
+ }
+
+ /**
+ * For each import, find the exporter and see what you can learn from it.
+ */
+ static Pattern versionPattern = Pattern
+ .compile("(\\d+\\.\\d+)\\.\\d+");
+
+ void augmentImports() {
+ for (Iterator imp = imports.keySet().iterator(); imp
+ .hasNext();) {
+ String packageName = (String) imp.next();
+ Map currentAttributes = (Map) imports
+ .get(packageName);
+
+ Map exporter = (Map) cpExports.get(packageName);
+ if (exporter != null) {
+ // See if we can borrow te version
+ String version = (String) exporter.get("version");
+ if (version == null) version = (String) exporter
+ .get("specification-version");
+ if (version != null) {
+ // We remove the micro part of the version
+ // to a bit more lenient
+ Matcher m = versionPattern.matcher(version);
+ if (m.matches()) version = m.group(1);
+ currentAttributes.put("version", version);
+ }
+
+ // If we use an import with mandatory
+ // attributes we better all use them
+ String mandatory = (String) exporter
+ .get("mandatory:");
+ if (mandatory != null) {
+ String[] attrs = mandatory.split("\\w*,\\w*");
+ for (int i = 0; i < attrs.length; i++) {
+ currentAttributes.put(attrs[i], exporter
+ .get(attrs[i]));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * We will add all exports to the imports unless there is a -noimport
+ * directive specified on an export. This directive is skipped for the
+ * manifest.
+ *
+ */
+ Map addExportsToImports(Map exports) {
+ Map importsFromExports = new HashMap();
+ for (Iterator export = exports.entrySet().iterator(); export
+ .hasNext();) {
+ Map.Entry entry = (Map.Entry) export.next();
+ String packageName = (String) entry.getKey();
+ Map parameters = (Map) entry.getValue();
+ String noimport = (String) parameters
+ .get("-noimport:");
+ if (noimport == null
+ || !noimport.equalsIgnoreCase("true")) {
+ Map importParameters = (Map) importsFromExports
+ .get(packageName);
+ if (importParameters == null) importsFromExports
+ .put(packageName, parameters);
+ }
+ }
+ return importsFromExports;
+ }
+
+ public Manifest calcManifest() throws IOException {
+ analyze();
+ Manifest manifest = new Manifest();
+ Attributes main = manifest.getMainAttributes();
+
+ main.putValue(BUNDLE_MANIFESTVERSION, "2");
+ main.putValue("Created-By", "Bnd-" + getVersion());
+
+ String exportHeader = printClauses(exports,
+ "uses:|include:|exclude:|mandatory:");
+
+ if (exportHeader.length() > 0) main.putValue(
+ EXPORT_PACKAGE, exportHeader);
+ else main.remove(EXPORT_PACKAGE);
+
+ Map temp = removeKeys(imports, "java.");
+ if (!temp.isEmpty()) {
+ main.putValue(IMPORT_PACKAGE, printClauses(temp,
+ "resolution:"));
+ } else {
+ main.remove(IMPORT_PACKAGE);
+ }
+
+ temp = new TreeMap(contained);
+ temp.keySet().removeAll(exports.keySet());
+
+ if (!temp.isEmpty()) {
+ main
+ .putValue(PRIVATE_PACKAGE, printClauses(temp, ""));
+ } else {
+ main.remove(PRIVATE_PACKAGE);
+ }
+
+ if (!ignored.isEmpty()) {
+ main.putValue(IGNORE_PACKAGE, printClauses(ignored,
+ ""));
+ } else {
+ main.remove(IGNORE_PACKAGE);
+ }
+
+ if (bundleClasspath != null
+ && !bundleClasspath.isEmpty()) main
+ .putValue(BUNDLE_CLASSPATH, printClauses(
+ bundleClasspath, ""));
+ else main.remove(BUNDLE_CLASSPATH);
+
+ Map l = doServiceComponent(getProperty(SERVICE_COMPONENT));
+ if (!l.isEmpty()) main.putValue(SERVICE_COMPONENT,
+ printClauses(l, ""));
+ else main.remove(SERVICE_COMPONENT);
+
+ for (Iterator h = properties.keySet().iterator(); h
+ .hasNext();) {
+ String header = (String) h.next();
+ if (!Character.isUpperCase(header.charAt(0))) continue;
+
+ if (header.equals(BUNDLE_CLASSPATH)
+ || header.equals(EXPORT_PACKAGE)
+ || header.equals(IMPORT_PACKAGE)) continue;
+
+ String value = getProperty(header);
+ if (value != null && main.getValue(header) == null) main
+ .putValue(header, value);
+ }
+
+ main.put(Attributes.Name.MANIFEST_VERSION, "1");
+
+ // Copy old values into new manifest, when they
+ // exist in the old one, but not in the new one
+ merge(manifest);
+
+ // Check for some defaults
+ String p = getProperty("project");
+ if (p != null) {
+ if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
+ main.putValue(BUNDLE_SYMBOLICNAME, p);
+ }
+ if (main.getValue(BUNDLE_NAME) == null) {
+ main.putValue(BUNDLE_NAME, p);
+ }
+ }
+ if (main.getValue(BUNDLE_VERSION) == null) main
+ .putValue(BUNDLE_VERSION, "0");
+
+ dot.setManifest(manifest);
+ return manifest;
+ }
+
+ /**
+ * @param manifest
+ * @throws Exception
+ */
+ private void merge(Manifest manifest) throws IOException {
+ Manifest old = dot.getManifest();
+ if (old != null) {
+ for (Iterator e = old.getMainAttributes().entrySet()
+ .iterator(); e.hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ Attributes.Name name = (Attributes.Name) entry
+ .getKey();
+ String value = (String) entry.getValue();
+ if (name.toString().equalsIgnoreCase("Created-By")) name = new Attributes.Name(
+ "Originally-Created-By");
+ if (!manifest.getMainAttributes().containsKey(name)) manifest
+ .getMainAttributes().put(name, value);
+ }
+
+ // do not overwrite existing entries
+ Map oldEntries = old.getEntries();
+ Map newEntries = manifest.getEntries();
+ for (Iterator e = oldEntries.entrySet().iterator(); e
+ .hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ if (!newEntries.containsKey(entry.getKey())) {
+ newEntries.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * Add the uses clauses
+ *
+ * @param exports
+ * @param uses
+ * @throws MojoExecutionException
+ */
+ void doUses(Map exports, Map uses) {
+ for (Iterator i = exports.keySet().iterator(); i
+ .hasNext();) {
+ String packageName = (String) i.next();
+ Map clause = (Map) exports.get(packageName);
+
+ Set t = (Set) uses.get(packageName);
+ if (t != null && !t.isEmpty()) {
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+ for (Iterator u = t.iterator(); u.hasNext();) {
+ String usedPackage = (String) u.next();
+ if (!usedPackage.equals(packageName)
+ && !usedPackage.startsWith("java.")) {
+ sb.append(del);
+ sb.append(usedPackage);
+ del = ",";
+ }
+ }
+ String s = sb.toString();
+ if (s.length() > 0) clause.put("uses:", sb
+ .toString());
+ }
+ }
+ }
+
+ /**
+ * Print a standard Map based OSGi header.
+ *
+ * @param exports
+ * map { name => Map { attribute|directive => value } }
+ * @return the clauses
+ */
+ String printClauses(Map exports, String allowedDirectives) {
+ StringBuffer sb = new StringBuffer();
+ String del = "";
+ for (Iterator i = exports.keySet().iterator(); i
+ .hasNext();) {
+ String name = (String) i.next();
+ Map map = (Map) exports.get(name);
+ sb.append(del);
+ sb.append(name);
+
+ for (Iterator j = map.keySet().iterator(); j
+ .hasNext();) {
+ String key = (String) j.next();
+
+ // Skip directives we do not recognize
+ if (key.endsWith(":")
+ && allowedDirectives.indexOf(key) < 0) continue;
+
+ String value = (String) map.get(key);
+ sb.append(";");
+ sb.append(key);
+ sb.append("=");
+ boolean dirty = value.indexOf(',') >= 0
+ || value.indexOf(';') >= 0;
+ if (dirty) sb.append("\"");
+ sb.append(value);
+ if (dirty) sb.append("\"");
+ }
+ del = ",";
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Merge the attributes of two maps, where the first map can contain
+ * wildcarded names. The idea is that the first map contains patterns (for
+ * example *) with a set of attributes. These patterns are matched against the
+ * found packages in actual. If they match, the result is set with the merged
+ * set of attributes. It is expected that the instructions are ordered so that
+ * the instructor can define which pattern matches first. Attributes in the
+ * instructions override any attributes from the actual.<br/>
+ *
+ * A pattern is a modified regexp so it looks like globbing. The * becomes a .*
+ * just like the ? becomes a .?. '.' are replaced with \\. Additionally, if
+ * the pattern starts with an exclamation mark, it will remove that matches
+ * for that pattern (- the !) from the working set. So the following patterns
+ * should work:
+ * <ul>
+ * <li>com.foo.bar</li>
+ * <li>com.foo.*</li>
+ * <li>com.foo.???</li>
+ * <li>com.*.[^b][^a][^r]</li>
+ * <li>!com.foo.* (throws away any match for com.foo.*)</li>
+ * </ul>
+ * Enough rope to hang the average developer I would say.
+ *
+ *
+ * @param instructions
+ * the instructions with patterns. A
+ * @param actual
+ * the actual found packages
+ */
+
+ Map merge(String type, Map instructions, Map actual,
+ Set superfluous) {
+ actual = new HashMap(actual); // we do not want to ruin our original
+ Map result = new HashMap();
+ for (Iterator i = instructions.keySet().iterator(); i
+ .hasNext();) {
+ String instruction = (String) i.next();
+ String originalInstruction = instruction;
+
+ Map instructedAttributes = (Map) instructions
+ .get(instruction);
+
+ if (instruction.startsWith("=")) {
+ result.put(instruction.substring(1),
+ instructedAttributes);
+ superfluous.remove(originalInstruction);
+ continue;
+ }
+
+ Instruction instr = getPattern(instruction);
+
+ for (Iterator p = actual.keySet().iterator(); p
+ .hasNext();) {
+ String packageName = (String) p.next();
+
+ if (instr.matches(packageName)) {
+ superfluous.remove(originalInstruction);
+ if (!instr.isNegated()) {
+ Map newAttributes = new HashMap();
+ newAttributes.putAll((Map) actual
+ .get(packageName));
+ newAttributes.putAll(instructedAttributes);
+ result.put(packageName, newAttributes);
+ } else {
+ ignored.put(packageName, new HashMap());
+ }
+ p.remove(); // Can never match again for another pattern
+ }
+ }
+
+ }
+ return result;
+ }
+
+ public static Instruction getPattern(String string) {
+ StringBuffer sb = new StringBuffer();
+ for (int c = 0; c < string.length(); c++) {
+ switch (string.charAt(c)) {
+ case '.':
+ sb.append("\\.");
+ break;
+ case '*':
+ sb.append(".*");
+ break;
+ case '?':
+ sb.append(".?");
+ break;
+ default:
+ sb.append(string.charAt(c));
+ break;
+ }
+ }
+ string = sb.toString();
+ if (string.endsWith("\\..*")) {
+ sb.append("|");
+ sb.append(string.substring(0, string.length() - 4));
+ }
+ return new Instruction(sb.toString());
+ }
+
+ public Map getBundleClasspath() {
+ return bundleClasspath;
+ }
+
+ public Map getContained() {
+ return contained;
+ }
+
+ public Map getExports() {
+ return exports;
+ }
+
+ public Map getImports() {
+ return imports;
+ }
+
+ public Map getReferred() {
+ return referred;
+ }
+
+ public Map getUses() {
+ return uses;
+ }
+
+ /**
+ * Return the set of unreachable code depending on exports and the bundle
+ * activator.
+ *
+ * @return
+ */
+ public Set getUnreachable() {
+ Set unreachable = new HashSet(uses.keySet()); // all
+ for (Iterator r = exports.keySet().iterator(); r
+ .hasNext();) {
+ String packageName = (String) r.next();
+ removeTransitive(packageName, unreachable);
+ }
+ if (activator != null) {
+ String pack = activator.substring(0, activator
+ .lastIndexOf('.'));
+ removeTransitive(pack, unreachable);
+ }
+ return unreachable;
+ }
+
+ void removeTransitive(String name, Set unreachable) {
+ unreachable.remove(name);
+ if (!unreachable.contains(name)) return;
+
+ Set ref = (Set) uses.get(name);
+ if (ref != null) {
+ for (Iterator r = ref.iterator(); r.hasNext();) {
+ String element = (String) r.next();
+ removeTransitive(element, unreachable);
+ }
+ }
+ }
+
+ public Jar getJar() {
+ return dot;
+ }
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ /**
+ * Check if a service component header is actually referring to a class. If
+ * so, replace the reference with an XML file reference. This makes it easier
+ * to create and use components.
+ *
+ * @throws UnsupportedEncodingException
+ *
+ */
+ public Map doServiceComponent(String serviceComponent)
+ throws UnsupportedEncodingException {
+ Map list = new LinkedHashMap();
+ Map sc = parseHeader(serviceComponent);
+ if (!sc.isEmpty()) {
+ for (Iterator i = sc.entrySet().iterator(); i
+ .hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ String name = (String) entry.getKey();
+ Map info = (Map) entry.getValue();
+ if (name == null) {
+ error("No name in Service-Component header: "
+ + info);
+ continue;
+ }
+ if (dot.exists(name)) {
+ // Normal service component
+ list.put(name, info);
+ } else {
+ if (!checkClass(name)) error("Not found Service-Component header: "
+ + name);
+ else {
+ // We have a definition, so make an XML resources
+ Resource resource = createComponentResource(
+ name, info);
+ dot.putResource("OSGI-INF/" + name + ".xml",
+ resource);
+ list.put("OSGI-INF/" + name + ".xml",
+ new HashMap());
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * @param list
+ * @param name
+ * @param info
+ * @throws UnsupportedEncodingException
+ */
+ /*
+ * <?xml version="1.0" ?> <descriptor
+ * xmlns="http://www.osgi.org/xmlns/app/v1.0.0"> <application
+ * class="com.acme.app.SampleMidlet"> <reference name="log"
+ * interface="org.osgi.service.log.LogService"/> </application> </descriptor>
+ */
+ private Resource createComponentResource(String name,
+ Map info) throws UnsupportedEncodingException {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(
+ new OutputStreamWriter(out, "UTF-8"));
+ pw.println("<?xml version='1.0' encoding='utf-8'?>");
+ pw.println("<component name='" + name + "'>");
+ pw.println(" <implementation class='" + name + "'/>");
+ String provides = (String) info.get("provide:");
+ provides(pw, provides);
+ properties(pw, info);
+ reference(info, pw);
+ pw.println("</component>");
+ pw.close();
+ return new EmbeddedResource(out.toByteArray());
+ }
+
+
+ private void properties(PrintWriter pw, Map info) {
+ Set properties = getMatchSet((String) info
+ .get("properties:"));
+ for (Iterator p = properties.iterator(); p.hasNext();) {
+ String clause = (String) p.next();
+ int n = clause.indexOf('=');
+ if (n <= 0) {
+ error("Not a valid property in service component: "
+ + clause);
+ } else {
+ String type = null;
+ String name = clause.substring(0, n);
+ if (name.contains("@")) {
+ String parts[] = name.split("@");
+ name = parts[1];
+ type = parts[0];
+ }
+ String value = clause.substring(n + 1);
+ // TODO verify validity of name and value
+ pw.print("<property name='");
+ pw.print(name);
+ pw.print("'");
+
+ if (type != null) {
+ if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
+ pw.print(" type='");
+ pw.print(type);
+ pw.print("'");
+ } else {
+ warnings.add("Invalid property type '" + type + "' for property " + name );
+ }
+ }
+
+ if (value.contains("\\n")) {
+ pw.print("'>");
+ pw.print(value.replaceAll("\\n", "\n"));
+ pw.println("</property>");
+ } else {
+ pw.print(" value='");
+ pw.print(value);
+ pw.print("'/>");
+ }
+ }
+ }
+ }
+
+ /**
+ * @param info
+ * @param pw
+ */
+ private void reference(Map info, PrintWriter pw) {
+ Set dynamic = getMatchSet((String) info.get("dynamic:"));
+ Set optional = getMatchSet((String) info
+ .get("optional:"));
+ Set multiple = getMatchSet((String) info
+ .get("multiple:"));
+
+ for (Iterator r = info.entrySet().iterator(); r
+ .hasNext();) {
+ Map.Entry ref = (Map.Entry) r.next();
+ String referenceName = (String) ref.getKey();
+ String interfaceName = (String) ref.getValue();
+ // TODO check if the interface is contained or imported
+
+ if (referenceName.endsWith(":")) continue;
+
+ if (!checkClass(interfaceName)) error("Component definition refers to a class that is neither imported nor contained: "
+ + interfaceName);
+
+ pw.print(" <reference name='" + referenceName
+ + "' interface='" + interfaceName + "'");
+
+ String cardinality = optional.contains(referenceName) ? "0"
+ : "1";
+ cardinality += "..";
+ cardinality += multiple.contains(referenceName) ? "n"
+ : "1";
+ if (!cardinality.equals("1..1")) pw
+ .print(" cardinality='" + cardinality + "'");
+
+ if (Character.isLowerCase(referenceName.charAt(0))) {
+ String z = referenceName.substring(0, 1)
+ .toUpperCase()
+ + referenceName.substring(1);
+ pw.print(" bind='set" + z + "'");
+ // TODO Verify that the methods exist
+
+ // TODO ProSyst requires both a bind and unbind :-(
+ // if ( dynamic.contains(referenceName) )
+ pw.print(" unbind='unset" + z + "'");
+ // TODO Verify that the methods exist
+ }
+ if (dynamic.contains(referenceName)) {
+ pw.print(" policy='dynamic'");
+ }
+ pw.println("/>");
+ }
+ }
+
+ /**
+ * @param pw
+ * @param provides
+ */
+ private void provides(PrintWriter pw, String provides) {
+ if (provides != null) {
+ pw.println(" <service>");
+ StringTokenizer st = new StringTokenizer(provides,
+ ",");
+ while (st.hasMoreTokens()) {
+ String interfaceName = st.nextToken();
+ pw.println(" <provide interface='"
+ + interfaceName + "'/>");
+ if (!checkClass(interfaceName)) error("Component definition provides a class that is neither imported nor contained: "
+ + interfaceName);
+ }
+ pw.println(" </service>");
+ }
+ }
+
+ private boolean checkClass(String interfaceName) {
+ String path = interfaceName.replace('.', '/')
+ + ".class";
+ if (classspace.containsKey(path)) return true;
+
+ String pack = interfaceName;
+ int n = pack.lastIndexOf('.');
+ if (n > 0) pack = pack.substring(0, n);
+ else pack = ".";
+
+ return imports.containsKey(pack);
+ }
+
+ private Set getMatchSet(String list) {
+ if (list == null) return new HashSet();
+
+ String parts[] = list.split("\\s*,\\s*");
+ return new HashSet(Arrays.asList(parts));
+ }
+
+ /**
+ * Specifically for Maven
+ *
+ * @param properties
+ * the properties
+ */
+
+ public static Properties getManifest(File dirOrJar)
+ throws IOException {
+ Analyzer analyzer = new Analyzer();
+ analyzer.setJar(dirOrJar);
+ Properties properties = new Properties();
+ properties.put(IMPORT_PACKAGE, "*");
+ properties.put(EXPORT_PACKAGE, "*");
+ analyzer.setProperties(properties);
+ Manifest m = analyzer.calcManifest();
+ Properties result = new Properties();
+ for (Iterator i = m.getMainAttributes().keySet()
+ .iterator(); i.hasNext();) {
+ Attributes.Name name = (Attributes.Name) i.next();
+ result.put(name.toString(), m.getMainAttributes()
+ .getValue(name));
+ }
+ return result;
+ }
+
+}
Propchange: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Analyzer.java
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java
URL: http://svn.apache.org/viewvc/maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java?view=auto&rev=504757
==============================================================================
--- maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java (added)
+++ maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java Wed Feb 7 16:54:27 2007
@@ -0,0 +1,351 @@
+/* Copyright 2006 aQute SARL
+ * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.zip.ZipException;
+
+/**
+ * Include-Resource: ( [name '=' ] file )+
+ *
+ * Private-Package: package-decl ( ',' package-decl )*
+ *
+ * Export-Package: package-decl ( ',' package-decl )*
+ *
+ * Import-Package: package-decl ( ',' package-decl )*
+ *
+ * @version $Revision: 1.2 $
+ */
+public class Builder extends Analyzer {
+ boolean sources = false;
+ private File[] sourcePath;
+
+ public void setProperties(Properties properties) {
+ super.setProperties(properties);
+ }
+
+ public Jar build() throws Exception {
+ sources = getProperty("-sources") != null;
+
+ dot = new Jar("dot");
+ doExpand(dot);
+ doIncludes(dot);
+ dot.setManifest(calcManifest());
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+ doVerify(dot);
+ if (dot.getResources().isEmpty())
+ error("The JAR is empty");
+
+// Signer signer = getSigner();
+// if ( signer != null ) {
+// signer.signJar(dot);
+// }
+ return dot;
+ }
+
+ /**
+ *
+ */
+ private void addSources(Jar dot) {
+ if (!sources)
+ return;
+
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ getProperties().store(out, "Generated by BND, at " + new Date());
+ dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out
+ .toByteArray()));
+ }
+ catch (Exception e) {
+ error("Can not embed bnd file in JAR: " + e);
+ }
+
+ for (Iterator cpe = classspace.keySet().iterator(); cpe.hasNext();) {
+ String path = (String) cpe.next();
+ path = path.substring(0, path.length() - ".class".length())
+ + ".java";
+
+ for (int i = 0; i < sourcePath.length; i++) {
+ File root = sourcePath[i];
+ File f = new File(root, path);
+ if (f.exists()) {
+ dot
+ .putResource(
+ "OSGI-OPT/src/" + path,
+ new FileResource(f));
+ }
+ }
+ }
+ }
+
+ private void doVerify(Jar dot) throws Exception {
+ Verifier verifier = new Verifier(dot);
+ verifier.verify();
+ errors.addAll(verifier.getErrors());
+ warnings.addAll(verifier.getWarnings());
+ }
+
+ private void doExpand(Jar jar) throws IOException {
+ Map prive = replaceWithPattern(getHeader(Analyzer.PRIVATE_PACKAGE));
+ Map export = replaceWithPattern(getHeader(Analyzer.EXPORT_PACKAGE));
+ if (prive.isEmpty() && export.isEmpty()) {
+ warnings
+ .add("Neither Export-Package nor Private-Package is set, therefore no packages will be included");
+ }
+ doExpand(jar, "Export-Package ", export);
+ doExpand(jar, "Private-Package ", prive);
+ }
+
+ private void doExpand(Jar jar, String name, Map instructions) {
+ Set superfluous = new HashSet(instructions.keySet());
+ if (classpathForExpansion == null) {
+ classpathForExpansion = classpath ;
+ }
+ for (Iterator c = classpathForExpansion.iterator(); c.hasNext();) {
+ Jar now = (Jar) c.next();
+ doExpand(jar, instructions, now, superfluous);
+ }
+ if (superfluous.size() > 0) {
+ StringBuffer sb = new StringBuffer();
+ String del = "Instructions for " + name + " that are never used: ";
+ for (Iterator i = superfluous.iterator(); i.hasNext();) {
+ Instruction p = (Instruction) i.next();
+ sb.append(del);
+ sb.append(p.getPattern());
+ del = ", ";
+ }
+ warning(sb.toString());
+ }
+ }
+
+ /**
+ * Iterate over each directory in the class path entry and check if that
+ * directory is a desired package.
+ *
+ * @param included
+ * @param classpathEntry
+ */
+ private void doExpand(Jar jar, Map included, Jar classpathEntry,
+ Set superfluous) {
+ for (Iterator p = classpathEntry.getDirectories().entrySet().iterator(); p
+ .hasNext();) {
+ Map.Entry directory = (Map.Entry) p.next();
+ String path = (String) directory.getKey();
+
+ if (doNotCopy.matcher(getName(path)).matches())
+ continue;
+
+ String pack = path.replace('/', '.');
+ Instruction instr = matches(included, pack, superfluous);
+ if ( instr != null && ! instr.isNegated() ) {
+ Map contents = (Map) directory.getValue();
+ jar.addDirectory(contents);
+ }
+ }
+ }
+
+ private Map replaceWithPattern(Map header) {
+ Map map = new LinkedHashMap();
+ for (Iterator e = header.entrySet().iterator(); e.hasNext();) {
+ Map.Entry entry = (Map.Entry) e.next();
+ String pattern = (String) entry.getKey();
+ Instruction instr = Analyzer.getPattern(pattern);
+ map.put(instr, entry.getValue());
+ }
+ return map;
+ }
+
+ private Instruction matches(Map instructions, String pack, Set superfluousPatterns) {
+ for (Iterator i = instructions.keySet().iterator(); i.hasNext();) {
+ Instruction pattern = (Instruction) i.next();
+ if (pattern.matches(pack)) {
+ superfluousPatterns.remove(pattern);
+ return pattern;
+ }
+ }
+ return null;
+ }
+
+ private Map getHeader(String string) {
+ if (string == null)
+ return new LinkedHashMap();
+ 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 doIncludes(Jar jar) throws Exception {
+ Macro macro = new Macro(getProperties(), this);
+
+ String includes = getProperty("Bundle-Includes");
+ if (includes == null)
+ includes = getProperty("Include-Resource");
+ else
+ warnings
+ .add("Please use Include-Resource instead of Bundle-Includes");
+
+ if (includes == null)
+ return;
+
+ String clauses[] = includes.split("\\s*,\\s*");
+ for (int i = 0; i < clauses.length; i++) {
+ boolean preprocess = false;
+ if (clauses[i].startsWith("{") && clauses[i].endsWith("}")) {
+ preprocess = true;
+ clauses[i] = clauses[i].substring(1, clauses[i].length() - 1)
+ .trim();
+ }
+
+ if (clauses[i].startsWith("@")) {
+ extractFromJar(jar, clauses[i]);
+ }
+ else {
+ String parts[] = clauses[i].split("\\s*=\\s*");
+
+ File source;
+ String destinationPath;
+
+ if (parts.length == 1) {
+ // Just a copy, destination path defined by
+ // source path.
+ source = new File(base, parts[0]);
+
+ // Directories should be copied to the root
+ // but files to their file name ...
+ if (source.isDirectory())
+ destinationPath = "";
+ else
+ destinationPath = source.getName();
+ }
+ else {
+ source = new File(base, parts[1]);
+ destinationPath = parts[0];
+ }
+
+ // Some people insist on ending a directory with
+ // a slash ... it now also works if you do /=dir
+ if (destinationPath.endsWith("/"))
+ destinationPath = destinationPath.substring(
+ 0,
+ destinationPath.length() - 1);
+
+ copy(jar, destinationPath, source, preprocess ? macro : null);
+ }
+ }
+ }
+
+ /**
+ * @param jar
+ * @param clauses
+ * @param i
+ * @throws ZipException
+ * @throws IOException
+ */
+ private void extractFromJar(Jar jar, String clause )
+ 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
+ String name = clause.substring(1);
+ int n = name.lastIndexOf("!/");
+ Pattern filter = null;
+ if (n > 0) {
+ String fstring = name.substring(n+2);
+ name = name.substring(0, n);
+ filter = wildcard(fstring);
+ }
+ File file = new File(base.getAbsoluteFile(), name);
+ ZipResource.build(jar, file, filter );
+ }
+
+ private Pattern wildcard(String spec) {
+ StringBuffer sb = new StringBuffer();
+ for (int j = 0; j < spec.length(); j++) {
+ char c = spec.charAt(j);
+ switch (c) {
+ case '.' :
+ sb.append("\\.");
+ break;
+
+ case '*' :
+ // test for ** (all directories)
+ if (j < spec.length() - 1 && spec.charAt(j + 1) == '*') {
+ sb.append(".*");
+ j++;
+ }
+ else
+ sb.append("[^/]*");
+ break;
+ default :
+ sb.append(c);
+ break;
+ }
+ }
+ String s = sb.toString();
+ try {
+ return Pattern.compile(s);
+ }
+ catch (Exception e) {
+ error("Invalid regular expression on wildcarding: "
+ + spec + " used *");
+ }
+ return null;
+ }
+
+ private void copy(Jar jar, String path, File from, Macro macro)
+ throws Exception {
+ if (doNotCopy.matcher(from.getName()).matches())
+ return;
+
+ if (from.isDirectory()) {
+ String next = path;
+ if (next.length() != 0)
+ next += '/';
+
+ File files[] = from.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ copy(jar, next + files[i].getName(), files[i], macro);
+ }
+ }
+ else {
+ if (macro != null) {
+ String content = read(from);
+ content = macro.process(content);
+ jar.putResource(path, new EmbeddedResource(content
+ .getBytes("UTF-8")));
+ }
+ else
+ jar.putResource(path, new FileResource(from));
+ }
+ }
+
+ private String read(File from) throws Exception {
+ long size = from.length();
+ byte[] buffer = new byte[(int) size];
+ FileInputStream in = new FileInputStream(from);
+ in.read(buffer);
+ in.close();
+ return new String(buffer, "UTF-8");
+ }
+
+ private String getName(String where) {
+ int n = where.lastIndexOf('/');
+ if (n < 0)
+ return where;
+
+ return where.substring(n + 1);
+ }
+
+ public void setSourcepath(File[] files) {
+ sourcePath = files;
+ }
+}
Propchange: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/plugins/maven-bundle-plugin/src/main/java/aQute/lib/osgi/Builder.java
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"