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