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 [13/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/Verifier.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,959 @@
+package aQute.lib.osgi;
+
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.libg.qtokens.*;
+
+public class Verifier extends Analyzer {
+
+ Jar dot;
+ Manifest manifest;
+ Map<String, Map<String, String>> referred = newHashMap();
+ Map<String, Map<String, String>> contained = newHashMap();
+ Map<String, Set<String>> uses = newHashMap();
+ Map<String, Map<String, String>> mimports;
+ Map<String, Map<String, String>> mdynimports;
+ Map<String, Map<String, String>> mexports;
+ List<Jar> bundleClasspath;
+ Map<String, Map<String, String>> ignore = newHashMap(); // Packages
+ // to
+ // ignore
+
+ Map<String, Clazz> classSpace;
+ boolean r3;
+ boolean usesRequire;
+ boolean fragment;
+ Attributes main;
+
+ final static Pattern EENAME = Pattern
+ .compile("CDC-1\\.0/Foundation-1\\.0"
+ + "|CDC-1\\.1/Foundation-1\\.1"
+ + "|OSGi/Minimum-1\\.[1-9]"
+ + "|JRE-1\\.1"
+ + "|J2SE-1\\.2"
+ + "|J2SE-1\\.3"
+ + "|J2SE-1\\.4"
+ + "|J2SE-1\\.5"
+ + "|JavaSE-1\\.6"
+ + "|JavaSE-1\\.7"
+ + "|PersonalJava-1\\.1"
+ + "|PersonalJava-1\\.2"
+ + "|CDC-1\\.0/PersonalBasis-1\\.0"
+ + "|CDC-1\\.0/PersonalJava-1\\.0");
+
+ final static int V1_1 = 45;
+ final static int V1_2 = 46;
+ final static int V1_3 = 47;
+ final static int V1_4 = 48;
+ final static int V1_5 = 49;
+ final static int V1_6 = 50;
+ final static int V1_7 = 51;
+
+ static class EE {
+ String name;
+ int target;
+
+ EE(String name, int source, int target) {
+ this.name = name;
+ this.target = target;
+ }
+ }
+
+ final static EE[] ees = {
+ new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
+ new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
+ new EE("OSGi/Minimum-1.0", V1_3, V1_1),
+ new EE("OSGi/Minimum-1.1", V1_3, V1_2),
+ new EE("JRE-1.1", V1_1, V1_1), //
+ new EE("J2SE-1.2", V1_2, V1_1), //
+ new EE("J2SE-1.3", V1_3, V1_1), //
+ new EE("J2SE-1.4", V1_3, V1_2), //
+ new EE("J2SE-1.5", V1_5, V1_5), //
+ new EE("JavaSE-1.6", V1_6, V1_6),
+ new EE("PersonalJava-1.1", V1_1, V1_1),
+ new EE("PersonalJava-1.2", V1_1, V1_1),
+ new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
+ new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1),
+ new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
+ new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2) };
+
+ final static Pattern BUNDLEMANIFESTVERSION = Pattern
+ .compile("2");
+ public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
+ public final static Pattern SYMBOLICNAME = Pattern
+ .compile(SYMBOLICNAME_STRING);
+
+ public final static String VERSION_STRING = "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
+ public final static Pattern VERSION = Pattern
+ .compile(VERSION_STRING);
+ final static Pattern FILTEROP = Pattern
+ .compile("=|<=|>=|~=");
+ public final static Pattern VERSIONRANGE = Pattern
+ .compile("((\\(|\\[)"
+ + VERSION_STRING
+ + ","
+ + VERSION_STRING
+ + "(\\]|\\)))|"
+ + VERSION_STRING);
+ final static Pattern FILE = Pattern
+ .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
+ final static Pattern WILDCARDPACKAGE = Pattern
+ .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
+ public final static Pattern ISO639 = Pattern
+ .compile("[A-Z][A-Z]");
+ public final static Pattern HEADER_PATTERN = Pattern
+ .compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
+ public final static Pattern TOKEN = Pattern
+ .compile("[-a-zA-Z0-9_]+");
+
+ public final static Pattern NUMBERPATTERN = Pattern
+ .compile("\\d+");
+ public final static Pattern PATHPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern FQNPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern URLPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern ANYPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern FILTERPATTERN = Pattern
+ .compile(".*");
+ public final static Pattern TRUEORFALSEPATTERN = Pattern
+ .compile("true|false|TRUE|FALSE");
+ public static final Pattern WILDCARDNAMEPATTERN = Pattern
+ .compile(".*");
+ public static final Pattern BUNDLE_ACTIVATIONPOLICYPATTERN = Pattern
+ .compile("lazy");
+
+ public final static String EES[] = {
+ "CDC-1.0/Foundation-1.0", "CDC-1.1/Foundation-1.1",
+ "OSGi/Minimum-1.0", "OSGi/Minimum-1.1", "OSGi/Minimum-1.2",
+ "JRE-1.1", "J2SE-1.2", "J2SE-1.3", "J2SE-1.4", "J2SE-1.5",
+ "JavaSE-1.6", "JavaSE-1.7", "PersonalJava-1.1", "PersonalJava-1.2",
+ "CDC-1.0/PersonalBasis-1.0", "CDC-1.0/PersonalJava-1.0" };
+
+ public final static String OSNAMES[] = {
+ "AIX", // IBM
+ "DigitalUnix", // Compaq
+ "Embos", // Segger Embedded Software Solutions
+ "Epoc32", // SymbianOS Symbian OS
+ "FreeBSD", // Free BSD
+ "HPUX", // hp-ux Hewlett Packard
+ "IRIX", // Silicon Graphics
+ "Linux", // Open source
+ "MacOS", // Apple
+ "NetBSD", // Open source
+ "Netware", // Novell
+ "OpenBSD", // Open source
+ "OS2", // OS/2 IBM
+ "QNX", // procnto QNX
+ "Solaris", // Sun (almost an alias of SunOS)
+ "SunOS", // Sun Microsystems
+ "VxWorks", // WindRiver Systems
+ "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE",
+ "Windows2000", // Win2000
+ "Windows2003", // Win2003
+ "WindowsXP", "WindowsVista", };
+
+ public final static String PROCESSORNAMES[] = { "68k", // Motorola
+ // 68000
+ "ARM_LE", // Intel Strong ARM. Deprecated because it does not
+ // specify the endianness. See the following two rows.
+ "arm_le", // Intel Strong ARM Little Endian mode
+ "arm_be", // Intel String ARM Big Endian mode
+ "Alpha", //
+ "ia64n",// Hewlett Packard 32 bit
+ "ia64w",// Hewlett Packard 64 bit mode
+ "Ignite", // psc1k PTSC
+ "Mips", // SGI
+ "PArisc", // Hewlett Packard
+ "PowerPC", // power ppc Motorola/IBM Power PC
+ "Sh4", // Hitachi
+ "Sparc", // SUN
+ "Sparcv9", // SUN
+ "S390", // IBM Mainframe 31 bit
+ "S390x", // IBM Mainframe 64-bit
+ "V850E", // NEC V850E
+ "x86", // pentium i386
+ "i486", // i586 i686 Intel& AMD 32 bit
+ "x86-64", };
+
+ Properties properties;
+
+ public Verifier(Jar jar) throws Exception {
+ this(jar, null);
+ }
+
+ public Verifier(Jar jar, Properties properties) throws Exception {
+ this.dot = jar;
+ this.properties = properties;
+ this.manifest = jar.getManifest();
+ if (manifest == null) {
+ manifest = new Manifest();
+ error("This file contains no manifest and is therefore not a bundle");
+ }
+ main = this.manifest.getMainAttributes();
+ verifyHeaders(main);
+ r3 = getHeader(Analyzer.BUNDLE_MANIFESTVERSION) == null;
+ usesRequire = getHeader(Analyzer.REQUIRE_BUNDLE) != null;
+ fragment = getHeader(Analyzer.FRAGMENT_HOST) != null;
+
+ bundleClasspath = getBundleClassPath();
+ mimports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.IMPORT_PACKAGE));
+ mdynimports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.DYNAMICIMPORT_PACKAGE));
+ mexports = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.EXPORT_PACKAGE));
+
+ ignore = parseHeader(manifest.getMainAttributes().getValue(
+ Analyzer.IGNORE_PACKAGE));
+ }
+
+ public Verifier() {
+ // TODO Auto-generated constructor stub
+ }
+
+ private void verifyHeaders(Attributes main) {
+ for (Object element : main.keySet()) {
+ Attributes.Name header = (Attributes.Name) element;
+ String h = header.toString();
+ if (!HEADER_PATTERN.matcher(h).matches())
+ error("Invalid Manifest header: " + h + ", pattern="
+ + HEADER_PATTERN);
+ }
+ }
+
+ private List<Jar> getBundleClassPath() {
+ List<Jar> list = newList();
+ String bcp = getHeader(Analyzer.BUNDLE_CLASSPATH);
+ if (bcp == null) {
+ list.add(dot);
+ } else {
+ Map<String, Map<String, String>> entries = parseHeader(bcp);
+ for (Map.Entry<String, Map<String, String>> ex : entries.entrySet()) {
+ String jarOrDir = ex.getKey();
+ if (jarOrDir.equals(".")) {
+ list.add(dot);
+ } else {
+ if (jarOrDir.equals("/"))
+ jarOrDir = "";
+ if (jarOrDir.endsWith("/")) {
+ error("Bundle-Classpath directory must not end with a slash: "
+ + jarOrDir);
+ jarOrDir = jarOrDir.substring(0, jarOrDir.length() - 1);
+ }
+
+ Resource resource = dot.getResource(jarOrDir);
+ if (resource != null) {
+ try {
+ Jar sub = new Jar(jarOrDir);
+ addClose(sub);
+ EmbeddedResource.build(sub, resource);
+ if (!jarOrDir.endsWith(".jar"))
+ warning("Valid JAR file on Bundle-Classpath does not have .jar extension: "
+ + jarOrDir);
+ list.add(sub);
+ } catch (Exception e) {
+ error("Invalid embedded JAR file on Bundle-Classpath: "
+ + jarOrDir + ", " + e);
+ }
+ } else if (dot.getDirectories().containsKey(jarOrDir)) {
+ if (r3)
+ error("R3 bundles do not support directories on the Bundle-ClassPath: "
+ + jarOrDir);
+
+ try {
+ Jar sub = new Jar(jarOrDir);
+ addClose(sub);
+ for (Map.Entry<String, Resource> entry : dot
+ .getResources().entrySet()) {
+ if (entry.getKey().startsWith(jarOrDir))
+ sub.putResource(entry.getKey().substring(
+ jarOrDir.length() + 1), entry
+ .getValue());
+ }
+ list.add(sub);
+ } catch (Exception e) {
+ error("Invalid embedded directory file on Bundle-Classpath: "
+ + jarOrDir + ", " + e);
+ }
+ } else {
+ // Map<String, String> info = ex.getValue();
+ // if (! "optional".equals(
+ // info.get(RESOLUTION_DIRECTIVE)))
+ // warning("Cannot find a file or directory for
+ // Bundle-Classpath entry: %s",
+ // jarOrDir);
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ /*
+ * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( â,â optional) ?
+ * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+ * optional ::= â*â
+ */
+ public void verifyNative() {
+ String nc = getHeader("Bundle-NativeCode");
+ doNative(nc);
+ }
+
+ public void doNative(String nc) {
+ if (nc != null) {
+ QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
+ char del;
+ do {
+ do {
+ String name = qt.nextToken();
+ if (name == null) {
+ error("Can not parse name from bundle native code header: "
+ + nc);
+ return;
+ }
+ del = qt.getSeparator();
+ if (del == ';') {
+ if (dot != null && !dot.exists(name)) {
+ error("Native library not found in JAR: " + name);
+ }
+ } else {
+ String value = null;
+ if (del == '=')
+ value = qt.nextToken();
+
+ String key = name.toLowerCase();
+ if (key.equals("osname")) {
+ // ...
+ } else if (key.equals("osversion")) {
+ // verify version range
+ verify(value, VERSIONRANGE);
+ } else if (key.equals("language")) {
+ verify(value, ISO639);
+ } else if (key.equals("processor")) {
+ // verify(value, PROCESSORS);
+ } else if (key.equals("selection-filter")) {
+ // verify syntax filter
+ verifyFilter(value);
+ } else if (name.equals("*") && value == null) {
+ // Wildcard must be at end.
+ if (qt.nextToken() != null)
+ error("Bundle-Native code header may only END in wildcard: nc");
+ } else {
+ warning("Unknown attribute in native code: " + name
+ + "=" + value);
+ }
+ del = qt.getSeparator();
+ }
+ } while (del == ';');
+ } while (del == ',');
+ }
+ }
+
+ public boolean verifyFilter(String value) {
+ try {
+ verifyFilter(value, 0);
+ return true;
+ } catch (Exception e) {
+ error("Not a valid filter: " + value + e.getMessage());
+ return false;
+ }
+ }
+
+ private void verifyActivator() {
+ String bactivator = getHeader("Bundle-Activator");
+ if (bactivator != null) {
+ Clazz cl = loadClass(bactivator);
+ if (cl == null) {
+ int n = bactivator.lastIndexOf('.');
+ if (n > 0) {
+ String pack = bactivator.substring(0, n);
+ if (mimports.containsKey(pack))
+ return;
+ error("Bundle-Activator not found on the bundle class path nor in imports: "
+ + bactivator);
+ } else
+ error("Activator uses default package and is not local (default package can not be imported): "
+ + bactivator);
+ }
+ }
+ }
+
+ private Clazz loadClass(String className) {
+ String path = className.replace('.', '/') + ".class";
+ return (Clazz) classSpace.get(path);
+ }
+
+ private void verifyComponent() {
+ String serviceComponent = getHeader("Service-Component");
+ if (serviceComponent != null) {
+ Map<String, Map<String, String>> map = parseHeader(serviceComponent);
+ for (String component : map.keySet()) {
+ if (component.indexOf("*") < 0 && !dot.exists(component)) {
+ error("Service-Component entry can not be located in JAR: "
+ + component);
+ } else {
+ // validate component ...
+ }
+ }
+ }
+ }
+
+ public void info() {
+ System.out.println("Refers : " + referred);
+ System.out.println("Contains : " + contained);
+ System.out.println("Manifest Imports : " + mimports);
+ System.out.println("Manifest Exports : " + mexports);
+ }
+
+ /**
+ * Invalid exports are exports mentioned in the manifest but not found on
+ * the classpath. This can be calculated with: exports - contains.
+ *
+ * Unfortunately, we also must take duplicate names into account. These
+ * duplicates are of course no erroneous.
+ */
+ private void verifyInvalidExports() {
+ Set<String> invalidExport = newSet(mexports.keySet());
+ invalidExport.removeAll(contained.keySet());
+
+ // We might have duplicate names that are marked for it. These
+ // should not be counted. Should we test them against the contained
+ // set? Hmm. If someone wants to hang himself by using duplicates than
+ // I guess he can go ahead ... This is not a recommended practice
+ for (Iterator<String> i = invalidExport.iterator(); i.hasNext();) {
+ String pack = i.next();
+ if (isDuplicate(pack))
+ i.remove();
+ }
+
+ if (!invalidExport.isEmpty())
+ error("Exporting packages that are not on the Bundle-Classpath"
+ + bundleClasspath + ": " + invalidExport);
+ }
+
+ /**
+ * Invalid imports are imports that we never refer to. They can be
+ * calculated by removing the refered packages from the imported packages.
+ * This leaves packages that the manifest imported but that we never use.
+ */
+ private void verifyInvalidImports() {
+ Set<String> invalidImport = newSet(mimports.keySet());
+ invalidImport.removeAll(referred.keySet());
+ // TODO Added this line but not sure why it worked before ...
+ invalidImport.removeAll(contained.keySet());
+ String bactivator = getHeader(Analyzer.BUNDLE_ACTIVATOR);
+ if (bactivator != null) {
+ int n = bactivator.lastIndexOf('.');
+ if (n > 0) {
+ invalidImport.remove(bactivator.substring(0, n));
+ }
+ }
+ if (isPedantic() && !invalidImport.isEmpty())
+ warning("Importing packages that are never refered to by any class on the Bundle-Classpath"
+ + bundleClasspath + ": " + invalidImport);
+ }
+
+ /**
+ * Check for unresolved imports. These are referals that are not imported by
+ * the manifest and that are not part of our bundle classpath. The are
+ * calculated by removing all the imported packages and contained from the
+ * refered packages.
+ */
+ private void verifyUnresolvedReferences() {
+ Set<String> unresolvedReferences = new TreeSet<String>(referred
+ .keySet());
+ unresolvedReferences.removeAll(mimports.keySet());
+ unresolvedReferences.removeAll(contained.keySet());
+
+ // Remove any java.** packages.
+ for (Iterator<String> p = unresolvedReferences.iterator(); p.hasNext();) {
+ String pack = p.next();
+ if (pack.startsWith("java.") || ignore.containsKey(pack))
+ p.remove();
+ else {
+ // Remove any dynamic imports
+ if (isDynamicImport(pack))
+ p.remove();
+ }
+ }
+
+ if (!unresolvedReferences.isEmpty()) {
+ // Now we want to know the
+ // classes that are the culprits
+ Set<String> culprits = new HashSet<String>();
+ for (Clazz clazz : classSpace.values()) {
+ if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+ culprits.add(clazz.getPath());
+ }
+
+ error("Unresolved references to " + unresolvedReferences
+ + " by class(es) on the Bundle-Classpath" + bundleClasspath
+ + ": " + culprits);
+ }
+ }
+
+ /**
+ * @param p
+ * @param pack
+ */
+ private boolean isDynamicImport(String pack) {
+ for (String pattern : mdynimports.keySet()) {
+ // Wildcard?
+ if (pattern.equals("*"))
+ return true; // All packages can be dynamically imported
+
+ if (pattern.endsWith(".*")) {
+ pattern = pattern.substring(0, pattern.length() - 2);
+ if (pack.startsWith(pattern)
+ && (pack.length() == pattern.length() || pack
+ .charAt(pattern.length()) == '.'))
+ return true;
+ } else {
+ if (pack.equals(pattern))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasOverlap(Set<?> a, Set<?> b) {
+ for (Iterator<?> i = a.iterator(); i.hasNext();) {
+ if (b.contains(i.next()))
+ return true;
+ }
+ return false;
+ }
+
+ public void verify() throws Exception {
+ if (classSpace == null)
+ classSpace = analyzeBundleClasspath(dot,
+ parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH)),
+ contained, referred, uses);
+
+
+ verifyDirectives("Export-Package", "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE);
+ verifyDirectives("Import-Package", "resolution:");
+ verifyDirectives("Require-Bundle", "visibility:|resolution:");
+ verifyDirectives("Fragment-Host", "resolution:");
+ verifyDirectives("Provide-Capability", "effective:|uses:");
+ verifyDirectives("Require-Capability", "effective:|resolve:|filter:");
+ verifyDirectives("Bundle-SymbolicName", "singleton:|fragment-attachment:|mandatory:");
+
+
+ verifyManifestFirst();
+ verifyActivator();
+ verifyActivationPolicy();
+ verifyComponent();
+ verifyNative();
+ verifyInvalidExports();
+ verifyInvalidImports();
+ verifyUnresolvedReferences();
+ verifySymbolicName();
+ verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
+ verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
+ verifyHeader("Bundle-Version", VERSION, true);
+ verifyListHeader("Bundle-Classpath", FILE, false);
+ verifyDynamicImportPackage();
+ verifyBundleClasspath();
+ verifyUses();
+ if (usesRequire) {
+ if (!getErrors().isEmpty()) {
+ getWarnings()
+ .add(
+ 0,
+ "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
+ }
+ }
+ }
+
+ /**
+ * Verify if the header does not contain any other directives
+ *
+ * @param header
+ * @param directives
+ */
+ private void verifyDirectives(String header, String directives) {
+ Pattern pattern = Pattern.compile(directives);
+ Map<String,Map<String,String>> map = parseHeader(manifest.getMainAttributes().getValue(header));
+ for ( Map.Entry<String, Map<String,String>> entry : map.entrySet()) {
+ for ( String key : entry.getValue().keySet()) {
+ if ( key.endsWith(":")) {
+ if ( ! key.startsWith("x-")) {
+ Matcher m = pattern.matcher(key);
+ if ( m.matches())
+ continue;
+
+ warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.", key, header, directives.replace('|', ','));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Verify the use clauses
+ */
+ private void verifyUses() {
+ }
+
+ public boolean verifyActivationPolicy() {
+ String policy = getHeader(Constants.BUNDLE_ACTIVATIONPOLICY);
+ if (policy == null)
+ return true;
+
+ return verifyActivationPolicy(policy);
+ }
+
+ public boolean verifyActivationPolicy(String policy) {
+ Map<String, Map<String, String>> map = parseHeader(policy);
+ if (map.size() == 0)
+ warning("Bundle-ActivationPolicy is set but has no argument %s",
+ policy);
+ else if (map.size() > 1)
+ warning("Bundle-ActivationPolicy has too many arguments %s", policy);
+ else {
+ Map<String, String> s = map.get("lazy");
+ if (s == null)
+ warning(
+ "Bundle-ActivationPolicy set but is not set to lazy: %s",
+ policy);
+ else
+ return true;
+ }
+
+ return false;
+ }
+
+ public void verifyBundleClasspath() {
+ Map<String, Map<String, String>> bcp = parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH));
+ if (bcp.isEmpty() || bcp.containsKey("."))
+ return;
+
+ for ( String path : bcp.keySet() ) {
+ if ( path.endsWith("/"))
+ error("A Bundle-ClassPath entry must not end with '/': %s", path);
+
+ if ( dot.getDirectories().containsKey(path))
+ // We assume that any classes are in a directory
+ // and therefore do not care when the bundle is included
+ return;
+ }
+
+ for (String path : dot.getResources().keySet()) {
+ if (path.endsWith(".class")) {
+ warning("The Bundle-Classpath does not contain the actual bundle JAR (as specified with '.' in the Bundle-Classpath) but the JAR does contain classes. Is this intentional?");
+ return;
+ }
+ }
+ }
+
+ /**
+ * <pre>
+ * DynamicImport-Package ::= dynamic-description
+ * ( ',' dynamic-description )*
+ *
+ * dynamic-description::= wildcard-names ( ';' parameter )*
+ * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
+ * wildcard-name ::= package-name
+ * | ( package-name '.*' ) // See 1.4.2
+ * | '*'
+ * </pre>
+ */
+ private void verifyDynamicImportPackage() {
+ verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
+ String dynamicImportPackage = getHeader("DynamicImport-Package");
+ if (dynamicImportPackage == null)
+ return;
+
+ Map<String, Map<String, String>> map = parseHeader(dynamicImportPackage);
+ for (String name : map.keySet()) {
+ name = name.trim();
+ if (!verify(name, WILDCARDPACKAGE))
+ error("DynamicImport-Package header contains an invalid package name: "
+ + name);
+
+ Map<String, String> sub = map.get(name);
+ if (r3 && sub.size() != 0) {
+ error("DynamicPackage-Import has attributes on import: "
+ + name
+ + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
+ }
+ }
+ }
+
+ private void verifyManifestFirst() {
+ if (!dot.manifestFirst) {
+ error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
+ }
+ }
+
+ private void verifySymbolicName() {
+ Map<String, Map<String, String>> bsn = parseHeader(getHeader(Analyzer.BUNDLE_SYMBOLICNAME));
+ if (!bsn.isEmpty()) {
+ if (bsn.size() > 1)
+ error("More than one BSN specified " + bsn);
+
+ String name = (String) bsn.keySet().iterator().next();
+ if (!SYMBOLICNAME.matcher(name).matches()) {
+ error("Symbolic Name has invalid format: " + name);
+ }
+ }
+ }
+
+ /**
+ * <pre>
+ * filter ::= â(â filter-comp â)â
+ * filter-comp ::= and | or | not | operation
+ * and ::= â&â filter-list
+ * or ::= â|â filter-list
+ * not ::= â!â filter
+ * filter-list ::= filter | filter filter-list
+ * operation ::= simple | present | substring
+ * simple ::= attr filter-type value
+ * filter-type ::= equal | approx | greater | less
+ * equal ::= â=â
+ * approx ::= â˜=â
+ * greater ::= â>=â
+ * less ::= â<=â
+ * present ::= attr â=*â
+ * substring ::= attr â=â initial any final
+ * inital ::= () | value
+ * any ::= â*â star-value
+ * star-value ::= () | value â*â star-value
+ * final ::= () | value
+ * value ::= <see text>
+ * </pre>
+ *
+ * @param expr
+ * @param index
+ * @return
+ */
+
+ public static int verifyFilter(String expr, int index) {
+ try {
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ if (expr.charAt(index) != '(')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ( at position " + index
+ + " : " + expr);
+
+ index++; // skip (
+
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ switch (expr.charAt(index)) {
+ case '!':
+ index++; // skip !
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ if (expr.charAt(index) != '(')
+ throw new IllegalArgumentException(
+ "Filter mismatch: ! (not) must have one sub expression "
+ + index + " : " + expr);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+
+ index = verifyFilter(expr, index);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ) at position " + index
+ + " : " + expr);
+ return index + 1;
+
+ case '&':
+ case '|':
+ index++; // skip operator
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ while (expr.charAt(index) == '(') {
+ index = verifyFilter(expr, index);
+ while (Character.isWhitespace(expr.charAt(index)))
+ index++;
+ }
+
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ) at position " + index
+ + " : " + expr);
+ return index + 1; // skip )
+
+ default:
+ index = verifyFilterOperation(expr, index);
+ if (expr.charAt(index) != ')')
+ throw new IllegalArgumentException(
+ "Filter mismatch: expected ) at position " + index
+ + " : " + expr);
+ return index + 1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException(
+ "Filter mismatch: early EOF from " + index);
+ }
+ }
+
+ static private int verifyFilterOperation(String expr, int index) {
+ StringBuffer sb = new StringBuffer();
+ while ("=><~()".indexOf(expr.charAt(index)) < 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String attr = sb.toString().trim();
+ if (attr.length() == 0)
+ throw new IllegalArgumentException(
+ "Filter mismatch: attr at index " + index + " is 0");
+ sb = new StringBuffer();
+ while ("=><~".indexOf(expr.charAt(index)) >= 0) {
+ sb.append(expr.charAt(index++));
+ }
+ String operator = sb.toString();
+ if (!verify(operator, FILTEROP))
+ throw new IllegalArgumentException(
+ "Filter error, illegal operator " + operator + " at index "
+ + index);
+
+ sb = new StringBuffer();
+ while (")".indexOf(expr.charAt(index)) < 0) {
+ switch (expr.charAt(index)) {
+ case '\\':
+ if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0 )
+ index++;
+ else
+ throw new IllegalArgumentException(
+ "Filter error, illegal use of backslash at index "
+ + index
+ + ". Backslash may only be used before * or () or \\");
+ }
+ sb.append(expr.charAt(index++));
+ }
+ return index;
+ }
+
+ private String getHeader(String string) {
+ return main.getValue(string);
+ }
+
+ private boolean verifyHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
+ for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
+ if (!verify((String) i.next(), regex)) {
+ String msg = "Invalid value for " + name + ", " + value
+ + " does not match " + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ static private boolean verify(String value, Pattern regex) {
+ return regex.matcher(value).matches();
+ }
+
+ private boolean verifyListHeader(String name, Pattern regex, boolean error) {
+ String value = manifest.getMainAttributes().getValue(name);
+ if (value == null)
+ return false;
+
+ Map<String, Map<String, String>> map = parseHeader(value);
+ for (String header : map.keySet()) {
+ if (!regex.matcher(header).matches()) {
+ String msg = "Invalid value for " + name + ", " + value
+ + " does not match " + regex.pattern();
+ if (error)
+ error(msg);
+ else
+ warning(msg);
+ }
+ }
+ return true;
+ }
+
+ public String getProperty(String key, String deflt) {
+ if (properties == null)
+ return deflt;
+ return properties.getProperty(key, deflt);
+ }
+
+ public void setClassSpace(Map<String, Clazz> classspace,
+ Map<String, Map<String, String>> contained,
+ Map<String, Map<String, String>> referred,
+ Map<String, Set<String>> uses) {
+ this.classSpace = classspace;
+ this.contained = contained;
+ this.referred = referred;
+ this.uses = uses;
+ }
+
+ public static boolean isVersion(String version) {
+ return VERSION.matcher(version).matches();
+ }
+
+ public static boolean isIdentifier(String value) {
+ if (value.length() < 1)
+ return false;
+
+ if (!Character.isJavaIdentifierStart(value.charAt(0)))
+ return false;
+
+ for (int i = 1; i < value.length(); i++) {
+ if (!Character.isJavaIdentifierPart(value.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isMember(String value, String[] matches) {
+ for (String match : matches) {
+ if (match.equals(value))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isFQN(String name) {
+ if ( name.length() == 0)
+ return false;
+ if ( !Character.isJavaIdentifierStart(name.charAt(0)))
+ return false;
+
+ for ( int i=1; i<name.length(); i++) {
+ char c = name.charAt(i);
+ if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
+ continue;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * public int verifyFilter(StringBuffer sb, String s, int rover) { rover =
+ * skip(s, rover); char c = s.charAt(rover); if (c == '(') { sb.append('(');
+ * char type; rover = skip(s, ++rover); c = s.charAt(rover); switch (c) {
+ * case '!': // not case '&': // and case '|': // or sb.append(c); type = c;
+ * while(true) { rover = skip(++rover); c = s.charAt(rover); if ( c != '(')
+ * break; rover = verifyFilter(s, rover); } break;
+ *
+ * case ')': return rover + 1;
+ *
+ * default: rover = skip(s,rover); c = s.charAt(rover); while (
+ * Character.isLetterOrDigit(c) || ) } } }
+ */
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/Verifier.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,42 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+
+public abstract class WriteResource implements Resource {
+ long lastModified;
+ String extra;
+
+ public InputStream openInputStream() throws Exception {
+ PipedInputStream pin = new PipedInputStream();
+ final PipedOutputStream pout = new PipedOutputStream(pin);
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ write(pout);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ pout.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ };
+ t.start();
+ return pin;
+ }
+
+ public abstract void write(OutputStream out) throws IOException, Exception;
+
+ public abstract long lastModified();
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/WriteResource.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,84 @@
+package aQute.lib.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.zip.*;
+
+public class ZipResource implements Resource {
+ ZipFile zip;
+ ZipEntry entry;
+ long lastModified;
+ String extra;
+
+ ZipResource(ZipFile zip, ZipEntry entry, long lastModified) {
+ this.zip = zip;
+ this.entry = entry;
+ this.lastModified = lastModified;
+ byte[] data = entry.getExtra();
+ if (data != null)
+ this.extra = new String(data);
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return zip.getInputStream(entry);
+ }
+
+ public String toString() {
+ return ":" + zip.getName() + "(" + entry.getName() + "):";
+ }
+
+ public static ZipFile build(Jar jar, File file) throws ZipException,
+ IOException {
+ return build(jar, file, null);
+ }
+
+ public static ZipFile build(Jar jar, File file, Pattern pattern)
+ throws ZipException, IOException {
+
+ try {
+ ZipFile zip = new ZipFile(file);
+ nextEntry: for (Enumeration<? extends ZipEntry> e = zip.entries(); e
+ .hasMoreElements();) {
+ ZipEntry entry = e.nextElement();
+ if (pattern != null) {
+ Matcher m = pattern.matcher(entry.getName());
+ if (!m.matches())
+ continue nextEntry;
+ }
+ if (!entry.isDirectory()) {
+ long time = entry.getTime();
+ if (time <= 0)
+ time = file.lastModified();
+ jar.putResource(entry.getName(), new ZipResource(zip,
+ entry, time), true);
+ }
+ }
+ return zip;
+ } catch (ZipException ze) {
+ throw new ZipException("The JAR/ZIP file ("
+ + file.getAbsolutePath() + ") seems corrupted, error: "
+ + ze.getMessage());
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("Problem opening JAR: "
+ + file.getAbsolutePath());
+ }
+ }
+
+ public void write(OutputStream out) throws Exception {
+ FileResource.copy(this, out);
+ }
+
+ public long lastModified() {
+ return lastModified;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/ZipResource.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,248 @@
+package aQute.lib.osgi.eclipse;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.parsers.*;
+
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import aQute.libg.reporter.*;
+
+/**
+ * Parse the Eclipse project information for the classpath. Unfortunately, it is
+ * impossible to read the variables. They are ignored but that can cause
+ * problems.
+ *
+ * @version $Revision$
+ */
+public class EclipseClasspath {
+ static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ DocumentBuilder db;
+ File project;
+ File workspace;
+ Set<File> sources = new LinkedHashSet<File>();
+ Set<File> allSources = new LinkedHashSet<File>();
+
+ Set<File> classpath = new LinkedHashSet<File>();
+ List<File> dependents = new ArrayList<File>();
+ File output;
+ boolean recurse = true;
+ Set<File> exports = new LinkedHashSet<File>();
+ Map<String, String> properties = new HashMap<String, String>();
+ Reporter reporter;
+ int options;
+ Set<File> bootclasspath = new LinkedHashSet<File>();
+
+ public final static int DO_VARIABLES = 1;
+
+ /**
+ * Parse an Eclipse project structure to discover the classpath.
+ *
+ * @param workspace
+ * Points to workspace
+ * @param project
+ * Points to project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project,
+ int options) throws Exception {
+ this.project = project.getCanonicalFile();
+ this.workspace = workspace.getCanonicalFile();
+ this.reporter = reporter;
+ db = documentBuilderFactory.newDocumentBuilder();
+ parse(this.project, true);
+ db = null;
+ }
+
+ public EclipseClasspath(Reporter reporter, File workspace, File project)
+ throws Exception {
+ this(reporter, workspace, project, 0);
+ }
+
+ /**
+ * Recursive routine to parse the files. If a sub project is detected, it is
+ * parsed before the parsing continues. This should give the right order.
+ *
+ * @param project
+ * Project directory
+ * @param top
+ * If this is the top project
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+ void parse(File project, boolean top) throws ParserConfigurationException,
+ SAXException, IOException {
+ File file = new File(project, ".classpath");
+ if (!file.exists())
+ throw new FileNotFoundException(".classpath file not found: "
+ + file.getAbsolutePath());
+
+ Document doc = db.parse(file);
+ NodeList nodelist = doc.getDocumentElement().getElementsByTagName(
+ "classpathentry");
+
+ if (nodelist == null)
+ throw new IllegalArgumentException(
+ "Can not find classpathentry in classpath file");
+
+ for (int i = 0; i < nodelist.getLength(); i++) {
+ Node node = nodelist.item(i);
+ NamedNodeMap attrs = node.getAttributes();
+ String kind = get(attrs, "kind");
+ if ("src".equals(kind)) {
+ String path = get(attrs, "path");
+ // TODO boolean exported = "true".equalsIgnoreCase(get(attrs,
+ // "exported"));
+ if (path.startsWith("/")) {
+ // We have another project
+ File subProject = getFile(workspace, project, path);
+ if (recurse)
+ parse(subProject, false);
+ dependents.add(subProject.getCanonicalFile());
+ } else {
+ File src = getFile(workspace, project, path);
+ allSources.add(src);
+ if (top) {
+ // We only want the sources for our own project
+ // or we'll compile all at once. Not a good idea
+ // because project settings can differ.
+ sources.add(src);
+ }
+ }
+ } else if ("lib".equals(kind)) {
+ String path = get(attrs, "path");
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ if (top || exported) {
+ File jar = getFile(workspace, project, path);
+ if (jar.getName().startsWith("ee."))
+ bootclasspath.add(jar);
+ else
+ classpath.add(jar);
+ if (exported)
+ exports.add(jar);
+ }
+ } else if ("output".equals(kind)) {
+ String path = get(attrs, "path");
+ path = path.replace('/', File.separatorChar);
+ output = getFile(workspace, project, path);
+ classpath.add(output);
+ exports.add(output);
+ } else if ("var".equals(kind)) {
+ boolean exported = "true".equalsIgnoreCase(get(attrs,
+ "exported"));
+ File lib = replaceVar(get(attrs, "path"));
+ File slib = replaceVar(get(attrs, "sourcepath"));
+ if (lib != null) {
+ classpath.add(lib);
+ if (exported)
+ exports.add(lib);
+ }
+ if (slib != null)
+ sources.add(slib);
+ } else if ("con".equals(kind)) {
+ // Should do something useful ...
+ }
+ }
+ }
+
+ private File getFile(File abs, File relative, String opath) {
+ String path = opath.replace('/', File.separatorChar);
+ File result = new File(path);
+ if (result.isAbsolute() && result.isFile()) {
+ return result;
+ }
+ if (path.startsWith(File.separator)) {
+ result = abs;
+ path = path.substring(1);
+ } else
+ result = relative;
+
+ StringTokenizer st = new StringTokenizer(path, File.separator);
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ result = new File(result, token);
+ }
+
+ if (!result.exists())
+ System.err.println("File not found: project=" + project
+ + " workspace=" + workspace + " path=" + opath + " file="
+ + result);
+ return result;
+ }
+
+ static Pattern PATH = Pattern.compile("([A-Z_]+)/(.*)");
+
+ private File replaceVar(String path) {
+ if ((options & DO_VARIABLES) == 0)
+ return null;
+
+ Matcher m = PATH.matcher(path);
+ if (m.matches()) {
+ String var = m.group(1);
+ String remainder = m.group(2);
+ String base = (String) properties.get(var);
+ if (base != null) {
+ File b = new File(base);
+ File f = new File(b, remainder.replace('/', File.separatorChar));
+ return f;
+ } else
+ reporter.error("Can't find replacement variable for: " + path);
+ } else
+ reporter.error("Cant split variable path: " + path);
+ return null;
+ }
+
+ private String get(NamedNodeMap map, String name) {
+ Node node = map.getNamedItem(name);
+ if (node == null)
+ return null;
+
+ return node.getNodeValue();
+ }
+
+ public Set<File> getClasspath() {
+ return classpath;
+ }
+
+ public Set<File> getSourcepath() {
+ return sources;
+ }
+
+ public File getOutput() {
+ return output;
+ }
+
+ public List<File> getDependents() {
+ return dependents;
+ }
+
+ public void setRecurse(boolean recurse) {
+ this.recurse = recurse;
+ }
+
+ public Set<File> getExports() {
+ return exports;
+ }
+
+ public void setProperties(Map<String, String> map) {
+ this.properties = map;
+ }
+
+ public Set<File> getBootclasspath() {
+ return bootclasspath;
+ }
+
+ public Set<File> getAllSources() {
+ return allSources;
+ }
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/eclipse/EclipseClasspath.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/osgi/packageinfo Mon Oct 17 10:31:43 2011
@@ -0,0 +1 @@
+version 1.43.1
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,281 @@
+package aQute.lib.putjar;
+
+import java.io.*;
+import java.util.zip.*;
+
+import aQute.libg.fileiterator.*;
+
+public class DirectoryInputStream extends InputStream {
+ final File root;
+ final FileIterator fi;
+ File element;
+ int entries = 0;
+ int state = START;
+ long where = 0;
+
+ final static int START = 0;
+ final static int HEADER = 1;
+ final static int DATA = 2;
+ final static int DIRECTORY = 4;
+ final static int EOF = 5;
+
+ final static InputStream eof = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream directory = new ByteArrayOutputStream();
+ InputStream current = eof;
+
+ public DirectoryInputStream(File dir) {
+ root = dir;
+ fi = new FileIterator(dir);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (fi == null)
+ return -1;
+
+ int c = current.read();
+ if (c < 0) {
+ next();
+ c = current.read();
+ }
+ if (c >= 0)
+ where++;
+
+ return c;
+ }
+
+ void next() throws IOException {
+ switch (state) {
+ case START:
+ case DATA:
+ nextHeader();
+ break;
+
+ case HEADER:
+ if (element.isFile() && element.length() > 0) {
+ current = new FileInputStream(element);
+ state = DATA;
+ } else
+ nextHeader();
+ break;
+
+ case DIRECTORY:
+ state = EOF;
+ current = eof;
+ break;
+
+ case EOF:
+ break;
+ }
+ }
+
+ private void nextHeader() throws IOException {
+ if (fi.hasNext()) {
+ element = fi.next();
+ state = HEADER;
+ current = getHeader(root, element);
+ entries++;
+ } else {
+ current = getDirectory();
+ state = DIRECTORY;
+ }
+ }
+
+ /**
+ * <pre>
+ * end of central dir signature 4 bytes (0x06054b50)
+ * number of this disk 2 bytes
+ * number of the disk with the
+ * start of the central directory 2 bytes
+ * total number of entries in the
+ * central directory on this disk 2 bytes
+ * total number of entries in
+ * the central directory 2 bytes
+ * size of the central directory 4 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 4 bytes
+ * .ZIP file comment length 2 bytes
+ * .ZIP file comment (variable size)
+ * </pre>
+ *
+ * @return
+ */
+ InputStream getDirectory() throws IOException {
+ long where = this.where;
+ int sizeDirectory = directory.size();
+
+ writeInt(directory, 0x504b0506); // Signature
+ writeShort(directory, 0); // # of disk
+ writeShort(directory, 0); // # of the disk with start of the central
+ // dir
+ writeShort(directory, entries); // # of entries
+ writeInt(directory, sizeDirectory); // Size of central dir
+ writeInt(directory, (int) where);
+ writeShort(directory, 0);
+
+ directory.close();
+
+ byte[] data = directory.toByteArray();
+ return new ByteArrayInputStream(data);
+ }
+
+ private void writeShort(OutputStream out, int v) throws IOException {
+ for (int i = 0; i < 2; i++) {
+ out.write((byte) (v & 0xFF));
+ v = v >> 8;
+ }
+ }
+
+ private void writeInt(OutputStream out, int v) throws IOException {
+ for (int i = 0; i < 4; i++) {
+ out.write((byte) (v & 0xFF));
+ v = v >> 8;
+ }
+ }
+
+ /**
+ * Local file header:
+ *
+ * <pre>
+ *
+ * local file header signature 4 bytes (0x04034b50)
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ *
+ * file name (variable size)
+ * extra field (variable size)
+ *
+ * central file header signature 4 bytes (0x02014b50)
+ * version made by 2 bytes
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ * file comment length 2 bytes
+ * disk number start 2 bytes
+ * internal file attributes 2 bytes
+ * external file attributes 4 bytes
+ * relative offset of local header 4 bytes
+ *
+ * file name (variable size)
+ * extra field (variable size)
+ * file comment (variable size)
+ * </pre>
+ * </pre>
+ *
+ * @param file
+ * @return
+ */
+ private InputStream getHeader(File root, File file) throws IOException {
+ long where = this.where;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ // Signature
+ writeInt(bout, 0x04034b50);
+ writeInt(directory, 0x504b0102);
+
+ // Version needed to extract
+ writeShort(directory, 0);
+
+ // Version needed to extract
+ writeShort(bout, 10);
+ writeShort(directory, 10);
+
+ // General purpose bit flag (use descriptor)
+ writeShort(bout, 0); // descriptor follows data
+ writeShort(directory, 0); // descriptor follows data
+
+ // Compresson method (stored)
+ writeShort(bout, 0);
+ writeShort(directory, 0);
+
+ // Mod time
+ writeInt(bout, 0);
+ writeInt(directory, 0);
+
+ if (file.isDirectory()) {
+ writeInt(bout, 0); // CRC
+ writeInt(bout, 0); // Compressed size
+ writeInt(bout, 0); // Uncompressed Size
+ writeInt(directory, 0);
+ writeInt(directory, 0);
+ writeInt(directory, 0);
+ } else {
+ CRC32 crc = getCRC(file);
+ writeInt(bout, (int) crc.getValue());
+ writeInt(bout, (int) file.length());
+ writeInt(bout, (int) file.length());
+ writeInt(directory, (int) crc.getValue());
+ writeInt(directory, (int) file.length());
+ writeInt(directory, (int) file.length());
+ }
+
+ String p = getPath(root, file);
+ if (file.isDirectory())
+ p = p + "/";
+ byte[] path = p.getBytes("UTF-8");
+ writeShort(bout, path.length);
+ writeShort(directory, path.length);
+
+ writeShort(bout, 0); // extra length
+ writeShort(directory, 0);
+
+ bout.write(path);
+
+ writeShort(directory, 0); // File comment length
+ writeShort(directory, 0); // disk number start 2 bytes
+ writeShort(directory, 0); // internal file attributes 2 bytes
+ writeInt(directory, 0); // external file attributes 4 bytes
+ writeInt(directory, (int) where); // relative offset of local header 4
+ // bytes
+
+ directory.write(path);
+
+ byte[] bytes = bout.toByteArray();
+ return new ByteArrayInputStream(bytes);
+ }
+
+ private String getPath(File root, File file) {
+ if (file.equals(root))
+ return "";
+
+ String p = getPath(root, file.getParentFile());
+ if (p.length() == 0)
+ p = file.getName();
+ else {
+ p = p + "/" + file.getName();
+ }
+ return p;
+ }
+
+ private CRC32 getCRC(File file) throws IOException {
+ CRC32 crc = new CRC32();
+ FileInputStream in = new FileInputStream(file);
+ try {
+ byte data[] = new byte[10000];
+ int size = in.read(data);
+ while (size > 0) {
+ crc.update(data, 0, size);
+ size = in.read(data);
+ }
+ } finally {
+ in.close();
+ }
+ return crc;
+ }
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/DirectoryInputStream.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/putjar/packageinfo Mon Oct 17 10:31:43 2011
@@ -0,0 +1 @@
+version 1.0
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,25 @@
+package aQute.lib.spring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import aQute.lib.osgi.Analyzer;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class JPAComponent extends XMLTypeProcessor {
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ List<XMLType> types = new ArrayList<XMLType>();
+ process(types,"jpa.xsl", "META-INF", "persistence.xml");
+ return types;
+ }
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/JPAComponent.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,97 @@
+package aQute.lib.spring;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class SpringComponent implements AnalyzerPlugin {
+ static Transformer transformer;
+ static Pattern SPRING_SOURCE = Pattern.compile("META-INF/spring/.*\\.xml");
+ static Pattern QN = Pattern.compile("[_A-Za-z$][_A-Za-z0-9$]*(\\.[_A-Za-z$][_A-Za-z0-9$]*)*");
+
+ public static Set<CharSequence> analyze(InputStream in) throws Exception {
+ if (transformer == null) {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Source source = new StreamSource(SpringComponent.class
+ .getResourceAsStream("extract.xsl"));
+ transformer = tf.newTransformer(source);
+ }
+
+ Set<CharSequence> refers = new HashSet<CharSequence>();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ Result r = new StreamResult(bout);
+ Source s = new StreamSource(in);
+ transformer.transform(s, r);
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ bout.close();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(bin, "UTF8"));
+
+ String line = br.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String parts[] = line.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ int n = parts[i].lastIndexOf('.');
+ if (n > 0) {
+ refers.add(parts[i].subSequence(0, n));
+ }
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ return refers;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ Jar jar = analyzer.getJar();
+ Map dir = (Map) jar.getDirectories().get("META-INF/spring");
+ if ( dir == null || dir.isEmpty())
+ return false;
+
+ for (Iterator i = dir.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ String path = (String) entry.getKey();
+ Resource resource = (Resource) entry.getValue();
+ if (SPRING_SOURCE.matcher(path).matches()) {
+ try {
+ InputStream in = resource.openInputStream();
+ Set set = analyze(in);
+ in.close();
+ for (Iterator r = set.iterator(); r.hasNext();) {
+ String pack = (String) r.next();
+ if ( !QN.matcher(pack).matches())
+ analyzer.warning("Package does not seem a package in spring resource ("+path+"): " + pack );
+ if (!analyzer.getReferred().containsKey(pack))
+ analyzer.getReferred().put(pack, new LinkedHashMap());
+ }
+ } catch( Exception e ) {
+ analyzer.error("Unexpected exception in processing spring resources("+path+"): " + e );
+ }
+ }
+ }
+ return false;
+ }
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringComponent.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,33 @@
+package aQute.lib.spring;
+
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+/**
+ * This component is called when we find a resource in the META-INF/*.xml
+ * pattern. We parse the resource and and the imports to the builder.
+ *
+ * Parsing is done with XSLT (first time I see the use of having XML for the
+ * Spring configuration files!).
+ *
+ * @author aqute
+ *
+ */
+public class SpringXMLType extends XMLTypeProcessor {
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ List<XMLType> types = new ArrayList<XMLType>();
+
+ String header = analyzer.getProperty("Bundle-Blueprint", "META-INF/blueprint");
+ process(types,"extract.xsl", header, ".*\\.xml");
+ header = analyzer.getProperty("Spring-Context", "META-INF/spring");
+ process(types,"extract.xsl", header, ".*\\.xml");
+
+ return types;
+ }
+
+
+
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/SpringXMLType.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,108 @@
+package aQute.lib.spring;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+import aQute.lib.osgi.*;
+
+public class XMLType {
+
+ Transformer transformer;
+ Pattern paths;
+ String root;
+
+
+ static Pattern QN = Pattern
+ .compile("[_A-Za-z$][_A-Za-z0-9$]*(\\.[_A-Za-z$][_A-Za-z0-9$]*)*");
+
+ public XMLType(URL source, String root, String paths ) throws Exception {
+ transformer = getTransformer(source);
+ this.paths = Pattern.compile(paths);
+ this.root = root;
+ }
+
+ public Set<String> analyze(InputStream in) throws Exception {
+ Set<String> refers = new HashSet<String>();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ Result r = new StreamResult(bout);
+ Source s = new StreamSource(in);
+ transformer.transform(s, r);
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ bout.close();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(bin, "UTF8"));
+
+ String line = br.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String parts[] = line.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ int n = parts[i].lastIndexOf('.');
+ if (n > 0) {
+ refers.add(parts[i].subSequence(0, n).toString());
+ }
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ return refers;
+ }
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ Jar jar = analyzer.getJar();
+ Map<String,Resource> dir = jar.getDirectories().get(root);
+ if (dir == null || dir.isEmpty()) {
+ Resource resource = jar.getResource(root);
+ if ( resource != null )
+ process(analyzer, root, resource);
+ return false;
+ }
+
+ for (Iterator<Map.Entry<String,Resource>> i = dir.entrySet().iterator(); i.hasNext();) {
+ Map.Entry<String,Resource> entry = i.next();
+ String path = entry.getKey();
+ Resource resource = entry.getValue();
+ if (paths.matcher(path).matches()) {
+ process(analyzer, path, resource);
+ }
+ }
+ return false;
+ }
+
+ private void process(Analyzer analyzer, String path, Resource resource) {
+ try {
+ InputStream in = resource.openInputStream();
+ Set<String> set = analyze(in);
+ in.close();
+ for (Iterator<String> r = set.iterator(); r.hasNext();) {
+ String pack = r.next();
+ if (!QN.matcher(pack).matches())
+ analyzer
+ .warning("Package does not seem a package in spring resource ("
+ + path + "): " + pack);
+ if (!analyzer.getReferred().containsKey(pack))
+ analyzer.getReferred().put(pack,
+ new LinkedHashMap<String,String>());
+ }
+ } catch (Exception e) {
+ analyzer
+ .error("Unexpected exception in processing spring resources("
+ + path + "): " + e);
+ }
+ }
+
+ protected Transformer getTransformer(java.net.URL url) throws Exception {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Source source = new StreamSource(url.openStream());
+ return tf.newTransformer(source);
+ }
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLType.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Added: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,33 @@
+package aQute.lib.spring;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+
+public class XMLTypeProcessor implements AnalyzerPlugin {
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ List<XMLType> types = getTypes(analyzer);
+ for ( XMLType type : types ) {
+ type.analyzeJar(analyzer);
+ }
+ return false;
+ }
+
+ protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ return new ArrayList<XMLType>();
+ }
+
+
+ protected void process(List<XMLType> types, String resource, String paths,
+ String pattern) throws Exception {
+
+ Map<String,Map<String,String>> map = Processor.parseHeader(paths,null);
+ for ( String path : map.keySet() ) {
+ types.add( new XMLType( getClass().getResource(resource), path, pattern ));
+ }
+ }
+
+
+}
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: felix/trunk/bundleplugin/src/main/java/aQute/lib/spring/XMLTypeProcessor.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision