| 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) || ) } } } |
| */ |
| } |