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