blob: 09c494f346505fef04a6813024a45d5584338f6c [file] [log] [blame]
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochbb014372012-06-07 21:57:32 +00002
3/**
4 * This class can calculate the required headers for a (potential) JAR file. It
5 * analyzes a directory or JAR for the packages that are contained and that are
6 * referred to by the bytecodes. The user can the use regular expressions to
7 * define the attributes and directives. The matching is not fully regex for
8 * convenience. A * and ? get a . prefixed and dots are escaped.
9 *
10 * <pre>
11 * *;auto=true any
12 * org.acme.*;auto=true org.acme.xyz
13 * org.[abc]*;auto=true org.acme.xyz
14 * </pre>
15 *
16 * Additional, the package instruction can start with a '=' or a '!'. The '!'
17 * indicates negation. Any matching package is removed. The '=' is literal, the
18 * expression will be copied verbatim and no matching will take place.
19 *
20 * Any headers in the given properties are used in the output properties.
21 */
22import static aQute.libg.generics.Create.*;
23
24import java.io.*;
25import java.net.*;
26import java.util.*;
27import java.util.Map.Entry;
28import java.util.jar.*;
29import java.util.jar.Attributes.Name;
30import java.util.regex.*;
31
32import aQute.bnd.annotation.*;
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000033import aQute.bnd.header.*;
34import aQute.bnd.osgi.Descriptors.Descriptor;
35import aQute.bnd.osgi.Descriptors.PackageRef;
36import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochbb014372012-06-07 21:57:32 +000037import aQute.bnd.service.*;
38import aQute.lib.base64.*;
39import aQute.lib.collections.*;
40import aQute.lib.filter.*;
41import aQute.lib.hex.*;
42import aQute.lib.io.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000043import aQute.libg.cryptography.*;
44import aQute.libg.generics.*;
Stuart McCulloch2286f232012-06-15 13:27:53 +000045import aQute.libg.reporter.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000046
47public class Analyzer extends Processor {
48 private final SortedSet<Clazz.JAVA> ees = new TreeSet<Clazz.JAVA>();
Stuart McCullochd4826102012-06-26 16:34:24 +000049 static Properties bndInfo;
Stuart McCullochbb014372012-06-07 21:57:32 +000050
51 // Bundle parameters
52 private Jar dot;
53 private final Packages contained = new Packages();
54 private final Packages referred = new Packages();
55 private Packages exports;
56 private Packages imports;
57 private TypeRef activator;
58
59 // Global parameters
Stuart McCulloch2286f232012-06-15 13:27:53 +000060 private final MultiMap<PackageRef,PackageRef> uses = new MultiMap<PackageRef,PackageRef>(
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +000061 PackageRef.class, PackageRef.class,
62 true);
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000063 private final MultiMap<PackageRef,PackageRef> apiUses = new MultiMap<PackageRef,PackageRef>(
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +000064 PackageRef.class, PackageRef.class,
65 true);
Stuart McCullochbb014372012-06-07 21:57:32 +000066 private final Packages classpathExports = new Packages();
67 private final Descriptors descriptors = new Descriptors();
68 private final List<Jar> classpath = list();
Stuart McCulloch2286f232012-06-15 13:27:53 +000069 private final Map<TypeRef,Clazz> classspace = map();
70 private final Map<TypeRef,Clazz> importedClassesCache = map();
Stuart McCullochbb014372012-06-07 21:57:32 +000071 private boolean analyzed = false;
72 private boolean diagnostics = false;
73 private boolean inited = false;
Stuart McCulloch2286f232012-06-15 13:27:53 +000074 final protected AnalyzerMessages msgs = ReporterMessages.base(this,
75 AnalyzerMessages.class);
Stuart McCullochbb014372012-06-07 21:57:32 +000076
77 public Analyzer(Processor parent) {
78 super(parent);
79 }
80
Stuart McCulloch2286f232012-06-15 13:27:53 +000081 public Analyzer() {}
Stuart McCullochbb014372012-06-07 21:57:32 +000082
83 /**
84 * Specifically for Maven
85 *
Stuart McCulloch2286f232012-06-15 13:27:53 +000086 * @param properties
87 * the properties
Stuart McCullochbb014372012-06-07 21:57:32 +000088 */
89
90 public static Properties getManifest(File dirOrJar) throws Exception {
91 Analyzer analyzer = new Analyzer();
92 try {
93 analyzer.setJar(dirOrJar);
94 Properties properties = new Properties();
95 properties.put(IMPORT_PACKAGE, "*");
96 properties.put(EXPORT_PACKAGE, "*");
97 analyzer.setProperties(properties);
98 Manifest m = analyzer.calcManifest();
99 Properties result = new Properties();
100 for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i.hasNext();) {
101 Attributes.Name name = (Attributes.Name) i.next();
102 result.put(name.toString(), m.getMainAttributes().getValue(name));
103 }
104 return result;
105 }
106 finally {
107 analyzer.close();
108 }
109 }
110
111 /**
112 * Calculates the data structures for generating a manifest.
113 *
114 * @throws IOException
115 */
116 public void analyze() throws Exception {
117 if (!analyzed) {
118 analyzed = true;
119 uses.clear();
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000120 apiUses.clear();
Stuart McCullochbb014372012-06-07 21:57:32 +0000121 classspace.clear();
122 classpathExports.clear();
123
124 // Parse all the class in the
125 // the jar according to the OSGi bcp
126 analyzeBundleClasspath();
127
128 //
129 // calculate class versions in use
130 //
131 for (Clazz c : classspace.values()) {
132 ees.add(c.getFormat());
133 }
134
135 //
136 // Get exported packages from the
137 // entries on the classpath
138 //
139
140 for (Jar current : getClasspath()) {
141 getExternalExports(current, classpathExports);
142 for (String dir : current.getDirectories().keySet()) {
143 PackageRef packageRef = getPackageRef(dir);
144 Resource resource = current.getResource(dir + "/packageinfo");
Stuart McCullochd4826102012-06-26 16:34:24 +0000145 getExportVersionsFromPackageInfo(packageRef, resource, classpathExports);
Stuart McCullochbb014372012-06-07 21:57:32 +0000146 }
147 }
148
149 // Handle the bundle activator
150
151 String s = getProperty(BUNDLE_ACTIVATOR);
152 if (s != null) {
153 activator = getTypeRefFromFQN(s);
154 referTo(activator);
Stuart McCullochd4826102012-06-26 16:34:24 +0000155 trace("activator %s %s", s, activator);
Stuart McCullochbb014372012-06-07 21:57:32 +0000156 }
157
158 // Execute any plugins
159 // TODO handle better reanalyze
160 doPlugins();
161
162 Jar extra = getExtra();
163 while (extra != null) {
164 dot.addAll(extra);
165 analyzeJar(extra, "", true);
166 extra = getExtra();
167 }
168
169 referred.keySet().removeAll(contained.keySet());
170
171 //
172 // EXPORTS
173 //
174 {
175 Set<Instruction> unused = Create.set();
176
177 Instructions filter = new Instructions(getExportPackage());
178 filter.append(getExportContents());
179
180 exports = filter(filter, contained, unused);
181
182 if (!unused.isEmpty()) {
183 warning("Unused Export-Package instructions: %s ", unused);
184 }
185
186 // See what information we can find to augment the
187 // exports. I.e. look on the classpath
188 augmentExports(exports);
189 }
190
191 //
192 // IMPORTS
193 // Imports MUST come after exports because we use information from
194 // the exports
195 //
196 {
197 // Add all exports that do not have an -noimport: directive
198 // to the imports.
199 Packages referredAndExported = new Packages(referred);
200 referredAndExported.putAll(doExportsToImports(exports));
201
202 removeDynamicImports(referredAndExported);
203
204 // Remove any Java references ... where are the closures???
205 for (Iterator<PackageRef> i = referredAndExported.keySet().iterator(); i.hasNext();) {
206 if (i.next().isJava())
207 i.remove();
208 }
209
210 Set<Instruction> unused = Create.set();
211 String h = getProperty(IMPORT_PACKAGE);
212 if (h == null) // If not set use a default
213 h = "*";
214
215 if (isPedantic() && h.trim().length() == 0)
216 warning("Empty Import-Package header");
217
218 Instructions filter = new Instructions(h);
219 imports = filter(filter, referredAndExported, unused);
220 if (!unused.isEmpty()) {
221 // We ignore the end wildcard catch
222 if (!(unused.size() == 1 && unused.iterator().next().toString().equals("*")))
223 warning("Unused Import-Package instructions: %s ", unused);
224 }
225
226 // See what information we can find to augment the
227 // imports. I.e. look in the exports
228 augmentImports(imports, exports);
229 }
230
231 //
232 // USES
233 //
234 // Add the uses clause to the exports
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000235
236 boolean api = isTrue(getProperty(EXPERIMENTS)) || true; // brave,
237 // lets see
238
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000239 doUses(exports, api ? apiUses : uses, imports);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000240
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000241 //
242 // Verify that no exported package has a reference to a private
243 // package
244 // This can cause a lot of harm.
245 // TODO restrict the check to public API only, but even then
246 // exported packages
247 // should preferably not refer to private packages.
248 //
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000249 Set<PackageRef> privatePackages = getPrivates();
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000250
251 // References to java are not imported so they would show up as
252 // private
253 // packages, lets kill them as well.
254
255 for (Iterator<PackageRef> p = privatePackages.iterator(); p.hasNext();)
256 if (p.next().isJava())
257 p.remove();
258
259 for (PackageRef exported : exports.keySet()) {
260 List<PackageRef> used = uses.get(exported);
261 if (used != null) {
262 Set<PackageRef> privateReferences = new HashSet<PackageRef>(apiUses.get(exported));
263 privateReferences.retainAll(privatePackages);
264 if (!privateReferences.isEmpty())
265 msgs.Export_Has_PrivateReferences_(exported, privateReferences.size(), privateReferences);
266 }
267 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000268
269 //
270 // Checks
271 //
272 if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
273 error("The default package '.' is not permitted by the Import-Package syntax. \n"
274 + " This can be caused by compile errors in Eclipse because Eclipse creates \n"
275 + "valid class files regardless of compile errors.\n"
276 + "The following package(s) import from the default package "
277 + uses.transpose().get(Descriptors.DEFAULT_PACKAGE));
278 }
279
280 }
281 }
282
283 /**
284 * Discussed with BJ and decided to kill the .
285 *
286 * @param referredAndExported
287 */
288 void removeDynamicImports(Packages referredAndExported) {
289
290 // // Remove any matching a dynamic import package instruction
291 // Instructions dynamicImports = new
292 // Instructions(getDynamicImportPackage());
293 // Collection<PackageRef> dynamic = dynamicImports.select(
294 // referredAndExported.keySet(), false);
295 // referredAndExported.keySet().removeAll(dynamic);
296 }
297
298 protected Jar getExtra() throws Exception {
299 return null;
300 }
301
302 /**
303 *
304 */
305 void doPlugins() {
306 for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
307 try {
308 boolean reanalyze = plugin.analyzeJar(this);
309 if (reanalyze) {
310 classspace.clear();
311 analyzeBundleClasspath();
312 }
313 }
314 catch (Exception e) {
315 error("Analyzer Plugin %s failed %s", plugin, e);
316 }
317 }
318 }
319
320 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000321 * @return
322 */
323 boolean isResourceOnly() {
324 return isTrue(getProperty(RESOURCEONLY));
325 }
326
327 /**
328 * One of the main workhorses of this class. This will analyze the current
329 * setp and calculate a new manifest according to this setup. This method
330 * will also set the manifest on the main jar dot
331 *
332 * @return
333 * @throws IOException
334 */
335 public Manifest calcManifest() throws Exception {
336 analyze();
337 Manifest manifest = new Manifest();
338 Attributes main = manifest.getMainAttributes();
339
340 main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
341 main.putValue(BUNDLE_MANIFESTVERSION, "2");
342
343 boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
344
345 if (!noExtraHeaders) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000346 main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
347 + ")");
Stuart McCullochbb014372012-06-07 21:57:32 +0000348 main.putValue(TOOL, "Bnd-" + getBndVersion());
349 main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
350 }
351
352 String exportHeader = printClauses(exports, true);
353
354 if (exportHeader.length() > 0)
355 main.putValue(EXPORT_PACKAGE, exportHeader);
356 else
357 main.remove(EXPORT_PACKAGE);
358
359 // Remove all the Java packages from the imports
360 if (!imports.isEmpty()) {
361 main.putValue(IMPORT_PACKAGE, printClauses(imports));
Stuart McCulloch2286f232012-06-15 13:27:53 +0000362 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000363 main.remove(IMPORT_PACKAGE);
364 }
365
366 Packages temp = new Packages(contained);
367 temp.keySet().removeAll(exports.keySet());
368
369 if (!temp.isEmpty())
370 main.putValue(PRIVATE_PACKAGE, printClauses(temp));
371 else
372 main.remove(PRIVATE_PACKAGE);
373
374 Parameters bcp = getBundleClasspath();
375 if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
376 main.remove(BUNDLE_CLASSPATH);
377 else
378 main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
379
380 doNamesection(dot, manifest);
381
382 for (Enumeration< ? > h = getProperties().propertyNames(); h.hasMoreElements();) {
383 String header = (String) h.nextElement();
384 if (header.trim().length() == 0) {
385 warning("Empty property set with value: " + getProperties().getProperty(header));
386 continue;
387 }
388
389 if (isMissingPlugin(header.trim())) {
390 error("Missing plugin for command %s", header);
391 }
392 if (!Character.isUpperCase(header.charAt(0))) {
393 if (header.charAt(0) == '@')
394 doNameSection(manifest, header);
395 continue;
396 }
397
Stuart McCulloch2286f232012-06-15 13:27:53 +0000398 if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE) || header.equals(IMPORT_PACKAGE))
Stuart McCullochbb014372012-06-07 21:57:32 +0000399 continue;
400
401 if (header.equalsIgnoreCase("Name")) {
402 error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
403 continue;
404 }
405
406 if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
407 String value = getProperty(header);
408 if (value != null && main.getValue(header) == null) {
409 if (value.trim().length() == 0)
410 main.remove(header);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000411 else if (value.trim().equals(EMPTY_HEADER))
412 main.putValue(header, "");
Stuart McCullochbb014372012-06-07 21:57:32 +0000413 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000414 main.putValue(header, value);
Stuart McCullochbb014372012-06-07 21:57:32 +0000415 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000416 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000417 // TODO should we report?
418 }
419 }
420
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000421 // Copy old values into new manifest, when they
422 // exist in the old one, but not in the new one
423 merge(manifest, dot.getManifest());
424
Stuart McCullochbb014372012-06-07 21:57:32 +0000425 //
426 // Calculate the bundle symbolic name if it is
427 // not set.
428 // 1. set
429 // 2. name of properties file (must be != bnd.bnd)
430 // 3. name of directory, which is usualy project name
431 //
432 String bsn = getBsn();
433 if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
434 main.putValue(BUNDLE_SYMBOLICNAME, bsn);
435 }
436
437 //
438 // Use the same name for the bundle name as BSN when
439 // the bundle name is not set
440 //
441 if (main.getValue(BUNDLE_NAME) == null) {
442 main.putValue(BUNDLE_NAME, bsn);
443 }
444
445 if (main.getValue(BUNDLE_VERSION) == null)
446 main.putValue(BUNDLE_VERSION, "0");
447
Stuart McCullochbb014372012-06-07 21:57:32 +0000448 // Remove all the headers mentioned in -removeheaders
449 Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
450 Collection<Object> result = instructions.select(main.keySet(), false);
451 main.keySet().removeAll(result);
452
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000453 // We should not set the manifest here, this is in general done
454 // by the caller.
455 // dot.setManifest(manifest);
Stuart McCullochbb014372012-06-07 21:57:32 +0000456 return manifest;
457 }
458
459 /**
460 * Parse the namesection as instructions and then match them against the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000461 * current set of resources For example:
Stuart McCullochbb014372012-06-07 21:57:32 +0000462 *
463 * <pre>
464 * -namesection: *;baz=true, abc/def/bar/X.class=3
465 * </pre>
466 *
467 * The raw value of {@link Constants#NAMESECTION} is used but the values of
468 * the attributes are replaced where @ is set to the resource name. This
469 * allows macro to operate on the resource
Stuart McCullochbb014372012-06-07 21:57:32 +0000470 */
471
472 private void doNamesection(Jar dot, Manifest manifest) {
473
474 Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
475 Instructions instructions = new Instructions(namesection);
476 Set<String> resources = new HashSet<String>(dot.getResources().keySet());
477
478 //
479 // For each instruction, iterator over the resources and filter
480 // them. If a resource matches, it must be removed even if the
481 // instruction is negative. If positive, add a name section
482 // to the manifest for the given resource name. Then add all
483 // attributes from the instruction to that name section.
484 //
Stuart McCulloch2286f232012-06-15 13:27:53 +0000485 for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000486 boolean matched = false;
487
488 // For each instruction
489
490 for (Iterator<String> i = resources.iterator(); i.hasNext();) {
491 String path = i.next();
492 // For each resource
493
494 if (instr.getKey().matches(path)) {
495
496 // Instruction matches the resource
497
498 matched = true;
499 if (!instr.getKey().isNegated()) {
500
501 // Positive match, add the attributes
502
503 Attributes attrs = manifest.getAttributes(path);
504 if (attrs == null) {
505 attrs = new Attributes();
506 manifest.getEntries().put(path, attrs);
507 }
508
509 //
510 // Add all the properties from the instruction to the
511 // name section
512 //
513
Stuart McCulloch2286f232012-06-15 13:27:53 +0000514 for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000515 setProperty("@", path);
516 try {
517 String processed = getReplacer().process(property.getValue());
518 attrs.putValue(property.getKey(), processed);
519 }
520 finally {
521 unsetProperty("@");
522 }
523 }
524 }
525 i.remove();
526 }
527 }
528
529 if (!matched && resources.size() > 0)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000530 warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
Stuart McCullochbb014372012-06-07 21:57:32 +0000531 }
532
533 }
534
535 /**
536 * This method is called when the header starts with a @, signifying a name
537 * section header. The name part is defined by replacing all the @ signs to
538 * a /, removing the first and the last, and using the last part as header
539 * name:
540 *
541 * <pre>
542 * &#064;org@osgi@service@event@Implementation-Title
543 * </pre>
544 *
545 * This will be the header Implementation-Title in the
546 * org/osgi/service/event named section.
547 *
548 * @param manifest
549 * @param header
550 */
551 private void doNameSection(Manifest manifest, String header) {
552 String path = header.replace('@', '/');
553 int n = path.lastIndexOf('/');
554 // Must succeed because we start with @
555 String name = path.substring(n + 1);
556 // Skip first /
557 path = path.substring(1, n);
558 if (name.length() != 0 && path.length() != 0) {
559 Attributes attrs = manifest.getAttributes(path);
560 if (attrs == null) {
561 attrs = new Attributes();
562 manifest.getEntries().put(path, attrs);
563 }
564 attrs.putValue(name, getProperty(header));
Stuart McCulloch2286f232012-06-15 13:27:53 +0000565 } else {
566 warning("Invalid header (starts with @ but does not seem to be for the Name section): %s", header);
Stuart McCullochbb014372012-06-07 21:57:32 +0000567 }
568 }
569
570 /**
571 * Clear the key part of a header. I.e. remove everything from the first ';'
572 *
573 * @param value
574 * @return
575 */
576 public String getBsn() {
577 String value = getProperty(BUNDLE_SYMBOLICNAME);
578 if (value == null) {
579 if (getPropertiesFile() != null)
580 value = getPropertiesFile().getName();
581
582 String projectName = getBase().getName();
583 if (value == null || value.equals("bnd.bnd")) {
584 value = projectName;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000585 } else if (value.endsWith(".bnd")) {
586 value = value.substring(0, value.length() - 4);
587 if (!value.startsWith(getBase().getName()))
588 value = projectName + "." + value;
Stuart McCullochbb014372012-06-07 21:57:32 +0000589 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000590 }
591
592 if (value == null)
593 return "untitled";
594
595 int n = value.indexOf(';');
596 if (n > 0)
597 value = value.substring(0, n);
598 return value.trim();
599 }
600
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000601 public String _bsn(@SuppressWarnings("unused")
602 String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000603 return getBsn();
604 }
605
606 /**
607 * Calculate an export header solely based on the contents of a JAR file
608 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000609 * @param bundle
610 * The jar file to analyze
Stuart McCullochbb014372012-06-07 21:57:32 +0000611 * @return
612 */
613 public String calculateExportsFromContents(Jar bundle) {
614 String ddel = "";
615 StringBuilder sb = new StringBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000616 Map<String,Map<String,Resource>> map = bundle.getDirectories();
Stuart McCullochbb014372012-06-07 21:57:32 +0000617 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
618 String directory = i.next();
619 if (directory.equals("META-INF") || directory.startsWith("META-INF/"))
620 continue;
621 if (directory.equals("OSGI-OPT") || directory.startsWith("OSGI-OPT/"))
622 continue;
623 if (directory.equals("/"))
624 continue;
625
626 if (directory.endsWith("/"))
627 directory = directory.substring(0, directory.length() - 1);
628
629 directory = directory.replace('/', '.');
630 sb.append(ddel);
631 sb.append(directory);
632 ddel = ",";
633 }
634 return sb.toString();
635 }
636
637 public Packages getContained() {
638 return contained;
639 }
640
641 public Packages getExports() {
642 return exports;
643 }
644
645 public Packages getImports() {
646 return imports;
647 }
648
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000649 public Set<PackageRef> getPrivates() {
650 HashSet<PackageRef> privates = new HashSet<PackageRef>(contained.keySet());
651 privates.removeAll(exports.keySet());
652 privates.removeAll(imports.keySet());
653 return privates;
654 }
655
Stuart McCullochbb014372012-06-07 21:57:32 +0000656 public Jar getJar() {
657 return dot;
658 }
659
660 public Packages getReferred() {
661 return referred;
662 }
663
664 /**
665 * Return the set of unreachable code depending on exports and the bundle
666 * activator.
667 *
668 * @return
669 */
670 public Set<PackageRef> getUnreachable() {
671 Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
672 for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
673 PackageRef packageRef = r.next();
674 removeTransitive(packageRef, unreachable);
675 }
676 if (activator != null) {
677 removeTransitive(activator.getPackageRef(), unreachable);
678 }
679 return unreachable;
680 }
681
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000682 public Map<PackageRef,List<PackageRef>> getUses() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000683 return uses;
684 }
685
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000686 public Map<PackageRef,List<PackageRef>> getAPIUses() {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000687 return apiUses;
688 }
689
Stuart McCullochbb014372012-06-07 21:57:32 +0000690 /**
691 * Get the version for this bnd
692 *
693 * @return version or unknown.
694 */
695 public String getBndVersion() {
Stuart McCullochd4826102012-06-26 16:34:24 +0000696 return getBndInfo("version", "<unknown>");
Stuart McCullochbb014372012-06-07 21:57:32 +0000697 }
698
699 public long getBndLastModified() {
Stuart McCullochd4826102012-06-26 16:34:24 +0000700 String time = getBndInfo("lastmodified", "0");
Stuart McCullochbb014372012-06-07 21:57:32 +0000701 try {
702 return Long.parseLong(time);
703 }
704 catch (Exception e) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000705 // Ignore
Stuart McCullochbb014372012-06-07 21:57:32 +0000706 }
707 return 0;
708 }
709
710 public String getBndInfo(String key, String defaultValue) {
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000711 if (bndInfo == null) {
712 try {
Stuart McCullochd4826102012-06-26 16:34:24 +0000713 Properties bndInfoLocal = new Properties();
714 URL url = Analyzer.class.getResource("bnd.info");
715 if (url != null) {
716 InputStream in = url.openStream();
717 try {
718 bndInfoLocal.load(in);
719 }
720 finally {
721 in.close();
722 }
723 }
724 bndInfo = bndInfoLocal;
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000725 }
726 catch (Exception e) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000727 e.printStackTrace();
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000728 return defaultValue;
Stuart McCullochbb014372012-06-07 21:57:32 +0000729 }
730 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000731 String value = bndInfo.getProperty(key);
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000732 if (value == null)
733 return defaultValue;
734 return value;
Stuart McCullochbb014372012-06-07 21:57:32 +0000735 }
736
737 /**
738 * Merge the existing manifest with the instructions but do not override
739 * existing properties.
740 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000741 * @param manifest
742 * The manifest to merge with
Stuart McCullochbb014372012-06-07 21:57:32 +0000743 * @throws IOException
744 */
745 public void mergeManifest(Manifest manifest) throws IOException {
746 if (manifest != null) {
747 Attributes attributes = manifest.getMainAttributes();
748 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
749 Name name = (Name) i.next();
750 String key = name.toString();
751 // Dont want instructions
752 if (key.startsWith("-"))
753 continue;
754
755 if (getProperty(key) == null)
756 setProperty(key, attributes.getValue(name));
757 }
758 }
759 }
760
761 public void setBase(File file) {
762 super.setBase(file);
763 getProperties().put("project.dir", getBase().getAbsolutePath());
764 }
765
766 /**
767 * Set the classpath for this analyzer by file.
768 *
769 * @param classpath
770 * @throws IOException
771 */
772 public void setClasspath(File[] classpath) throws IOException {
773 List<Jar> list = new ArrayList<Jar>();
774 for (int i = 0; i < classpath.length; i++) {
775 if (classpath[i].exists()) {
776 Jar current = new Jar(classpath[i]);
777 list.add(current);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000778 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000779 error("Missing file on classpath: %s", classpath[i]);
780 }
781 }
782 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
783 addClasspath(i.next());
784 }
785 }
786
787 public void setClasspath(Jar[] classpath) {
788 for (int i = 0; i < classpath.length; i++) {
789 addClasspath(classpath[i]);
790 }
791 }
792
793 public void setClasspath(String[] classpath) {
794 for (int i = 0; i < classpath.length; i++) {
795 Jar jar = getJarFromName(classpath[i], " setting classpath");
796 if (jar != null)
797 addClasspath(jar);
798 }
799 }
800
801 /**
802 * Set the JAR file we are going to work in. This will read the JAR in
803 * memory.
804 *
805 * @param jar
806 * @return
807 * @throws IOException
808 */
809 public Jar setJar(File jar) throws IOException {
810 Jar jarx = new Jar(jar);
811 addClose(jarx);
812 return setJar(jarx);
813 }
814
815 /**
816 * Set the JAR directly we are going to work on.
817 *
818 * @param jar
819 * @return
820 */
821 public Jar setJar(Jar jar) {
822 if (dot != null)
823 removeClose(dot);
824
825 this.dot = jar;
826 if (dot != null)
827 addClose(dot);
828
829 return jar;
830 }
831
832 protected void begin() {
833 if (inited == false) {
834 inited = true;
835 super.begin();
836
837 updateModified(getBndLastModified(), "bnd last modified");
838 verifyManifestHeadersCase(getProperties());
839
840 }
841 }
842
843 /**
844 * Try to get a Jar from a file name/path or a url, or in last resort from
845 * the classpath name part of their files.
846 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000847 * @param name
848 * URL or filename relative to the base
849 * @param from
850 * Message identifying the caller for errors
Stuart McCullochbb014372012-06-07 21:57:32 +0000851 * @return null or a Jar with the contents for the name
852 */
853 Jar getJarFromName(String name, String from) {
854 File file = new File(name);
855 if (!file.isAbsolute())
856 file = new File(getBase(), name);
857
858 if (file.exists())
859 try {
860 Jar jar = new Jar(file);
861 addClose(jar);
862 return jar;
863 }
864 catch (Exception e) {
865 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
866 }
867 // It is not a file ...
868 try {
869 // Lets try a URL
870 URL url = new URL(name);
871 Jar jar = new Jar(fileName(url.getPath()));
872 addClose(jar);
873 URLConnection connection = url.openConnection();
874 InputStream in = connection.getInputStream();
875 long lastModified = connection.getLastModified();
876 if (lastModified == 0)
877 // We assume the worst :-(
878 lastModified = System.currentTimeMillis();
879 EmbeddedResource.build(jar, in, lastModified);
880 in.close();
881 return jar;
882 }
883 catch (IOException ee) {
884 // Check if we have files on the classpath
885 // that have the right name, allows us to specify those
886 // names instead of the full path.
887 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
888 Jar entry = cp.next();
889 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
890 return entry;
891 }
892 }
893 // error("Can not find jar file for " + from + ": " + name);
894 }
895 return null;
896 }
897
898 private String fileName(String path) {
899 int n = path.lastIndexOf('/');
900 if (n > 0)
901 return path.substring(n + 1);
902 return path;
903 }
904
905 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000906 * @param manifests
907 * @throws Exception
908 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000909 private void merge(Manifest result, Manifest old) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000910 if (old != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000911 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
912 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000913 Attributes.Name name = (Attributes.Name) entry.getKey();
914 String value = (String) entry.getValue();
915 if (name.toString().equalsIgnoreCase("Created-By"))
916 name = new Attributes.Name("Originally-Created-By");
917 if (!result.getMainAttributes().containsKey(name))
918 result.getMainAttributes().put(name, value);
919 }
920
921 // do not overwrite existing entries
Stuart McCulloch2286f232012-06-15 13:27:53 +0000922 Map<String,Attributes> oldEntries = old.getEntries();
923 Map<String,Attributes> newEntries = result.getEntries();
924 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
925 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000926 if (!newEntries.containsKey(entry.getKey())) {
927 newEntries.put(entry.getKey(), entry.getValue());
928 }
929 }
930 }
931 }
932
933 /**
934 * Bnd is case sensitive for the instructions so we better check people are
935 * not using an invalid case. We do allow this to set headers that should
936 * not be processed by us but should be used by the framework.
937 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000938 * @param properties
939 * Properties to verify.
Stuart McCullochbb014372012-06-07 21:57:32 +0000940 */
941
942 void verifyManifestHeadersCase(Properties properties) {
943 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
944 String header = (String) i.next();
945 for (int j = 0; j < headers.length; j++) {
946 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
947 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
948 + header + " and expecting: " + headers[j]);
949 break;
950 }
951 }
952 }
953 }
954
955 /**
956 * We will add all exports to the imports unless there is a -noimport
957 * directive specified on an export. This directive is skipped for the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000958 * manifest. We also remove any version parameter so that augmentImports can
959 * do the version policy. The following method is really tricky and evolved
960 * over time. Coming from the original background of OSGi, it was a weird
961 * idea for me to have a public package that should not be substitutable. I
962 * was so much convinced that this was the right rule that I rücksichtlos
963 * imported them all. Alas, the real world was more subtle than that. It
964 * turns out that it is not a good idea to always import. First, there must
965 * be a need to import, i.e. there must be a contained package that refers
966 * to the exported package for it to make use importing that package.
967 * Second, if an exported package refers to an internal package than it
968 * should not be imported. Additionally, it is necessary to treat the
969 * exports in groups. If an exported package refers to another exported
970 * packages than it must be in the same group. A framework can only
971 * substitute exports for imports for the whole of such a group. WHY?????
972 * Not clear anymore ...
Stuart McCullochbb014372012-06-07 21:57:32 +0000973 */
974 Packages doExportsToImports(Packages exports) {
975
976 // private packages = contained - exported.
977 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
978 privatePackages.removeAll(exports.keySet());
979
980 // private references = ∀ p : private packages | uses(p)
981 Set<PackageRef> privateReferences = newSet();
982 for (PackageRef p : privatePackages) {
983 Collection<PackageRef> uses = this.uses.get(p);
984 if (uses != null)
985 privateReferences.addAll(uses);
986 }
987
988 // Assume we are going to export all exported packages
989 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
990
991 // Remove packages that are not referenced privately
992 toBeImported.retainAll(privateReferences);
993
994 // Not necessary to import anything that is already
995 // imported in the Import-Package statement.
996 // TODO toBeImported.removeAll(imports.keySet());
997
998 // Remove exported packages that are referring to
999 // private packages.
1000 // Each exported package has a uses clause. We just use
1001 // the used packages for each exported package to find out
1002 // if it refers to an internal package.
1003 //
1004
1005 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1006 PackageRef next = i.next();
1007 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1008
1009 for (PackageRef privatePackage : privatePackages) {
1010 if (usedByExportedPackage.contains(privatePackage)) {
1011 i.remove();
1012 break;
1013 }
1014 }
1015 }
1016
1017 // Clean up attributes and generate result map
1018 Packages result = new Packages();
1019 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1020 PackageRef ep = i.next();
1021 Attrs parameters = exports.get(ep);
1022
Stuart McCullochffa8aaf2012-06-17 20:38:35 +00001023 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochbb014372012-06-07 21:57:32 +00001024 if (noimport != null && noimport.equalsIgnoreCase("true"))
1025 continue;
1026
1027 // // we can't substitute when there is no version
1028 // String version = parameters.get(VERSION_ATTRIBUTE);
1029 // if (version == null) {
1030 // if (isPedantic())
1031 // warning(
1032 // "Cannot automatically import exported package %s because it has no version defined",
1033 // ep);
1034 // continue;
1035 // }
1036
1037 parameters = new Attrs();
1038 parameters.remove(VERSION_ATTRIBUTE);
1039 result.put(ep, parameters);
1040 }
1041 return result;
1042 }
1043
1044 public boolean referred(PackageRef packageName) {
1045 // return true;
Stuart McCulloch2286f232012-06-15 13:27:53 +00001046 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001047 if (!contained.getKey().equals(packageName)) {
1048 if (contained.getValue().contains(packageName))
1049 return true;
1050 }
1051 }
1052 return false;
1053 }
1054
1055 /**
Stuart McCullochbb014372012-06-07 21:57:32 +00001056 * @param jar
1057 */
1058 private void getExternalExports(Jar jar, Packages classpathExports) {
1059 try {
1060 Manifest m = jar.getManifest();
1061 if (m != null) {
1062 Domain domain = Domain.domain(m);
1063 Parameters exported = domain.getExportPackage();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001064 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001065 PackageRef ref = getPackageRef(e.getKey());
1066 if (!classpathExports.containsKey(ref)) {
1067 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1068 // jar.getBsn()+"-"+jar.getVersion());
1069
1070 classpathExports.put(ref, e.getValue());
1071 }
1072 }
1073 }
1074 }
1075 catch (Exception e) {
1076 warning("Erroneous Manifest for " + jar + " " + e);
1077 }
1078 }
1079
1080 /**
1081 * Find some more information about imports in manifest and other places. It
1082 * is assumed that the augmentsExports has already copied external attrs
1083 * from the classpathExports.
1084 *
1085 * @throws Exception
1086 */
1087 void augmentImports(Packages imports, Packages exports) throws Exception {
1088 List<PackageRef> noimports = Create.list();
1089 Set<PackageRef> provided = findProvidedPackages();
1090
1091 for (PackageRef packageRef : imports.keySet()) {
1092 String packageName = packageRef.getFQN();
1093
1094 setProperty(CURRENT_PACKAGE, packageName);
1095 try {
1096 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001097 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochbb014372012-06-07 21:57:32 +00001098
1099 String exportVersion = exportAttributes.getVersion();
1100 String importRange = importAttributes.getVersion();
1101
1102 if (exportVersion == null) {
1103 // TODO Should check if the source is from a bundle.
1104
Stuart McCulloch2286f232012-06-15 13:27:53 +00001105 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001106
1107 //
1108 // Version Policy - Import version substitution. We
1109 // calculate the export version and then allow the
1110 // import version attribute to use it in a substitution
1111 // by using a ${@} macro. The export version can
1112 // be defined externally or locally
1113 //
1114
1115 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch2286f232012-06-15 13:27:53 +00001116 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochbb014372012-06-07 21:57:32 +00001117
1118 exportVersion = cleanupVersion(exportVersion);
1119
1120 try {
1121 setProperty("@", exportVersion);
1122
1123 if (importRange != null) {
1124 importRange = cleanupVersion(importRange);
1125 importRange = getReplacer().process(importRange);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001126 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00001127 importRange = getVersionPolicy(provider);
1128
1129 }
1130 finally {
1131 unsetProperty("@");
1132 }
1133 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1134 }
1135
1136 //
1137 // Check if exporter has mandatory attributes
1138 //
1139 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1140 if (mandatory != null) {
1141 String[] attrs = mandatory.split("\\s*,\\s*");
1142 for (int i = 0; i < attrs.length; i++) {
1143 if (!importAttributes.containsKey(attrs[i]))
1144 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1145 }
1146 }
1147
1148 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1149 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1150
1151 fixupAttributes(importAttributes);
1152 removeAttributes(importAttributes);
1153
1154 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1155 if (result == null)
1156 noimports.add(packageRef);
1157 }
1158 finally {
1159 unsetProperty(CURRENT_PACKAGE);
1160 }
1161 }
1162
1163 if (isPedantic() && noimports.size() != 0) {
1164 warning("Imports that lack version ranges: %s", noimports);
1165 }
1166 }
1167
1168 /**
1169 * Find the packages we depend on, where we implement an interface that is a
1170 * Provider Type. These packages, when we import them, must use the provider
1171 * policy.
1172 *
1173 * @throws Exception
1174 */
1175 Set<PackageRef> findProvidedPackages() throws Exception {
1176 Set<PackageRef> providers = Create.set();
1177 Set<TypeRef> cached = Create.set();
1178
1179 for (Clazz c : classspace.values()) {
1180 TypeRef[] interfaces = c.getInterfaces();
1181 if (interfaces != null)
1182 for (TypeRef t : interfaces)
1183 if (cached.contains(t) || isProvider(t)) {
1184 cached.add(t);
1185 providers.add(t.getPackageRef());
1186 }
1187 }
1188 return providers;
1189 }
1190
1191 private boolean isProvider(TypeRef t) throws Exception {
1192 Clazz c = findClass(t);
1193 if (c == null)
1194 return false;
1195
1196 if (c.annotations == null)
1197 return false;
1198
1199 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1200 boolean result = c.annotations.contains(pt);
1201 return result;
1202 }
1203
1204 /**
1205 * Provide any macro substitutions and versions for exported packages.
1206 */
1207
1208 void augmentExports(Packages exports) {
1209 for (PackageRef packageRef : exports.keySet()) {
1210 String packageName = packageRef.getFQN();
1211 setProperty(CURRENT_PACKAGE, packageName);
1212 try {
1213 Attrs attributes = exports.get(packageRef);
1214 Attrs exporterAttributes = classpathExports.get(packageRef);
1215 if (exporterAttributes == null)
1216 continue;
1217
Stuart McCulloch2286f232012-06-15 13:27:53 +00001218 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001219 String key = entry.getKey();
1220 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1221 key = VERSION_ATTRIBUTE;
1222
1223 // dont overwrite and no directives
1224 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1225 attributes.put(key, entry.getValue());
1226 }
1227 }
1228
1229 fixupAttributes(attributes);
1230 removeAttributes(attributes);
1231
1232 }
1233 finally {
1234 unsetProperty(CURRENT_PACKAGE);
1235 }
1236 }
1237 }
1238
1239 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001240 * Fixup Attributes Execute any macros on an export and
Stuart McCullochbb014372012-06-07 21:57:32 +00001241 */
1242
1243 void fixupAttributes(Attrs attributes) {
1244 // Convert any attribute values that have macros.
1245 for (String key : attributes.keySet()) {
1246 String value = attributes.get(key);
1247 if (value.indexOf('$') >= 0) {
1248 value = getReplacer().process(value);
1249 attributes.put(key, value);
1250 }
1251 }
1252
1253 }
1254
1255 /**
1256 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1257 * can add a remove-attribute: directive with a regular expression for
1258 * attributes that need to be removed. We also remove all attributes that
1259 * have a value of !. This allows you to use macros with ${if} to remove
1260 * values.
1261 */
1262
1263 void removeAttributes(Attrs attributes) {
1264 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1265
1266 if (remove != null) {
1267 Instructions removeInstr = new Instructions(remove);
1268 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1269 }
1270
1271 // Remove any ! valued attributes
Stuart McCulloch2286f232012-06-15 13:27:53 +00001272 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001273 String v = i.next().getValue();
1274 if (v.equals("!"))
1275 i.remove();
1276 }
1277 }
1278
1279 /**
1280 * Calculate a version from a version policy.
1281 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001282 * @param version
1283 * The actual exported version
1284 * @param impl
1285 * true for implementations and false for clients
Stuart McCullochbb014372012-06-07 21:57:32 +00001286 */
1287
1288 String calculateVersionRange(String version, boolean impl) {
1289 setProperty("@", version);
1290 try {
1291 return getVersionPolicy(impl);
1292 }
1293 finally {
1294 unsetProperty("@");
1295 }
1296 }
1297
1298 /**
1299 * Add the uses clauses. This method iterates over the exports and cal
1300 *
1301 * @param exports
1302 * @param uses
1303 * @throws MojoExecutionException
1304 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001305 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001306 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1307 return;
1308
1309 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1310 PackageRef packageRef = i.next();
1311 String packageName = packageRef.getFQN();
1312 setProperty(CURRENT_PACKAGE, packageName);
1313 try {
1314 doUses(packageRef, exports, uses, imports);
1315 }
1316 finally {
1317 unsetProperty(CURRENT_PACKAGE);
1318 }
1319
1320 }
1321 }
1322
1323 /**
1324 * @param packageName
1325 * @param exports
1326 * @param uses
1327 * @param imports
1328 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001329 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch2286f232012-06-15 13:27:53 +00001330 Packages imports) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001331 Attrs clause = exports.get(packageRef);
1332
1333 // Check if someone already set the uses: directive
1334 String override = clause.get(USES_DIRECTIVE);
1335 if (override == null)
1336 override = USES_USES;
1337
1338 // Get the used packages
1339 Collection<PackageRef> usedPackages = uses.get(packageRef);
1340
1341 if (usedPackages != null) {
1342
1343 // Only do a uses on exported or imported packages
1344 // and uses should also not contain our own package
1345 // name
1346 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1347 sharedPackages.addAll(imports.keySet());
1348 sharedPackages.addAll(exports.keySet());
1349 sharedPackages.retainAll(usedPackages);
1350 sharedPackages.remove(packageRef);
1351
1352 StringBuilder sb = new StringBuilder();
1353 String del = "";
1354 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1355 PackageRef usedPackage = u.next();
1356 if (!usedPackage.isJava()) {
1357 sb.append(del);
1358 sb.append(usedPackage.getFQN());
1359 del = ",";
1360 }
1361 }
1362 if (override.indexOf('$') >= 0) {
1363 setProperty(CURRENT_USES, sb.toString());
1364 override = getReplacer().process(override);
1365 unsetProperty(CURRENT_USES);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001366 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00001367 // This is for backward compatibility 0.0.287
1368 // can be deprecated over time
Stuart McCulloch2286f232012-06-15 13:27:53 +00001369 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochbb014372012-06-07 21:57:32 +00001370
1371 if (override.endsWith(","))
1372 override = override.substring(0, override.length() - 1);
1373 if (override.startsWith(","))
1374 override = override.substring(1);
1375 if (override.length() > 0) {
1376 clause.put(USES_DIRECTIVE, override);
1377 }
1378 }
1379 }
1380
1381 /**
1382 * Transitively remove all elemens from unreachable through the uses link.
1383 *
1384 * @param name
1385 * @param unreachable
1386 */
1387 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1388 if (!unreachable.contains(name))
1389 return;
1390
1391 unreachable.remove(name);
1392
1393 List<PackageRef> ref = uses.get(name);
1394 if (ref != null) {
1395 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1396 PackageRef element = r.next();
1397 removeTransitive(element, unreachable);
1398 }
1399 }
1400 }
1401
1402 /**
1403 * Helper method to set the package info resource
1404 *
1405 * @param dir
1406 * @param key
1407 * @param value
1408 * @throws Exception
1409 */
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001410 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1411 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001412 if (r == null)
1413 return;
1414
1415 Properties p = new Properties();
Stuart McCullochbb014372012-06-07 21:57:32 +00001416 try {
Stuart McCullochd4826102012-06-26 16:34:24 +00001417 InputStream in = r.openInputStream();
1418 try {
1419 p.load(in);
Stuart McCullochbb014372012-06-07 21:57:32 +00001420 }
Stuart McCullochd4826102012-06-26 16:34:24 +00001421 finally {
1422 in.close();
1423 }
1424 Attrs map = classpathExports.get(packageRef);
1425 if (map == null) {
1426 classpathExports.put(packageRef, map = new Attrs());
1427 }
1428 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1429 String key = t.nextElement();
1430 String value = map.get(key);
1431 if (value == null) {
1432 value = p.getProperty(key);
1433
1434 // Messy, to allow directives we need to
1435 // allow the value to start with a ':' since we cannot
1436 // encode this in a property name
1437
1438 if (value.startsWith(":")) {
1439 key = key + ":";
1440 value = value.substring(1);
1441 }
1442 map.put(key, value);
1443 }
1444 }
1445 }
1446 catch (Exception e) {
1447 msgs.NoSuchFile_(r);
Stuart McCullochbb014372012-06-07 21:57:32 +00001448 }
1449 }
1450
1451 public void close() {
1452 if (diagnostics) {
1453 PrintStream out = System.err;
1454 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1455 out.println("Classpath used");
1456 for (Jar jar : getClasspath()) {
1457 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch2286f232012-06-15 13:27:53 +00001458 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochbb014372012-06-07 21:57:32 +00001459 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch2286f232012-06-15 13:27:53 +00001460 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1461 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1462 Map<String,Resource> dir = entry.getValue();
Stuart McCullochbb014372012-06-07 21:57:32 +00001463 String name = entry.getKey().replace('/', '.');
1464 if (dir != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001465 out.printf(" %-30s %d%n", name, dir.size());
1466 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001467 out.printf(" %-30s <<empty>>%n", name);
1468 }
1469 }
1470 }
1471 }
1472
1473 super.close();
1474 if (dot != null)
1475 dot.close();
1476
1477 if (classpath != null)
1478 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1479 Jar jar = j.next();
1480 jar.close();
1481 }
1482 }
1483
1484 /**
1485 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch2286f232012-06-15 13:27:53 +00001486 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1487 * )? }
Stuart McCullochbb014372012-06-07 21:57:32 +00001488 *
1489 * @param args
1490 * @return
1491 */
1492 public String _findpath(String args[]) {
1493 return findPath("findpath", args, true);
1494 }
1495
1496 public String _findname(String args[]) {
1497 return findPath("findname", args, false);
1498 }
1499
1500 String findPath(String name, String[] args, boolean fullPathName) {
1501 if (args.length > 3) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001502 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1503 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochbb014372012-06-07 21:57:32 +00001504 return null;
1505 }
1506
1507 String regexp = ".*";
1508 String replace = null;
1509
1510 switch (args.length) {
1511 case 3 :
1512 replace = args[2];
1513 //$FALL-THROUGH$
1514 case 2 :
1515 regexp = args[1];
1516 }
1517 StringBuilder sb = new StringBuilder();
1518 String del = "";
1519
1520 Pattern expr = Pattern.compile(regexp);
1521 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1522 String path = e.next();
1523 if (!fullPathName) {
1524 int n = path.lastIndexOf('/');
1525 if (n >= 0) {
1526 path = path.substring(n + 1);
1527 }
1528 }
1529
1530 Matcher m = expr.matcher(path);
1531 if (m.matches()) {
1532 if (replace != null)
1533 path = m.replaceAll(replace);
1534
1535 sb.append(del);
1536 sb.append(path);
1537 del = ", ";
1538 }
1539 }
1540 return sb.toString();
1541 }
1542
Stuart McCulloch2286f232012-06-15 13:27:53 +00001543 public void putAll(Map<String,String> additional, boolean force) {
1544 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1545 Map.Entry<String,String> entry = i.next();
Stuart McCullochbb014372012-06-07 21:57:32 +00001546 if (force || getProperties().get(entry.getKey()) == null)
1547 setProperty(entry.getKey(), entry.getValue());
1548 }
1549 }
1550
1551 boolean firstUse = true;
1552
1553 public List<Jar> getClasspath() {
1554 if (firstUse) {
1555 firstUse = false;
1556 String cp = getProperty(CLASSPATH);
1557 if (cp != null)
1558 for (String s : split(cp)) {
1559 Jar jar = getJarFromName(s, "getting classpath");
1560 if (jar != null)
1561 addClasspath(jar);
1562 else
1563 warning("Cannot find entry on -classpath: %s", s);
1564 }
1565 }
1566 return classpath;
1567 }
1568
1569 public void addClasspath(Jar jar) {
1570 if (isPedantic() && jar.getResources().isEmpty())
1571 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1572
1573 classpath.add(jar);
1574 }
1575
1576 public void addClasspath(Collection< ? > jars) throws IOException {
1577 for (Object jar : jars) {
1578 if (jar instanceof Jar)
1579 addClasspath((Jar) jar);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001580 else if (jar instanceof File)
1581 addClasspath((File) jar);
1582 else if (jar instanceof String)
1583 addClasspath(getFile((String) jar));
Stuart McCullochbb014372012-06-07 21:57:32 +00001584 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001585 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochbb014372012-06-07 21:57:32 +00001586 }
1587 }
1588
1589 public void addClasspath(File cp) throws IOException {
1590 if (!cp.exists())
1591 warning("File on classpath that does not exist: " + cp);
1592 Jar jar = new Jar(cp);
1593 addClose(jar);
1594 classpath.add(jar);
1595 }
1596
1597 public void clear() {
1598 classpath.clear();
1599 }
1600
1601 public Jar getTarget() {
1602 return dot;
1603 }
1604
1605 private void analyzeBundleClasspath() throws Exception {
1606 Parameters bcp = getBundleClasspath();
1607
1608 if (bcp.isEmpty()) {
1609 analyzeJar(dot, "", true);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001610 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001611 boolean okToIncludeDirs = true;
1612
1613 for (String path : bcp.keySet()) {
1614 if (dot.getDirectories().containsKey(path)) {
1615 okToIncludeDirs = false;
1616 break;
1617 }
1618 }
1619
1620 for (String path : bcp.keySet()) {
1621 Attrs info = bcp.get(path);
1622
1623 if (path.equals(".")) {
1624 analyzeJar(dot, "", okToIncludeDirs);
1625 continue;
1626 }
1627 //
1628 // There are 3 cases:
1629 // - embedded JAR file
1630 // - directory
1631 // - error
1632 //
1633
1634 Resource resource = dot.getResource(path);
1635 if (resource != null) {
1636 try {
1637 Jar jar = new Jar(path);
1638 addClose(jar);
1639 EmbeddedResource.build(jar, resource);
1640 analyzeJar(jar, "", true);
1641 }
1642 catch (Exception e) {
1643 warning("Invalid bundle classpath entry: " + path + " " + e);
1644 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001645 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001646 if (dot.getDirectories().containsKey(path)) {
1647 // if directories are used, we should not have dot as we
1648 // would have the classes in these directories on the
1649 // class path twice.
1650 if (bcp.containsKey("."))
1651 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1652 path, path);
1653 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001654 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001655 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1656 warning("No sub JAR or directory " + path);
1657 }
1658 }
1659 }
1660
1661 }
1662 }
1663
1664 /**
1665 * We traverse through all the classes that we can find and calculate the
1666 * contained and referred set and uses. This method ignores the Bundle
1667 * classpath.
1668 *
1669 * @param jar
1670 * @param contained
1671 * @param referred
1672 * @param uses
1673 * @throws IOException
1674 */
1675 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001676 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochbb014372012-06-07 21:57:32 +00001677
1678 next: for (String path : jar.getResources().keySet()) {
1679 if (path.startsWith(prefix)) {
1680
1681 String relativePath = path.substring(prefix.length());
1682
1683 if (okToIncludeDirs) {
1684 int n = relativePath.lastIndexOf('/');
1685 if (n < 0)
1686 n = relativePath.length();
1687 String relativeDir = relativePath.substring(0, n);
1688
1689 PackageRef packageRef = getPackageRef(relativeDir);
1690 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1691 contained.put(packageRef);
1692
1693 // For each package we encounter for the first
1694 // time. Unfortunately we can only do this once
1695 // we found a class since the bcp has a tendency
1696 // to overlap
1697 if (!packageRef.isMetaData()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001698 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCullochd4826102012-06-26 16:34:24 +00001699 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochbb014372012-06-07 21:57:32 +00001700 }
1701 }
1702 }
1703
1704 // Check class resources, we need to analyze them
1705 if (path.endsWith(".class")) {
1706 Resource resource = jar.getResource(path);
1707 Clazz clazz;
1708 Attrs info = null;
1709
1710 try {
1711 InputStream in = resource.openInputStream();
1712 clazz = new Clazz(this, path, resource);
1713 try {
1714 // Check if we have a package-info
1715 if (relativePath.endsWith("/package-info.class")) {
1716 // package-info can contain an Export annotation
1717 info = new Attrs();
1718 parsePackageInfoClass(clazz, info);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001719 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001720 // Otherwise we just parse it simply
1721 clazz.parseClassFile();
1722 }
1723 }
1724 finally {
1725 in.close();
1726 }
1727 }
1728 catch (Throwable e) {
1729 error("Invalid class file %s (%s)", e, relativePath, e);
1730 e.printStackTrace();
1731 continue next;
1732 }
1733
1734 String calculatedPath = clazz.getClassName().getPath();
1735 if (!calculatedPath.equals(relativePath)) {
1736 // If there is a mismatch we
1737 // warning
1738 if (okToIncludeDirs) // assume already reported
1739 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001740 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001741 classspace.put(clazz.getClassName(), clazz);
1742 PackageRef packageRef = clazz.getClassName().getPackageRef();
1743
1744 if (!contained.containsKey(packageRef)) {
1745 contained.put(packageRef);
1746 if (!packageRef.isMetaData()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001747 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCullochd4826102012-06-26 16:34:24 +00001748 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochbb014372012-06-07 21:57:32 +00001749 }
1750 }
1751 if (info != null)
1752 contained.merge(packageRef, false, info);
1753
Stuart McCullochbb014372012-06-07 21:57:32 +00001754 // Look at the referred packages
1755 // and copy them to our baseline
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001756 Set<PackageRef> refs = Create.set();
Stuart McCullochbb014372012-06-07 21:57:32 +00001757 for (PackageRef p : clazz.getReferred()) {
1758 referred.put(p);
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001759 refs.add(p);
Stuart McCullochbb014372012-06-07 21:57:32 +00001760 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001761 refs.remove(packageRef);
1762 uses.addAll(packageRef, refs);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001763
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001764 // Collect the API
1765 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochbb014372012-06-07 21:57:32 +00001766 }
1767 }
1768 }
1769 }
1770
1771 if (mismatched.size() > 0) {
1772 error("Classes found in the wrong directory: %s", mismatched);
1773 return false;
1774 }
1775 return true;
1776 }
1777
1778 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1779
1780 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1781 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1782 @Override
1783 public void annotation(Annotation a) {
1784 String name = a.name.getFQN();
1785 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1786
1787 // Check version
1788 String version = a.get("value");
1789 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1790 if (version != null) {
1791 version = getReplacer().process(version);
1792 if (Verifier.VERSION.matcher(version).matches())
1793 info.put(VERSION_ATTRIBUTE, version);
1794 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001795 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochbb014372012-06-07 21:57:32 +00001796 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001797 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001798 // Verify this matches with packageinfo
1799 String presentVersion = info.get(VERSION_ATTRIBUTE);
1800 try {
1801 Version av = new Version(presentVersion);
1802 Version bv = new Version(version);
1803 if (!av.equals(bv)) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001804 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1805 .getClassName().getFQN());
Stuart McCullochbb014372012-06-07 21:57:32 +00001806 }
1807 }
1808 catch (Exception e) {
1809 // Ignore
1810 }
1811 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001812 } else if (name.equals(Export.class.getName())) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001813
Stuart McCulloch2286f232012-06-15 13:27:53 +00001814 // Check mandatory attributes
1815 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1816 if (!attrs.isEmpty()) {
1817 info.putAll(attrs);
1818 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1819 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001820
Stuart McCulloch2286f232012-06-15 13:27:53 +00001821 // Check optional attributes
1822 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1823 if (!attrs.isEmpty()) {
1824 info.putAll(attrs);
1825 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001826
Stuart McCulloch2286f232012-06-15 13:27:53 +00001827 // Check Included classes
1828 Object[] included = a.get(Export.INCLUDE);
1829 if (included != null && included.length > 0) {
1830 StringBuilder sb = new StringBuilder();
1831 String del = "";
1832 for (Object i : included) {
1833 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1834 if (m.matches()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001835 sb.append(del);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001836 sb.append(m.group(2));
Stuart McCullochbb014372012-06-07 21:57:32 +00001837 del = ",";
1838 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001839 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001840 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochbb014372012-06-07 21:57:32 +00001841 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001842
1843 // Check Excluded classes
1844 Object[] excluded = a.get(Export.EXCLUDE);
1845 if (excluded != null && excluded.length > 0) {
1846 StringBuilder sb = new StringBuilder();
1847 String del = "";
1848 for (Object i : excluded) {
1849 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1850 if (m.matches()) {
1851 sb.append(del);
1852 sb.append(m.group(2));
1853 del = ",";
1854 }
1855 }
1856 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1857 }
1858
1859 // Check Uses
1860 Object[] uses = a.get(Export.USES);
1861 if (uses != null && uses.length > 0) {
1862 String old = info.get(USES_DIRECTIVE);
1863 if (old == null)
1864 old = "";
1865 StringBuilder sb = new StringBuilder(old);
1866 String del = sb.length() == 0 ? "" : ",";
1867
1868 for (Object use : uses) {
1869 sb.append(del);
1870 sb.append(use);
1871 del = ",";
1872 }
1873 info.put(USES_DIRECTIVE, sb.toString());
1874 }
1875 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001876 }
1877
1878 });
1879 }
1880
1881 /**
1882 * Clean up version parameters. Other builders use more fuzzy definitions of
1883 * the version syntax. This method cleans up such a version to match an OSGi
1884 * version.
1885 *
1886 * @param VERSION_STRING
1887 * @return
1888 */
Stuart McCulloch2286f232012-06-15 13:27:53 +00001889 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1890 Pattern.DOTALL);
1891 static Pattern fuzzyVersionRange = Pattern.compile(
1892 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1893 Pattern.DOTALL);
Stuart McCullochbb014372012-06-07 21:57:32 +00001894 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1895
1896 static Pattern nummeric = Pattern.compile("\\d*");
1897
1898 static public String cleanupVersion(String version) {
1899 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1900
1901 if (m.matches()) {
1902 return version;
1903 }
1904
1905 m = fuzzyVersionRange.matcher(version);
1906 if (m.matches()) {
1907 String prefix = m.group(1);
1908 String first = m.group(2);
1909 String last = m.group(3);
1910 String suffix = m.group(4);
1911 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1912 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001913
Stuart McCulloch2286f232012-06-15 13:27:53 +00001914 m = fuzzyVersion.matcher(version);
1915 if (m.matches()) {
1916 StringBuilder result = new StringBuilder();
1917 String major = removeLeadingZeroes(m.group(1));
1918 String minor = removeLeadingZeroes(m.group(3));
1919 String micro = removeLeadingZeroes(m.group(5));
1920 String qualifier = m.group(7);
1921
1922 if (major != null) {
1923 result.append(major);
1924 if (minor != null) {
1925 result.append(".");
1926 result.append(minor);
1927 if (micro != null) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001928 result.append(".");
Stuart McCulloch2286f232012-06-15 13:27:53 +00001929 result.append(micro);
Stuart McCullochbb014372012-06-07 21:57:32 +00001930 if (qualifier != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001931 result.append(".");
Stuart McCullochbb014372012-06-07 21:57:32 +00001932 cleanupModifier(result, qualifier);
1933 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001934 } else if (qualifier != null) {
1935 result.append(".0.");
1936 cleanupModifier(result, qualifier);
1937 }
1938 } else if (qualifier != null) {
1939 result.append(".0.0.");
1940 cleanupModifier(result, qualifier);
Stuart McCullochbb014372012-06-07 21:57:32 +00001941 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001942 return result.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +00001943 }
1944 }
1945 return version;
1946 }
1947
1948 private static String removeLeadingZeroes(String group) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001949 if (group == null)
1950 return null;
1951
Stuart McCullochbb014372012-06-07 21:57:32 +00001952 int n = 0;
Stuart McCulloch2286f232012-06-15 13:27:53 +00001953 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochbb014372012-06-07 21:57:32 +00001954 n++;
1955 if (n == 0)
1956 return group;
1957
1958 return group.substring(n);
1959 }
1960
1961 static void cleanupModifier(StringBuilder result, String modifier) {
1962 Matcher m = fuzzyModifier.matcher(modifier);
1963 if (m.matches())
1964 modifier = m.group(2);
1965
1966 for (int i = 0; i < modifier.length(); i++) {
1967 char c = modifier.charAt(i);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001968 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochbb014372012-06-07 21:57:32 +00001969 result.append(c);
1970 }
1971 }
1972
1973 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
1974 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
1975
Stuart McCullochbb014372012-06-07 21:57:32 +00001976 public String getVersionPolicy(boolean implemented) {
1977 if (implemented) {
Stuart McCullochd4826102012-06-26 16:34:24 +00001978 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochbb014372012-06-07 21:57:32 +00001979 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001980
Stuart McCullochd4826102012-06-26 16:34:24 +00001981 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochbb014372012-06-07 21:57:32 +00001982 }
1983
1984 /**
1985 * The extends macro traverses all classes and returns a list of class names
1986 * that extend a base class.
1987 */
1988
1989 static String _classesHelp = "${classes;'implementing'|'extending'|'importing'|'named'|'version'|'any';<pattern>}, Return a list of class fully qualified class names that extend/implement/import any of the contained classes matching the pattern\n";
1990
1991 public String _classes(String... args) throws Exception {
1992 // Macro.verifyCommand(args, _classesHelp, new
1993 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
1994 // null}, 3,3);
1995
1996 Collection<Clazz> matched = getClasses(args);
1997 if (matched.isEmpty())
1998 return "";
1999
2000 return join(matched);
2001 }
2002
2003 public Collection<Clazz> getClasses(String... args) throws Exception {
2004
2005 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2006 for (int i = 1; i < args.length; i++) {
2007 if (args.length < i + 1)
Stuart McCulloch2286f232012-06-15 13:27:53 +00002008 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2009 + _classesHelp);
Stuart McCullochbb014372012-06-07 21:57:32 +00002010
2011 String typeName = args[i];
2012 if (typeName.equalsIgnoreCase("extending"))
2013 typeName = "extends";
Stuart McCulloch2286f232012-06-15 13:27:53 +00002014 else if (typeName.equalsIgnoreCase("importing"))
2015 typeName = "imports";
2016 else if (typeName.equalsIgnoreCase("implementing"))
2017 typeName = "implements";
Stuart McCullochbb014372012-06-07 21:57:32 +00002018
2019 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2020
2021 if (type == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +00002022 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochbb014372012-06-07 21:57:32 +00002023
2024 Instruction instr = null;
2025 if (Clazz.HAS_ARGUMENT.contains(type)) {
2026 String s = args[++i];
2027 instr = new Instruction(s);
2028 }
2029 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2030 Clazz clazz = c.next();
2031 if (!clazz.is(type, instr, this)) {
2032 c.remove();
2033 }
2034 }
2035 }
2036 return matched;
2037 }
2038
2039 /**
2040 * Get the exporter of a package ...
2041 */
2042
2043 public String _exporters(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002044 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochbb014372012-06-07 21:57:32 +00002045 null, 2, 2);
2046 StringBuilder sb = new StringBuilder();
2047 String del = "";
2048 String pack = args[1].replace('.', '/');
2049 for (Jar jar : classpath) {
2050 if (jar.getDirectories().containsKey(pack)) {
2051 sb.append(del);
2052 sb.append(jar.getName());
2053 }
2054 }
2055 return sb.toString();
2056 }
2057
Stuart McCulloch2286f232012-06-15 13:27:53 +00002058 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochbb014372012-06-07 21:57:32 +00002059 return classspace;
2060 }
2061
2062 /**
2063 * Locate a resource on the class path.
2064 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002065 * @param path
2066 * Path of the reosurce
Stuart McCullochbb014372012-06-07 21:57:32 +00002067 * @return A resource or <code>null</code>
2068 */
2069 public Resource findResource(String path) {
2070 for (Jar entry : getClasspath()) {
2071 Resource r = entry.getResource(path);
2072 if (r != null)
2073 return r;
2074 }
2075 return null;
2076 }
2077
2078 /**
2079 * Find a clazz on the class path. This class has been parsed.
2080 *
2081 * @param path
2082 * @return
2083 */
2084 public Clazz findClass(TypeRef typeRef) throws Exception {
2085 Clazz c = classspace.get(typeRef);
2086 if (c != null)
2087 return c;
2088
2089 c = importedClassesCache.get(typeRef);
2090 if (c != null)
2091 return c;
2092
2093 Resource r = findResource(typeRef.getPath());
2094 if (r == null) {
2095 getClass().getClassLoader();
2096 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2097 if (url != null)
2098 r = new URLResource(url);
2099 }
2100 if (r != null) {
2101 c = new Clazz(this, typeRef.getPath(), r);
2102 c.parseClassFile();
2103 importedClassesCache.put(typeRef, c);
2104 }
2105 return c;
2106 }
2107
2108 /**
2109 * Answer the bundle version.
2110 *
2111 * @return
2112 */
2113 public String getVersion() {
2114 String version = getProperty(BUNDLE_VERSION);
2115 if (version == null)
2116 version = "0.0.0";
2117 return version;
2118 }
2119
2120 public boolean isNoBundle() {
2121 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2122 }
2123
2124 public void referTo(TypeRef ref) {
2125 PackageRef pack = ref.getPackageRef();
2126 if (!referred.containsKey(pack))
2127 referred.put(pack, new Attrs());
2128 }
2129
2130 public void referToByBinaryName(String binaryClassName) {
2131 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2132 referTo(ref);
2133 }
2134
2135 /**
2136 * Ensure that we are running on the correct bnd.
2137 */
2138 void doRequireBnd() {
2139 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2140 if (require == null || require.isEmpty())
2141 return;
2142
Stuart McCulloch2286f232012-06-15 13:27:53 +00002143 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochbb014372012-06-07 21:57:32 +00002144 map.put(Constants.VERSION_FILTER, getBndVersion());
2145
2146 for (String filter : require.keySet()) {
2147 try {
2148 Filter f = new Filter(filter);
2149 if (f.match(map))
2150 continue;
2151 error("%s fails %s", REQUIRE_BND, require.get(filter));
2152 }
2153 catch (Exception t) {
2154 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2155 }
2156 }
2157 }
2158
2159 /**
2160 * md5 macro
2161 */
2162
2163 static String _md5Help = "${md5;path}";
2164
2165 public String _md5(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002166 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2167 null, null, Pattern.compile("base64|hex")
2168 }, 2, 3);
Stuart McCullochbb014372012-06-07 21:57:32 +00002169
2170 Digester<MD5> digester = MD5.getDigester();
2171 Resource r = dot.getResource(args[1]);
2172 if (r == null)
2173 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2174
2175 IO.copy(r.openInputStream(), digester);
2176 boolean hex = args.length > 2 && args[2].equals("hex");
2177 if (hex)
2178 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch2286f232012-06-15 13:27:53 +00002179
2180 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochbb014372012-06-07 21:57:32 +00002181 }
2182
2183 /**
2184 * SHA1 macro
2185 */
2186
2187 static String _sha1Help = "${sha1;path}";
2188
2189 public String _sha1(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002190 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2191 null, null, Pattern.compile("base64|hex")
2192 }, 2, 3);
Stuart McCullochbb014372012-06-07 21:57:32 +00002193 Digester<SHA1> digester = SHA1.getDigester();
2194 Resource r = dot.getResource(args[1]);
2195 if (r == null)
2196 throw new FileNotFoundException("From sha1, not found " + args[1]);
2197
2198 IO.copy(r.openInputStream(), digester);
2199 return Base64.encodeBase64(digester.digest().digest());
2200 }
2201
2202 public Descriptor getDescriptor(String descriptor) {
2203 return descriptors.getDescriptor(descriptor);
2204 }
2205
2206 public TypeRef getTypeRef(String binaryClassName) {
2207 return descriptors.getTypeRef(binaryClassName);
2208 }
2209
2210 public PackageRef getPackageRef(String binaryName) {
2211 return descriptors.getPackageRef(binaryName);
2212 }
2213
2214 public TypeRef getTypeRefFromFQN(String fqn) {
2215 return descriptors.getTypeRefFromFQN(fqn);
2216 }
2217
2218 public TypeRef getTypeRefFromPath(String path) {
2219 return descriptors.getTypeRefFromPath(path);
2220 }
2221
2222 public boolean isImported(PackageRef packageRef) {
2223 return imports.containsKey(packageRef);
2224 }
2225
2226 /**
2227 * Merge the attributes of two maps, where the first map can contain
2228 * wildcarded names. The idea is that the first map contains instructions
2229 * (for example *) with a set of attributes. These patterns are matched
2230 * against the found packages in actual. If they match, the result is set
2231 * with the merged set of attributes. It is expected that the instructions
2232 * are ordered so that the instructor can define which pattern matches
2233 * first. Attributes in the instructions override any attributes from the
2234 * actual.<br/>
Stuart McCullochbb014372012-06-07 21:57:32 +00002235 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2236 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2237 * if the pattern starts with an exclamation mark, it will remove that
2238 * matches for that pattern (- the !) from the working set. So the following
2239 * patterns should work:
2240 * <ul>
2241 * <li>com.foo.bar</li>
2242 * <li>com.foo.*</li>
2243 * <li>com.foo.???</li>
2244 * <li>com.*.[^b][^a][^r]</li>
2245 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2246 * </ul>
2247 * Enough rope to hang the average developer I would say.
2248 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002249 * @param instructions
2250 * the instructions with patterns.
2251 * @param source
2252 * the actual found packages, contains no duplicates
Stuart McCullochbb014372012-06-07 21:57:32 +00002253 * @return Only the packages that were filtered by the given instructions
2254 */
2255
2256 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2257 Packages result = new Packages();
2258 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2259 Collections.sort(refs);
2260
2261 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2262 if (nomatch == null)
2263 nomatch = Create.set();
2264
2265 for (Instruction instruction : filters) {
2266 boolean match = false;
2267
2268 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2269 PackageRef packageRef = i.next();
2270
2271 if (packageRef.isMetaData()) {
2272 i.remove(); // no use checking it again
2273 continue;
2274 }
2275
2276 String packageName = packageRef.getFQN();
2277
2278 if (instruction.matches(packageName)) {
2279 match = true;
2280 if (!instruction.isNegated()) {
2281 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2282 instructions.get(instruction));
2283 }
2284 i.remove(); // Can never match again for another pattern
2285 }
2286 }
2287 if (!match && !instruction.isAny())
2288 nomatch.add(instruction);
2289 }
2290
2291 /*
2292 * Tricky. If we have umatched instructions they might indicate that we
2293 * want to have multiple decorators for the same package. So we check
2294 * the unmatched against the result list. If then then match and have
2295 * actually interesting properties then we merge them
2296 */
2297
2298 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2299 Instruction instruction = i.next();
2300
2301 // We assume the user knows what he is
2302 // doing and inserted a literal. So
2303 // we ignore any not matched literals
2304 if (instruction.isLiteral()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002305 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochbb014372012-06-07 21:57:32 +00002306 i.remove();
2307 continue;
2308 }
2309
2310 // Not matching a negated instruction looks
2311 // like an error ...
2312 if (instruction.isNegated()) {
2313 continue;
2314 }
2315
2316 // An optional instruction should not generate
2317 // an error
2318 if (instruction.isOptional()) {
2319 i.remove();
2320 continue;
2321 }
2322
2323 // boolean matched = false;
2324 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2325 // for (PackageRef ref : prefs) {
2326 // if (instruction.matches(ref.getFQN())) {
2327 // result.merge(ref, true, source.get(ref),
2328 // instructions.get(instruction));
2329 // matched = true;
2330 // }
2331 // }
2332 // if (matched)
2333 // i.remove();
2334 }
2335 return result;
2336 }
2337
2338 public void setDiagnostics(boolean b) {
2339 diagnostics = b;
2340 }
2341
2342 public Clazz.JAVA getLowestEE() {
2343 if (ees.isEmpty())
2344 return Clazz.JAVA.JDK1_4;
2345
2346 return ees.first();
2347 }
2348
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002349 public String _ee(@SuppressWarnings("unused")
2350 String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +00002351 return getLowestEE().getEE();
2352 }
2353
2354 /**
2355 * Calculate the output file for the given target. The strategy is:
2356 *
2357 * <pre>
2358 * parameter given if not null and not directory
2359 * if directory, this will be the output directory
2360 * based on bsn-version.jar
2361 * name of the source file if exists
2362 * Untitled-[n]
2363 * </pre>
2364 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002365 * @param output
2366 * may be null, otherwise a file path relative to base
Stuart McCullochbb014372012-06-07 21:57:32 +00002367 */
2368 public File getOutputFile(String output) {
2369
2370 if (output == null)
2371 output = get(Constants.OUTPUT);
2372
2373 File outputDir;
2374
2375 if (output != null) {
2376 File outputFile = getFile(output);
2377 if (outputFile.isDirectory())
2378 outputDir = outputFile;
2379 else
2380 return outputFile;
Stuart McCulloch2286f232012-06-15 13:27:53 +00002381 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00002382 outputDir = getBase();
2383
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002384 Entry<String,Attrs> name = getBundleSymbolicName();
2385 if (name != null) {
2386 String bsn = name.getKey();
Stuart McCullochbb014372012-06-07 21:57:32 +00002387 String version = getBundleVersion();
2388 Version v = Version.parseVersion(version);
Stuart McCulloch2286f232012-06-15 13:27:53 +00002389 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochbb014372012-06-07 21:57:32 +00002390 return new File(outputDir, outputName);
2391 }
2392
2393 File source = getJar().getSource();
2394 if (source != null) {
2395 String outputName = source.getName();
2396 return new File(outputDir, outputName);
2397 }
2398
Stuart McCulloch2286f232012-06-15 13:27:53 +00002399 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochbb014372012-06-07 21:57:32 +00002400 int n = 0;
2401 File f = getFile(outputDir, "Untitled");
2402 while (f.isFile()) {
2403 f = getFile(outputDir, "Untitled-" + n++);
2404 }
2405 return f;
2406 }
2407
2408 /**
2409 * Utility function to carefully save the file. Will create a backup if the
2410 * source file has the same path as the output. It will also only save if
2411 * the file was modified or the force flag is true
2412 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002413 * @param output
2414 * the output file, if null {@link #getOutputFile(String)} is
2415 * used.
2416 * @param force
2417 * if it needs to be overwritten
Stuart McCullochbb014372012-06-07 21:57:32 +00002418 * @throws Exception
2419 */
2420
2421 public boolean save(File output, boolean force) throws Exception {
2422 if (output == null)
2423 output = getOutputFile(null);
2424
2425 Jar jar = getJar();
2426 File source = jar.getSource();
2427
Stuart McCulloch2286f232012-06-15 13:27:53 +00002428 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2429 jar.lastModified() - output.lastModified());
Stuart McCullochbb014372012-06-07 21:57:32 +00002430
2431 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
2432 output.getParentFile().mkdirs();
2433 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2434 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2435 if (!source.renameTo(bak)) {
2436 error("Could not create backup file %s", bak);
Stuart McCulloch2286f232012-06-15 13:27:53 +00002437 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00002438 source.delete();
2439 }
2440 try {
2441 trace("Saving jar to %s", output);
2442 getJar().write(output);
2443 }
2444 catch (Exception e) {
2445 output.delete();
2446 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2447 }
2448 return true;
2449 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00002450 trace("Not modified %s", output);
2451 return false;
2452
Stuart McCullochbb014372012-06-07 21:57:32 +00002453 }
2454
2455 /**
2456 * Set default import and export instructions if none are set
2457 */
2458 public void setDefaults(String bsn, Version version) {
2459 if (getExportPackage() == null)
2460 setExportPackage("*");
2461 if (getImportPackage() == null)
2462 setExportPackage("*");
2463 if (bsn != null && getBundleSymbolicName() == null)
2464 setBundleSymbolicName(bsn);
2465 if (version != null && getBundleVersion() == null)
2466 setBundleVersion(version);
2467 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002468
2469 /**
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002470 * Remove the own references and optional java references from the uses lib
2471 *
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002472 * @param apiUses
2473 * @param removeJava
2474 * @return
2475 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002476 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002477 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002478 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002479 e.getValue().remove(e.getKey());
2480 if (!removeJava)
2481 continue;
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002482
2483 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2484 if (i.next().isJava())
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002485 i.remove();
2486 }
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002487 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002488 return map;
2489 }
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002490
2491 /**
2492 * Return the classes for a given source package.
2493 *
2494 * @param source
2495 * the source package
2496 * @return a set of classes for the requested package.
2497 */
2498 public Set<Clazz> getClassspace(PackageRef source) {
2499 Set<Clazz> result = new HashSet<Clazz>();
2500 for (Clazz c : getClassspace().values()) {
2501 if (c.getClassName().getPackageRef() == source)
2502 result.add(c);
2503 }
2504 return result;
2505 }
2506
2507 /**
2508 * Create a cross reference from package source, to packages in dest
2509 * @param source
2510 * @param dest
2511 * @param sourceModifiers
2512 * @return
2513 * @throws Exception
2514 */
2515 public Map<Clazz.Def, List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest, final int sourceModifiers)
2516 throws Exception {
2517 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def, TypeRef>(Clazz.Def.class, TypeRef.class, true);
2518
2519 for (final Clazz clazz : getClassspace().values()) {
2520 if ((clazz.accessx & sourceModifiers) == 0)
2521 continue;
2522
2523 if (source!=null && source != clazz.getClassName().getPackageRef())
2524 continue;
2525
2526 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2527 Clazz.Def member;
2528
2529 public void extendsClass(TypeRef zuper) throws Exception {
2530 if (dest.contains(zuper.getPackageRef()))
2531 xref.add(clazz.getExtends(zuper), zuper);
2532 }
2533
2534 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2535 for (TypeRef i : interfaces) {
2536 if (dest.contains(i.getPackageRef()))
2537 xref.add(clazz.getImplements(i), i);
2538 }
2539 }
2540
2541 public void referTo(TypeRef to, int modifiers) {
2542 if (to.isJava())
2543 return;
2544
2545 if (!dest.contains(to.getPackageRef()))
2546 return;
2547
2548 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2549 xref.add(member, to);
2550 }
2551
2552 }
2553
2554 public void method(Clazz.MethodDef defined) {
2555 member = defined;
2556 }
2557
2558 public void field(Clazz.FieldDef defined) {
2559 member = defined;
2560 }
2561
2562 });
2563
2564 }
2565 return xref;
2566 }
2567
Stuart McCullochbb014372012-06-07 21:57:32 +00002568}