blob: 85b36d4f5c23b761e4025f6b986b94e05c47b933 [file] [log] [blame]
Stuart McCullochd00f9712009-07-13 10:06:47 +00001/* Copyright 2006 aQute SARL
2 * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
3package aQute.lib.osgi;
4
5import java.io.*;
6import java.util.*;
7import java.util.jar.*;
8import java.util.regex.*;
9
10import aQute.libg.qtokens.*;
11
12public class Verifier extends Analyzer {
13
14 Jar dot;
15 Manifest manifest;
16 Map<String, Map<String, String>> referred = newHashMap();
17 Map<String, Map<String, String>> contained = newHashMap();
18 Map<String, Set<String>> uses = newHashMap();
19 Map<String, Map<String, String>> mimports;
20 Map<String, Map<String, String>> mdynimports;
21 Map<String, Map<String, String>> mexports;
22 List<Jar> bundleClasspath;
23 Map<String, Map<String, String>> ignore = newHashMap(); // Packages
24 // to
25 // ignore
26
27 Map<String, Clazz> classSpace;
28 boolean r3;
29 boolean usesRequire;
30 boolean fragment;
31 Attributes main;
32
33 final static Pattern EENAME = Pattern
34 .compile("CDC-1\\.0/Foundation-1\\.0"
35 + "|CDC-1\\.1/Foundation-1\\.1"
36 + "|OSGi/Minimum-1\\.[1-9]"
37 + "|JRE-1\\.1"
38 + "|J2SE-1\\.2"
39 + "|J2SE-1\\.3"
40 + "|J2SE-1\\.4"
41 + "|J2SE-1\\.5"
42 + "|JavaSE-1\\.6"
43 + "|JavaSE-1\\.7"
44 + "|PersonalJava-1\\.1"
45 + "|PersonalJava-1\\.2"
46 + "|CDC-1\\.0/PersonalBasis-1\\.0"
47 + "|CDC-1\\.0/PersonalJava-1\\.0");
48
49 final static Pattern BUNDLEMANIFESTVERSION = Pattern
50 .compile("2");
51 public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
52 public final static Pattern SYMBOLICNAME = Pattern
53 .compile(SYMBOLICNAME_STRING);
54
55 public final static String VERSION_STRING = "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
56 public final static Pattern VERSION = Pattern
57 .compile(VERSION_STRING);
58 final static Pattern FILTEROP = Pattern
59 .compile("=|<=|>=|~=");
60 public final static Pattern VERSIONRANGE = Pattern
61 .compile("((\\(|\\[)"
62 + VERSION_STRING
63 + ","
64 + VERSION_STRING
65 + "(\\]|\\)))|"
66 + VERSION_STRING);
67 final static Pattern FILE = Pattern
68 .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
69 final static Pattern WILDCARDPACKAGE = Pattern
70 .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
71 public final static Pattern ISO639 = Pattern
72 .compile("[A-Z][A-Z]");
73 public final static Pattern HEADER_PATTERN = Pattern
74 .compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
75 public final static Pattern TOKEN = Pattern
76 .compile("[-a-zA-Z0-9_]+");
77
78 public final static Pattern NUMBERPATTERN = Pattern
79 .compile("\\d+");
80 public final static Pattern PATHPATTERN = Pattern
81 .compile(".*");
82 public final static Pattern FQNPATTERN = Pattern
83 .compile(".*");
84 public final static Pattern URLPATTERN = Pattern
85 .compile(".*");
86 public final static Pattern ANYPATTERN = Pattern
87 .compile(".*");
88 public final static Pattern FILTERPATTERN = Pattern
89 .compile(".*");
90 public final static Pattern TRUEORFALSEPATTERN = Pattern.compile("true|false|TRUE|FALSE");
91 public static final Pattern WILDCARDNAMEPATTERN = Pattern.compile(".*");
92
93 public final static String EES[] = {
94 "CDC-1.0/Foundation-1.0",
95 "CDC-1.1/Foundation-1.1",
96 "OSGi/Minimum-1.0",
97 "OSGi/Minimum-1.1",
98 "OSGi/Minimum-1.2",
99 "JRE-1.1",
100 "J2SE-1.2",
101 "J2SE-1.3",
102 "J2SE-1.4",
103 "J2SE-1.5",
104 "JavaSE-1.6",
105 "JavaSE-1.7",
106 "PersonalJava-1.1",
107 "PersonalJava-1.2",
108 "CDC-1.0/PersonalBasis-1.0",
109 "CDC-1.0/PersonalJava-1.0"
110 };
111
112 public final static String OSNAMES[] = {
113 "AIX", // IBM
114 "DigitalUnix", // Compaq
115 "Embos", // Segger Embedded Software Solutions
116 "Epoc32", // SymbianOS Symbian OS
117 "FreeBSD", // Free BSD
118 "HPUX", // hp-ux Hewlett Packard
119 "IRIX", // Silicon Graphics
120 "Linux", // Open source
121 "MacOS", // Apple
122 "NetBSD", // Open source
123 "Netware", // Novell
124 "OpenBSD", // Open source
125 "OS2", // OS/2 IBM
126 "QNX", // procnto QNX
127 "Solaris", // Sun (almost an alias of SunOS)
128 "SunOS", // Sun Microsystems
129 "VxWorks", // WindRiver Systems
130 "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE",
131 "Windows2000", // Win2000
132 "Windows2003", // Win2003
133 "WindowsXP", "WindowsVista", };
134
135 public final static String PROCESSORNAMES[] = { "68k", // Motorola
136 // 68000
137 "ARM_LE", // Intel Strong ARM. Deprecated because it does not
138 // specify the endianness. See the following two rows.
139 "arm_le", // Intel Strong ARM Little Endian mode
140 "arm_be", // Intel String ARM Big Endian mode
141 "Alpha", //
142 "ia64n",// Hewlett Packard 64 bit
143 "ia64w",// Hewlett Packard 32 bit mode
144 "Ignite", // psc1k PTSC
145 "Mips", // SGI
146 "PArisc", // Hewlett Packard
147 "PowerPC", // power ppc Motorola/IBM Power PC
148 "Sh4", // Hitachi
149 "Sparc", // SUN
150 "S390", // IBM Mainframe 31 bit
151 "S390x", // IBM Mainframe 64-bit
152 "V850E", // NEC V850E
153 "x86", // pentium i386
154 "i486", // i586 i686 Intel& AMD 32 bit
155 "x86-64", };
156
157 Properties properties;
158
159 public Verifier(Jar jar) throws Exception {
160 this(jar, null);
161 }
162
163 public Verifier(Jar jar, Properties properties) throws Exception {
164 this.dot = jar;
165 this.properties = properties;
166 this.manifest = jar.getManifest();
167 if (manifest == null) {
168 manifest = new Manifest();
169 error("This file contains no manifest and is therefore not a bundle");
170 }
171 main = this.manifest.getMainAttributes();
172 verifyHeaders(main);
173 r3 = getHeader(Analyzer.BUNDLE_MANIFESTVERSION) == null;
174 usesRequire = getHeader(Analyzer.REQUIRE_BUNDLE) != null;
175 fragment = getHeader(Analyzer.FRAGMENT_HOST) != null;
176
177 bundleClasspath = getBundleClassPath();
178 mimports = parseHeader(manifest.getMainAttributes().getValue(
179 Analyzer.IMPORT_PACKAGE));
180 mdynimports = parseHeader(manifest.getMainAttributes().getValue(
181 Analyzer.DYNAMICIMPORT_PACKAGE));
182 mexports = parseHeader(manifest.getMainAttributes().getValue(
183 Analyzer.EXPORT_PACKAGE));
184
185 ignore = parseHeader(manifest.getMainAttributes().getValue(
186 Analyzer.IGNORE_PACKAGE));
187 }
188
189 public Verifier() {
190 // TODO Auto-generated constructor stub
191 }
192
193 private void verifyHeaders(Attributes main) {
194 for (Object element : main.keySet()) {
195 Attributes.Name header = (Attributes.Name) element;
196 String h = header.toString();
197 if (!HEADER_PATTERN.matcher(h).matches())
198 error("Invalid Manifest header: " + h + ", pattern="
199 + HEADER_PATTERN);
200 }
201 }
202
203 private List<Jar> getBundleClassPath() {
204 List<Jar> list = newList();
205 String bcp = getHeader(Analyzer.BUNDLE_CLASSPATH);
206 if (bcp == null) {
207 list.add(dot);
208 } else {
209 Map<String, Map<String, String>> entries = parseHeader(bcp);
210 for (String jarOrDir : entries.keySet()) {
211 if (jarOrDir.equals(".")) {
212 list.add(dot);
213 } else {
214 if (jarOrDir.equals("/"))
215 jarOrDir = "";
216 if (jarOrDir.endsWith("/")) {
217 error("Bundle-Classpath directory must not end with a slash: "
218 + jarOrDir);
219 jarOrDir = jarOrDir.substring(0, jarOrDir.length() - 1);
220 }
221
222 Resource resource = dot.getResource(jarOrDir);
223 if (resource != null) {
224 try {
225 Jar sub = new Jar(jarOrDir);
226 addClose(sub);
227 EmbeddedResource.build(sub, resource);
228 if (!jarOrDir.endsWith(".jar"))
229 warning("Valid JAR file on Bundle-Classpath does not have .jar extension: "
230 + jarOrDir);
231 list.add(sub);
232 } catch (Exception e) {
233 error("Invalid embedded JAR file on Bundle-Classpath: "
234 + jarOrDir + ", " + e);
235 }
236 } else if (dot.getDirectories().containsKey(jarOrDir)) {
237 if (r3)
238 error("R3 bundles do not support directories on the Bundle-ClassPath: "
239 + jarOrDir);
240
241 try {
242 Jar sub = new Jar(jarOrDir);
243 addClose(sub);
244 for (Map.Entry<String, Resource> entry : dot
245 .getResources().entrySet()) {
246 if (entry.getKey().startsWith(jarOrDir))
247 sub.putResource(entry.getKey().substring(
248 jarOrDir.length() + 1), entry
249 .getValue());
250 }
251 list.add(sub);
252 } catch (Exception e) {
253 error("Invalid embedded directory file on Bundle-Classpath: "
254 + jarOrDir + ", " + e);
255 }
256 } else {
257 error("Cannot find a file or directory for Bundle-Classpath entry: "
258 + jarOrDir);
259 }
260 }
261 }
262 }
263 return list;
264 }
265
266 /*
267 * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
268 * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
269 * optional ::= ’*’
270 */
271 public void verifyNative() {
272 String nc = getHeader("Bundle-NativeCode");
273 doNative(nc);
274 }
275
276 public void doNative(String nc) {
277 if (nc != null) {
278 QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
279 char del;
280 do {
281 do {
282 String name = qt.nextToken();
283 if (name == null) {
284 error("Can not parse name from bundle native code header: "
285 + nc);
286 return;
287 }
288 del = qt.getSeparator();
289 if (del == ';') {
290 if (dot != null && !dot.exists(name)) {
291 error("Native library not found in JAR: " + name);
292 }
293 } else {
294 String value = null;
295 if (del == '=')
296 value = qt.nextToken();
297
298 String key = name.toLowerCase();
299 if (key.equals("osname")) {
300 // ...
301 } else if (key.equals("osversion")) {
302 // verify version range
303 verify(value, VERSIONRANGE);
304 } else if (key.equals("language")) {
305 verify(value, ISO639);
306 } else if (key.equals("processor")) {
307 // verify(value, PROCESSORS);
308 } else if (key.equals("selection-filter")) {
309 // verify syntax filter
310 verifyFilter(value);
311 } else if (name.equals("*") && value == null) {
312 // Wildcard must be at end.
313 if (qt.nextToken() != null)
314 error("Bundle-Native code header may only END in wildcard: nc");
315 } else {
316 warning("Unknown attribute in native code: " + name
317 + "=" + value);
318 }
319 del = qt.getSeparator();
320 }
321 } while (del == ';');
322 } while (del == ',');
323 }
324 }
325
326 public void verifyFilter(String value) {
327 try {
328 verifyFilter(value, 0);
329 } catch (Exception e) {
330 error("Not a valid filter: " + value + e.getMessage());
331 }
332 }
333
334 private void verifyActivator() {
335 String bactivator = getHeader("Bundle-Activator");
336 if (bactivator != null) {
337 Clazz cl = loadClass(bactivator);
338 if (cl == null) {
339 int n = bactivator.lastIndexOf('.');
340 if (n > 0) {
341 String pack = bactivator.substring(0, n);
342 if (mimports.containsKey(pack))
343 return;
344 error("Bundle-Activator not found on the bundle class path nor in imports: "
345 + bactivator);
346 } else
347 error("Activator uses default package and is not local (default package can not be imported): "
348 + bactivator);
349 }
350 }
351 }
352
353 private Clazz loadClass(String className) {
354 String path = className.replace('.', '/') + ".class";
355 return (Clazz) classSpace.get(path);
356 }
357
358 private void verifyComponent() {
359 String serviceComponent = getHeader("Service-Component");
360 if (serviceComponent != null) {
361 Map<String, Map<String, String>> map = parseHeader(serviceComponent);
362 for (String component : map.keySet()) {
363 if (component.indexOf("*")<0 && !dot.exists(component)) {
364 error("Service-Component entry can not be located in JAR: "
365 + component);
366 } else {
367 // validate component ...
368 }
369 }
370 }
371 }
372
373 public void info() {
374 System.out.println("Refers : " + referred);
375 System.out.println("Contains : " + contained);
376 System.out.println("Manifest Imports : " + mimports);
377 System.out.println("Manifest Exports : " + mexports);
378 }
379
380 /**
381 * Invalid exports are exports mentioned in the manifest but not found on
382 * the classpath. This can be calculated with: exports - contains.
383 *
384 * Unfortunately, we also must take duplicate names into account. These
385 * duplicates are of course no erroneous.
386 */
387 private void verifyInvalidExports() {
388 Set<String> invalidExport = newSet(mexports.keySet());
389 invalidExport.removeAll(contained.keySet());
390
391 // We might have duplicate names that are marked for it. These
392 // should not be counted. Should we test them against the contained
393 // set? Hmm. If someone wants to hang himself by using duplicates than
394 // I guess he can go ahead ... This is not a recommended practice
395 for (Iterator<String> i = invalidExport.iterator(); i.hasNext();) {
396 String pack = i.next();
397 if (isDuplicate(pack))
398 i.remove();
399 }
400
401 if (!invalidExport.isEmpty())
402 error("Exporting packages that are not on the Bundle-Classpath"
403 + bundleClasspath + ": " + invalidExport);
404 }
405
406 /**
407 * Invalid imports are imports that we never refer to. They can be
408 * calculated by removing the refered packages from the imported packages.
409 * This leaves packages that the manifest imported but that we never use.
410 */
411 private void verifyInvalidImports() {
412 Set<String> invalidImport = newSet(mimports.keySet());
413 invalidImport.removeAll(referred.keySet());
414 // TODO Added this line but not sure why it worked before ...
415 invalidImport.removeAll(contained.keySet());
416 String bactivator = getHeader(Analyzer.BUNDLE_ACTIVATOR);
417 if (bactivator != null) {
418 int n = bactivator.lastIndexOf('.');
419 if (n > 0) {
420 invalidImport.remove(bactivator.substring(0, n));
421 }
422 }
423 if (isPedantic() && !invalidImport.isEmpty())
424 warning("Importing packages that are never refered to by any class on the Bundle-Classpath"
425 + bundleClasspath + ": " + invalidImport);
426 }
427
428 /**
429 * Check for unresolved imports. These are referals that are not imported by
430 * the manifest and that are not part of our bundle classpath. The are
431 * calculated by removing all the imported packages and contained from the
432 * refered packages.
433 */
434 private void verifyUnresolvedReferences() {
435 Set<String> unresolvedReferences = new TreeSet<String>(referred
436 .keySet());
437 unresolvedReferences.removeAll(mimports.keySet());
438 unresolvedReferences.removeAll(contained.keySet());
439
440 // Remove any java.** packages.
441 for (Iterator<String> p = unresolvedReferences.iterator(); p.hasNext();) {
442 String pack = p.next();
443 if (pack.startsWith("java.") || ignore.containsKey(pack))
444 p.remove();
445 else {
446 // Remove any dynamic imports
447 if (isDynamicImport(pack))
448 p.remove();
449 }
450 }
451
452 if (!unresolvedReferences.isEmpty()) {
453 // Now we want to know the
454 // classes that are the culprits
455 Set<String> culprits = new HashSet<String>();
456 for (Clazz clazz : classSpace.values()) {
457 if (hasOverlap(unresolvedReferences, clazz.imports.keySet()))
458 culprits.add(clazz.getPath());
459 }
460
461 error("Unresolved references to " + unresolvedReferences
462 + " by class(es) on the Bundle-Classpath" + bundleClasspath
463 + ": " + culprits);
464 }
465 }
466
467 /**
468 * @param p
469 * @param pack
470 */
471 private boolean isDynamicImport(String pack) {
472 for (String pattern : mdynimports.keySet()) {
473 // Wildcard?
474 if (pattern.equals("*"))
475 return true; // All packages can be dynamically imported
476
477 if (pattern.endsWith(".*")) {
478 pattern = pattern.substring(0, pattern.length() - 2);
479 if (pack.startsWith(pattern)
480 && (pack.length() == pattern.length() || pack
481 .charAt(pattern.length()) == '.'))
482 return true;
483 } else {
484 if (pack.equals(pattern))
485 return true;
486 }
487 }
488 return false;
489 }
490
491 private boolean hasOverlap(Set<?> a, Set<?> b) {
492 for (Iterator<?> i = a.iterator(); i.hasNext();) {
493 if (b.contains(i.next()))
494 return true;
495 }
496 return false;
497 }
498
499 public void verify() throws IOException {
500 if (classSpace == null)
501 classSpace = analyzeBundleClasspath(dot,
502 parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH)),
503 contained, referred, uses);
504 verifyManifestFirst();
505 verifyActivator();
506 verifyComponent();
507 verifyNative();
508 verifyInvalidExports();
509 verifyInvalidImports();
510 verifyUnresolvedReferences();
511 verifySymbolicName();
512 verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
513 verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
514 verifyHeader("Bundle-Version", VERSION, true);
515 verifyListHeader("Bundle-Classpath", FILE, false);
516 verifyDynamicImportPackage();
517 verifyBundleClasspath();
518 if (usesRequire) {
519 if (!getErrors().isEmpty()) {
520 getWarnings()
521 .add(
522 0,
523 "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
524 }
525 }
526 }
527
528 public void verifyBundleClasspath() {
529 Map<String, Map<String, String>> bcp = parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH));
530 if (bcp.isEmpty() || bcp.containsKey("."))
531 return;
532
533 for (String path : dot.getResources().keySet()) {
534 if (path.endsWith(".class")) {
535 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?");
536 return;
537 }
538 }
539 }
540
541 /**
542 * <pre>
543 * DynamicImport-Package ::= dynamic-description
544 * ( ',' dynamic-description )*
545 *
546 * dynamic-description::= wildcard-names ( ';' parameter )*
547 * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
548 * wildcard-name ::= package-name
549 * | ( package-name '.*' ) // See 1.4.2
550 * | '*'
551 * </pre>
552 */
553 private void verifyDynamicImportPackage() {
554 verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
555 String dynamicImportPackage = getHeader("DynamicImport-Package");
556 if (dynamicImportPackage == null)
557 return;
558
559 Map<String, Map<String, String>> map = parseHeader(dynamicImportPackage);
560 for (String name : map.keySet()) {
561 name = name.trim();
562 if (!verify(name, WILDCARDPACKAGE))
563 error("DynamicImport-Package header contains an invalid package name: "
564 + name);
565
566 Map<String, String> sub = map.get(name);
567 if (r3 && sub.size() != 0) {
568 error("DynamicPackage-Import has attributes on import: "
569 + name
570 + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
571 }
572 }
573 }
574
575 private void verifyManifestFirst() {
576 if (!dot.manifestFirst) {
577 error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
578 }
579 }
580
581 private void verifySymbolicName() {
582 Map<String, Map<String, String>> bsn = parseHeader(getHeader(Analyzer.BUNDLE_SYMBOLICNAME));
583 if (!bsn.isEmpty()) {
584 if (bsn.size() > 1)
585 error("More than one BSN specified " + bsn);
586
587 String name = (String) bsn.keySet().iterator().next();
588 if (!SYMBOLICNAME.matcher(name).matches()) {
589 error("Symbolic Name has invalid format: " + name);
590 }
591 }
592 }
593
594 /**
595 * <pre>
596 * filter ::= ’(’ filter-comp ’)’
597 * filter-comp ::= and | or | not | operation
598 * and ::= ’&amp;’ filter-list
599 * or ::= ’|’ filter-list
600 * not ::= ’!’ filter
601 * filter-list ::= filter | filter filter-list
602 * operation ::= simple | present | substring
603 * simple ::= attr filter-type value
604 * filter-type ::= equal | approx | greater | less
605 * equal ::= ’=’
606 * approx ::= ’&tilde;=’
607 * greater ::= ’&gt;=’
608 * less ::= ’&lt;=’
609 * present ::= attr ’=*’
610 * substring ::= attr ’=’ initial any final
611 * inital ::= () | value
612 * any ::= ’*’ star-value
613 * star-value ::= () | value ’*’ star-value
614 * final ::= () | value
615 * value ::= &lt;see text&gt;
616 * </pre>
617 *
618 * @param expr
619 * @param index
620 * @return
621 */
622
623 int verifyFilter(String expr, int index) {
624 try {
625 while (Character.isWhitespace(expr.charAt(index)))
626 index++;
627
628 if (expr.charAt(index) != '(')
629 throw new IllegalArgumentException(
630 "Filter mismatch: expected ( at position " + index
631 + " : " + expr);
632
633 index++;
634 while (Character.isWhitespace(expr.charAt(index)))
635 index++;
636
637 switch (expr.charAt(index)) {
638 case '!':
639 case '&':
640 case '|':
641 return verifyFilterSubExpression(expr, index) + 1;
642
643 default:
644 return verifyFilterOperation(expr, index) + 1;
645 }
646 } catch (IndexOutOfBoundsException e) {
647 throw new IllegalArgumentException(
648 "Filter mismatch: early EOF from " + index);
649 }
650 }
651
652 private int verifyFilterOperation(String expr, int index) {
653 StringBuffer sb = new StringBuffer();
654 while ("=><~()".indexOf(expr.charAt(index)) < 0) {
655 sb.append(expr.charAt(index++));
656 }
657 String attr = sb.toString().trim();
658 if (attr.length() == 0)
659 throw new IllegalArgumentException(
660 "Filter mismatch: attr at index " + index + " is 0");
661 sb = new StringBuffer();
662 while ("=><~".indexOf(expr.charAt(index)) >= 0) {
663 sb.append(expr.charAt(index++));
664 }
665 String operator = sb.toString();
666 if (!verify(operator, FILTEROP))
667 throw new IllegalArgumentException(
668 "Filter error, illegal operator " + operator + " at index "
669 + index);
670
671 sb = new StringBuffer();
672 while (")".indexOf(expr.charAt(index)) < 0) {
673 switch (expr.charAt(index)) {
674 case '\\':
675 if (expr.charAt(index + 1) == '*'
676 || expr.charAt(index + 1) == ')')
677 index++;
678 else
679 throw new IllegalArgumentException(
680 "Filter error, illegal use of backslash at index "
681 + index
682 + ". Backslash may only be used before * or (");
683 }
684 sb.append(expr.charAt(index++));
685 }
686 return index;
687 }
688
689 private int verifyFilterSubExpression(String expr, int index) {
690 do {
691 index = verifyFilter(expr, index + 1);
692 while (Character.isWhitespace(expr.charAt(index)))
693 index++;
694 if (expr.charAt(index) != ')')
695 throw new IllegalArgumentException(
696 "Filter mismatch: expected ) at position " + index
697 + " : " + expr);
698 index++;
699 } while (expr.charAt(index) == '(');
700 return index;
701 }
702
703 private String getHeader(String string) {
704 return main.getValue(string);
705 }
706
707 @SuppressWarnings("unchecked")
708 private boolean verifyHeader(String name, Pattern regex, boolean error) {
709 String value = manifest.getMainAttributes().getValue(name);
710 if (value == null)
711 return false;
712
713 QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
714 for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
715 if (!verify((String) i.next(), regex)) {
716 String msg = "Invalid value for " + name + ", " + value
717 + " does not match " + regex.pattern();
718 if (error)
719 error(msg);
720 else
721 warning(msg);
722 }
723 }
724 return true;
725 }
726
727 private boolean verify(String value, Pattern regex) {
728 return regex.matcher(value).matches();
729 }
730
731 private boolean verifyListHeader(String name, Pattern regex, boolean error) {
732 String value = manifest.getMainAttributes().getValue(name);
733 if (value == null)
734 return false;
735
736 Map<String, Map<String, String>> map = parseHeader(value);
737 for (String header : map.keySet()) {
738 if (!regex.matcher(header).matches()) {
739 String msg = "Invalid value for " + name + ", " + value
740 + " does not match " + regex.pattern();
741 if (error)
742 error(msg);
743 else
744 warning(msg);
745 }
746 }
747 return true;
748 }
749
750 public String getProperty(String key, String deflt) {
751 if (properties == null)
752 return deflt;
753 return properties.getProperty(key, deflt);
754 }
755
756 public void setClassSpace(Map<String, Clazz> classspace,
757 Map<String, Map<String, String>> contained,
758 Map<String, Map<String, String>> referred,
759 Map<String, Set<String>> uses) {
760 this.classSpace = classspace;
761 this.contained = contained;
762 this.referred = referred;
763 this.uses = uses;
764 }
765
766 public static boolean isVersion(String version) {
767 return VERSION.matcher(version).matches();
768 }
769
770 public static boolean isIdentifier(String value) {
771 if ( value.length() < 1 )
772 return false;
773
774 if ( !Character.isJavaIdentifierStart(value.charAt(0)))
775 return false;
776
777 for ( int i = 1; i<value.length(); i++ ) {
778 if ( !Character.isJavaIdentifierPart(value.charAt(i)))
779 return false;
780 }
781 return true;
782 }
783
784 public static boolean isMember(String value, String[] matches) {
785 for ( String match : matches ) {
786 if ( match.equals(value) )
787 return true;
788 }
789 return false;
790 }
791
792}