blob: 00cd67ec998c9a94b8119515ac744aca2fe2d2ce [file] [log] [blame]
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 ::= ’&amp;’ 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 ::= ’&tilde;=’
* greater ::= ’&gt;=’
* less ::= ’&lt;=’
* present ::= attr ’=*’
* substring ::= attr ’=’ initial any final
* inital ::= () | value
* any ::= ’*’ star-value
* star-value ::= () | value ’*’ star-value
* final ::= () | value
* value ::= &lt;see text&gt;
* </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) || ) } } }
*/
}