blob: 5c3e2b76ebeeea75c72b670d8a0d2d6b5812c517 [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 {
Stuart McCulloch5515a832012-07-22 00:19:13 +0000309 Processor previous = beginHandleErrors(plugin.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +0000310 boolean reanalyze = plugin.analyzeJar(this);
Stuart McCulloch5515a832012-07-22 00:19:13 +0000311 endHandleErrors(previous);
Stuart McCullochf3173222012-06-07 21:57:32 +0000312 if (reanalyze) {
313 classspace.clear();
314 analyzeBundleClasspath();
315 }
316 }
317 catch (Exception e) {
318 error("Analyzer Plugin %s failed %s", plugin, e);
319 }
320 }
321 }
322
323 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000324 * @return
325 */
326 boolean isResourceOnly() {
327 return isTrue(getProperty(RESOURCEONLY));
328 }
329
330 /**
331 * One of the main workhorses of this class. This will analyze the current
332 * setp and calculate a new manifest according to this setup. This method
333 * will also set the manifest on the main jar dot
334 *
335 * @return
336 * @throws IOException
337 */
338 public Manifest calcManifest() throws Exception {
339 analyze();
340 Manifest manifest = new Manifest();
341 Attributes main = manifest.getMainAttributes();
342
343 main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
344 main.putValue(BUNDLE_MANIFESTVERSION, "2");
345
346 boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
347
348 if (!noExtraHeaders) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000349 main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
350 + ")");
Stuart McCullochf3173222012-06-07 21:57:32 +0000351 main.putValue(TOOL, "Bnd-" + getBndVersion());
352 main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
353 }
354
355 String exportHeader = printClauses(exports, true);
356
357 if (exportHeader.length() > 0)
358 main.putValue(EXPORT_PACKAGE, exportHeader);
359 else
360 main.remove(EXPORT_PACKAGE);
361
362 // Remove all the Java packages from the imports
363 if (!imports.isEmpty()) {
364 main.putValue(IMPORT_PACKAGE, printClauses(imports));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000365 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000366 main.remove(IMPORT_PACKAGE);
367 }
368
369 Packages temp = new Packages(contained);
370 temp.keySet().removeAll(exports.keySet());
371
372 if (!temp.isEmpty())
373 main.putValue(PRIVATE_PACKAGE, printClauses(temp));
374 else
375 main.remove(PRIVATE_PACKAGE);
376
377 Parameters bcp = getBundleClasspath();
378 if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
379 main.remove(BUNDLE_CLASSPATH);
380 else
381 main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
382
383 doNamesection(dot, manifest);
384
385 for (Enumeration< ? > h = getProperties().propertyNames(); h.hasMoreElements();) {
386 String header = (String) h.nextElement();
387 if (header.trim().length() == 0) {
388 warning("Empty property set with value: " + getProperties().getProperty(header));
389 continue;
390 }
391
392 if (isMissingPlugin(header.trim())) {
393 error("Missing plugin for command %s", header);
394 }
395 if (!Character.isUpperCase(header.charAt(0))) {
396 if (header.charAt(0) == '@')
397 doNameSection(manifest, header);
398 continue;
399 }
400
Stuart McCulloch4482c702012-06-15 13:27:53 +0000401 if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE) || header.equals(IMPORT_PACKAGE))
Stuart McCullochf3173222012-06-07 21:57:32 +0000402 continue;
403
404 if (header.equalsIgnoreCase("Name")) {
405 error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
406 continue;
407 }
408
409 if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
410 String value = getProperty(header);
411 if (value != null && main.getValue(header) == null) {
412 if (value.trim().length() == 0)
413 main.remove(header);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000414 else if (value.trim().equals(EMPTY_HEADER))
415 main.putValue(header, "");
Stuart McCullochf3173222012-06-07 21:57:32 +0000416 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000417 main.putValue(header, value);
Stuart McCullochf3173222012-06-07 21:57:32 +0000418 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000419 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000420 // TODO should we report?
421 }
422 }
423
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000424 // Copy old values into new manifest, when they
425 // exist in the old one, but not in the new one
426 merge(manifest, dot.getManifest());
427
Stuart McCullochf3173222012-06-07 21:57:32 +0000428 //
429 // Calculate the bundle symbolic name if it is
430 // not set.
431 // 1. set
432 // 2. name of properties file (must be != bnd.bnd)
433 // 3. name of directory, which is usualy project name
434 //
435 String bsn = getBsn();
436 if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
437 main.putValue(BUNDLE_SYMBOLICNAME, bsn);
438 }
439
440 //
441 // Use the same name for the bundle name as BSN when
442 // the bundle name is not set
443 //
444 if (main.getValue(BUNDLE_NAME) == null) {
445 main.putValue(BUNDLE_NAME, bsn);
446 }
447
448 if (main.getValue(BUNDLE_VERSION) == null)
449 main.putValue(BUNDLE_VERSION, "0");
450
Stuart McCullochf3173222012-06-07 21:57:32 +0000451 // Remove all the headers mentioned in -removeheaders
452 Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
453 Collection<Object> result = instructions.select(main.keySet(), false);
454 main.keySet().removeAll(result);
455
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000456 // We should not set the manifest here, this is in general done
457 // by the caller.
458 // dot.setManifest(manifest);
Stuart McCullochf3173222012-06-07 21:57:32 +0000459 return manifest;
460 }
461
462 /**
463 * Parse the namesection as instructions and then match them against the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000464 * current set of resources For example:
Stuart McCullochf3173222012-06-07 21:57:32 +0000465 *
466 * <pre>
467 * -namesection: *;baz=true, abc/def/bar/X.class=3
468 * </pre>
469 *
470 * The raw value of {@link Constants#NAMESECTION} is used but the values of
471 * the attributes are replaced where @ is set to the resource name. This
472 * allows macro to operate on the resource
Stuart McCullochf3173222012-06-07 21:57:32 +0000473 */
474
475 private void doNamesection(Jar dot, Manifest manifest) {
476
477 Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
478 Instructions instructions = new Instructions(namesection);
479 Set<String> resources = new HashSet<String>(dot.getResources().keySet());
480
481 //
482 // For each instruction, iterator over the resources and filter
483 // them. If a resource matches, it must be removed even if the
484 // instruction is negative. If positive, add a name section
485 // to the manifest for the given resource name. Then add all
486 // attributes from the instruction to that name section.
487 //
Stuart McCulloch4482c702012-06-15 13:27:53 +0000488 for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000489 boolean matched = false;
490
491 // For each instruction
492
493 for (Iterator<String> i = resources.iterator(); i.hasNext();) {
494 String path = i.next();
495 // For each resource
496
497 if (instr.getKey().matches(path)) {
498
499 // Instruction matches the resource
500
501 matched = true;
502 if (!instr.getKey().isNegated()) {
503
504 // Positive match, add the attributes
505
506 Attributes attrs = manifest.getAttributes(path);
507 if (attrs == null) {
508 attrs = new Attributes();
509 manifest.getEntries().put(path, attrs);
510 }
511
512 //
513 // Add all the properties from the instruction to the
514 // name section
515 //
516
Stuart McCulloch4482c702012-06-15 13:27:53 +0000517 for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000518 setProperty("@", path);
519 try {
520 String processed = getReplacer().process(property.getValue());
521 attrs.putValue(property.getKey(), processed);
522 }
523 finally {
524 unsetProperty("@");
525 }
526 }
527 }
528 i.remove();
529 }
530 }
531
532 if (!matched && resources.size() > 0)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000533 warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
Stuart McCullochf3173222012-06-07 21:57:32 +0000534 }
535
536 }
537
538 /**
539 * This method is called when the header starts with a @, signifying a name
540 * section header. The name part is defined by replacing all the @ signs to
541 * a /, removing the first and the last, and using the last part as header
542 * name:
543 *
544 * <pre>
545 * &#064;org@osgi@service@event@Implementation-Title
546 * </pre>
547 *
548 * This will be the header Implementation-Title in the
549 * org/osgi/service/event named section.
550 *
551 * @param manifest
552 * @param header
553 */
554 private void doNameSection(Manifest manifest, String header) {
555 String path = header.replace('@', '/');
556 int n = path.lastIndexOf('/');
557 // Must succeed because we start with @
558 String name = path.substring(n + 1);
559 // Skip first /
560 path = path.substring(1, n);
561 if (name.length() != 0 && path.length() != 0) {
562 Attributes attrs = manifest.getAttributes(path);
563 if (attrs == null) {
564 attrs = new Attributes();
565 manifest.getEntries().put(path, attrs);
566 }
567 attrs.putValue(name, getProperty(header));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000568 } else {
569 warning("Invalid header (starts with @ but does not seem to be for the Name section): %s", header);
Stuart McCullochf3173222012-06-07 21:57:32 +0000570 }
571 }
572
573 /**
574 * Clear the key part of a header. I.e. remove everything from the first ';'
575 *
576 * @param value
577 * @return
578 */
579 public String getBsn() {
580 String value = getProperty(BUNDLE_SYMBOLICNAME);
581 if (value == null) {
582 if (getPropertiesFile() != null)
583 value = getPropertiesFile().getName();
584
585 String projectName = getBase().getName();
586 if (value == null || value.equals("bnd.bnd")) {
587 value = projectName;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000588 } else if (value.endsWith(".bnd")) {
589 value = value.substring(0, value.length() - 4);
590 if (!value.startsWith(getBase().getName()))
591 value = projectName + "." + value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000592 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000593 }
594
595 if (value == null)
596 return "untitled";
597
598 int n = value.indexOf(';');
599 if (n > 0)
600 value = value.substring(0, n);
601 return value.trim();
602 }
603
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000604 public String _bsn(@SuppressWarnings("unused")
605 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000606 return getBsn();
607 }
608
609 /**
610 * Calculate an export header solely based on the contents of a JAR file
611 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000612 * @param bundle
613 * The jar file to analyze
Stuart McCullochf3173222012-06-07 21:57:32 +0000614 * @return
615 */
616 public String calculateExportsFromContents(Jar bundle) {
617 String ddel = "";
618 StringBuilder sb = new StringBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000619 Map<String,Map<String,Resource>> map = bundle.getDirectories();
Stuart McCullochf3173222012-06-07 21:57:32 +0000620 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
621 String directory = i.next();
622 if (directory.equals("META-INF") || directory.startsWith("META-INF/"))
623 continue;
624 if (directory.equals("OSGI-OPT") || directory.startsWith("OSGI-OPT/"))
625 continue;
626 if (directory.equals("/"))
627 continue;
628
629 if (directory.endsWith("/"))
630 directory = directory.substring(0, directory.length() - 1);
631
632 directory = directory.replace('/', '.');
633 sb.append(ddel);
634 sb.append(directory);
635 ddel = ",";
636 }
637 return sb.toString();
638 }
639
640 public Packages getContained() {
641 return contained;
642 }
643
644 public Packages getExports() {
645 return exports;
646 }
647
648 public Packages getImports() {
649 return imports;
650 }
651
Stuart McCullochb32291a2012-07-16 14:10:57 +0000652 public Set<PackageRef> getPrivates() {
653 HashSet<PackageRef> privates = new HashSet<PackageRef>(contained.keySet());
654 privates.removeAll(exports.keySet());
655 privates.removeAll(imports.keySet());
656 return privates;
657 }
658
Stuart McCullochf3173222012-06-07 21:57:32 +0000659 public Jar getJar() {
660 return dot;
661 }
662
663 public Packages getReferred() {
664 return referred;
665 }
666
667 /**
668 * Return the set of unreachable code depending on exports and the bundle
669 * activator.
670 *
671 * @return
672 */
673 public Set<PackageRef> getUnreachable() {
674 Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
675 for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
676 PackageRef packageRef = r.next();
677 removeTransitive(packageRef, unreachable);
678 }
679 if (activator != null) {
680 removeTransitive(activator.getPackageRef(), unreachable);
681 }
682 return unreachable;
683 }
684
Stuart McCullochb32291a2012-07-16 14:10:57 +0000685 public Map<PackageRef,List<PackageRef>> getUses() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000686 return uses;
687 }
688
Stuart McCullochb32291a2012-07-16 14:10:57 +0000689 public Map<PackageRef,List<PackageRef>> getAPIUses() {
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000690 return apiUses;
691 }
692
Stuart McCullochf3173222012-06-07 21:57:32 +0000693 /**
694 * Get the version for this bnd
695 *
696 * @return version or unknown.
697 */
698 public String getBndVersion() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000699 return getBndInfo("version", "<unknown>");
Stuart McCullochf3173222012-06-07 21:57:32 +0000700 }
701
702 public long getBndLastModified() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000703 String time = getBndInfo("lastmodified", "0");
Stuart McCullochf3173222012-06-07 21:57:32 +0000704 try {
705 return Long.parseLong(time);
706 }
707 catch (Exception e) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000708 // Ignore
Stuart McCullochf3173222012-06-07 21:57:32 +0000709 }
710 return 0;
711 }
712
713 public String getBndInfo(String key, String defaultValue) {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000714 if (bndInfo == null) {
715 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000716 Properties bndInfoLocal = new Properties();
717 URL url = Analyzer.class.getResource("bnd.info");
718 if (url != null) {
719 InputStream in = url.openStream();
720 try {
721 bndInfoLocal.load(in);
722 }
723 finally {
724 in.close();
725 }
726 }
727 bndInfo = bndInfoLocal;
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000728 }
729 catch (Exception e) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000730 e.printStackTrace();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000731 return defaultValue;
Stuart McCullochf3173222012-06-07 21:57:32 +0000732 }
733 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000734 String value = bndInfo.getProperty(key);
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000735 if (value == null)
736 return defaultValue;
737 return value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000738 }
739
740 /**
741 * Merge the existing manifest with the instructions but do not override
742 * existing properties.
743 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000744 * @param manifest
745 * The manifest to merge with
Stuart McCullochf3173222012-06-07 21:57:32 +0000746 * @throws IOException
747 */
748 public void mergeManifest(Manifest manifest) throws IOException {
749 if (manifest != null) {
750 Attributes attributes = manifest.getMainAttributes();
751 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
752 Name name = (Name) i.next();
753 String key = name.toString();
754 // Dont want instructions
755 if (key.startsWith("-"))
756 continue;
757
758 if (getProperty(key) == null)
759 setProperty(key, attributes.getValue(name));
760 }
761 }
762 }
763
764 public void setBase(File file) {
765 super.setBase(file);
766 getProperties().put("project.dir", getBase().getAbsolutePath());
767 }
768
769 /**
770 * Set the classpath for this analyzer by file.
771 *
772 * @param classpath
773 * @throws IOException
774 */
775 public void setClasspath(File[] classpath) throws IOException {
776 List<Jar> list = new ArrayList<Jar>();
777 for (int i = 0; i < classpath.length; i++) {
778 if (classpath[i].exists()) {
779 Jar current = new Jar(classpath[i]);
780 list.add(current);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000781 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000782 error("Missing file on classpath: %s", classpath[i]);
783 }
784 }
785 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
786 addClasspath(i.next());
787 }
788 }
789
790 public void setClasspath(Jar[] classpath) {
791 for (int i = 0; i < classpath.length; i++) {
792 addClasspath(classpath[i]);
793 }
794 }
795
796 public void setClasspath(String[] classpath) {
797 for (int i = 0; i < classpath.length; i++) {
798 Jar jar = getJarFromName(classpath[i], " setting classpath");
799 if (jar != null)
800 addClasspath(jar);
801 }
802 }
803
804 /**
805 * Set the JAR file we are going to work in. This will read the JAR in
806 * memory.
807 *
808 * @param jar
809 * @return
810 * @throws IOException
811 */
812 public Jar setJar(File jar) throws IOException {
813 Jar jarx = new Jar(jar);
814 addClose(jarx);
815 return setJar(jarx);
816 }
817
818 /**
819 * Set the JAR directly we are going to work on.
820 *
821 * @param jar
822 * @return
823 */
824 public Jar setJar(Jar jar) {
825 if (dot != null)
826 removeClose(dot);
827
828 this.dot = jar;
829 if (dot != null)
830 addClose(dot);
831
832 return jar;
833 }
834
835 protected void begin() {
836 if (inited == false) {
837 inited = true;
838 super.begin();
839
840 updateModified(getBndLastModified(), "bnd last modified");
841 verifyManifestHeadersCase(getProperties());
842
843 }
844 }
845
846 /**
847 * Try to get a Jar from a file name/path or a url, or in last resort from
848 * the classpath name part of their files.
849 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000850 * @param name
851 * URL or filename relative to the base
852 * @param from
853 * Message identifying the caller for errors
Stuart McCullochf3173222012-06-07 21:57:32 +0000854 * @return null or a Jar with the contents for the name
855 */
856 Jar getJarFromName(String name, String from) {
857 File file = new File(name);
858 if (!file.isAbsolute())
859 file = new File(getBase(), name);
860
861 if (file.exists())
862 try {
863 Jar jar = new Jar(file);
864 addClose(jar);
865 return jar;
866 }
867 catch (Exception e) {
868 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
869 }
870 // It is not a file ...
871 try {
872 // Lets try a URL
873 URL url = new URL(name);
874 Jar jar = new Jar(fileName(url.getPath()));
875 addClose(jar);
876 URLConnection connection = url.openConnection();
877 InputStream in = connection.getInputStream();
878 long lastModified = connection.getLastModified();
879 if (lastModified == 0)
880 // We assume the worst :-(
881 lastModified = System.currentTimeMillis();
882 EmbeddedResource.build(jar, in, lastModified);
883 in.close();
884 return jar;
885 }
886 catch (IOException ee) {
887 // Check if we have files on the classpath
888 // that have the right name, allows us to specify those
889 // names instead of the full path.
890 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
891 Jar entry = cp.next();
892 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
893 return entry;
894 }
895 }
896 // error("Can not find jar file for " + from + ": " + name);
897 }
898 return null;
899 }
900
901 private String fileName(String path) {
902 int n = path.lastIndexOf('/');
903 if (n > 0)
904 return path.substring(n + 1);
905 return path;
906 }
907
908 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000909 * @param manifests
910 * @throws Exception
911 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000912 private void merge(Manifest result, Manifest old) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000913 if (old != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000914 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
915 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000916 Attributes.Name name = (Attributes.Name) entry.getKey();
917 String value = (String) entry.getValue();
918 if (name.toString().equalsIgnoreCase("Created-By"))
919 name = new Attributes.Name("Originally-Created-By");
920 if (!result.getMainAttributes().containsKey(name))
921 result.getMainAttributes().put(name, value);
922 }
923
924 // do not overwrite existing entries
Stuart McCulloch4482c702012-06-15 13:27:53 +0000925 Map<String,Attributes> oldEntries = old.getEntries();
926 Map<String,Attributes> newEntries = result.getEntries();
927 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
928 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000929 if (!newEntries.containsKey(entry.getKey())) {
930 newEntries.put(entry.getKey(), entry.getValue());
931 }
932 }
933 }
934 }
935
936 /**
937 * Bnd is case sensitive for the instructions so we better check people are
938 * not using an invalid case. We do allow this to set headers that should
939 * not be processed by us but should be used by the framework.
940 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000941 * @param properties
942 * Properties to verify.
Stuart McCullochf3173222012-06-07 21:57:32 +0000943 */
944
945 void verifyManifestHeadersCase(Properties properties) {
946 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
947 String header = (String) i.next();
948 for (int j = 0; j < headers.length; j++) {
949 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
950 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
951 + header + " and expecting: " + headers[j]);
952 break;
953 }
954 }
955 }
956 }
957
958 /**
959 * We will add all exports to the imports unless there is a -noimport
960 * directive specified on an export. This directive is skipped for the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000961 * manifest. We also remove any version parameter so that augmentImports can
962 * do the version policy. The following method is really tricky and evolved
963 * over time. Coming from the original background of OSGi, it was a weird
964 * idea for me to have a public package that should not be substitutable. I
965 * was so much convinced that this was the right rule that I rücksichtlos
966 * imported them all. Alas, the real world was more subtle than that. It
967 * turns out that it is not a good idea to always import. First, there must
968 * be a need to import, i.e. there must be a contained package that refers
969 * to the exported package for it to make use importing that package.
970 * Second, if an exported package refers to an internal package than it
971 * should not be imported. Additionally, it is necessary to treat the
972 * exports in groups. If an exported package refers to another exported
973 * packages than it must be in the same group. A framework can only
974 * substitute exports for imports for the whole of such a group. WHY?????
975 * Not clear anymore ...
Stuart McCullochf3173222012-06-07 21:57:32 +0000976 */
977 Packages doExportsToImports(Packages exports) {
978
979 // private packages = contained - exported.
980 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
981 privatePackages.removeAll(exports.keySet());
982
983 // private references = ∀ p : private packages | uses(p)
984 Set<PackageRef> privateReferences = newSet();
985 for (PackageRef p : privatePackages) {
986 Collection<PackageRef> uses = this.uses.get(p);
987 if (uses != null)
988 privateReferences.addAll(uses);
989 }
990
991 // Assume we are going to export all exported packages
992 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
993
994 // Remove packages that are not referenced privately
995 toBeImported.retainAll(privateReferences);
996
997 // Not necessary to import anything that is already
998 // imported in the Import-Package statement.
999 // TODO toBeImported.removeAll(imports.keySet());
1000
1001 // Remove exported packages that are referring to
1002 // private packages.
1003 // Each exported package has a uses clause. We just use
1004 // the used packages for each exported package to find out
1005 // if it refers to an internal package.
1006 //
1007
1008 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1009 PackageRef next = i.next();
1010 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1011
1012 for (PackageRef privatePackage : privatePackages) {
1013 if (usedByExportedPackage.contains(privatePackage)) {
1014 i.remove();
1015 break;
1016 }
1017 }
1018 }
1019
1020 // Clean up attributes and generate result map
1021 Packages result = new Packages();
1022 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1023 PackageRef ep = i.next();
1024 Attrs parameters = exports.get(ep);
1025
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001026 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochf3173222012-06-07 21:57:32 +00001027 if (noimport != null && noimport.equalsIgnoreCase("true"))
1028 continue;
1029
1030 // // we can't substitute when there is no version
1031 // String version = parameters.get(VERSION_ATTRIBUTE);
1032 // if (version == null) {
1033 // if (isPedantic())
1034 // warning(
1035 // "Cannot automatically import exported package %s because it has no version defined",
1036 // ep);
1037 // continue;
1038 // }
1039
1040 parameters = new Attrs();
1041 parameters.remove(VERSION_ATTRIBUTE);
1042 result.put(ep, parameters);
1043 }
1044 return result;
1045 }
1046
1047 public boolean referred(PackageRef packageName) {
1048 // return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001049 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001050 if (!contained.getKey().equals(packageName)) {
1051 if (contained.getValue().contains(packageName))
1052 return true;
1053 }
1054 }
1055 return false;
1056 }
1057
1058 /**
Stuart McCullochf3173222012-06-07 21:57:32 +00001059 * @param jar
1060 */
1061 private void getExternalExports(Jar jar, Packages classpathExports) {
1062 try {
1063 Manifest m = jar.getManifest();
1064 if (m != null) {
1065 Domain domain = Domain.domain(m);
1066 Parameters exported = domain.getExportPackage();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001067 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001068 PackageRef ref = getPackageRef(e.getKey());
1069 if (!classpathExports.containsKey(ref)) {
1070 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1071 // jar.getBsn()+"-"+jar.getVersion());
1072
1073 classpathExports.put(ref, e.getValue());
1074 }
1075 }
1076 }
1077 }
1078 catch (Exception e) {
1079 warning("Erroneous Manifest for " + jar + " " + e);
1080 }
1081 }
1082
1083 /**
1084 * Find some more information about imports in manifest and other places. It
1085 * is assumed that the augmentsExports has already copied external attrs
1086 * from the classpathExports.
1087 *
1088 * @throws Exception
1089 */
1090 void augmentImports(Packages imports, Packages exports) throws Exception {
1091 List<PackageRef> noimports = Create.list();
1092 Set<PackageRef> provided = findProvidedPackages();
1093
1094 for (PackageRef packageRef : imports.keySet()) {
1095 String packageName = packageRef.getFQN();
1096
1097 setProperty(CURRENT_PACKAGE, packageName);
1098 try {
1099 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001100 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochf3173222012-06-07 21:57:32 +00001101
1102 String exportVersion = exportAttributes.getVersion();
1103 String importRange = importAttributes.getVersion();
1104
1105 if (exportVersion == null) {
1106 // TODO Should check if the source is from a bundle.
1107
Stuart McCulloch4482c702012-06-15 13:27:53 +00001108 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001109
1110 //
1111 // Version Policy - Import version substitution. We
1112 // calculate the export version and then allow the
1113 // import version attribute to use it in a substitution
1114 // by using a ${@} macro. The export version can
1115 // be defined externally or locally
1116 //
1117
1118 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch4482c702012-06-15 13:27:53 +00001119 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochf3173222012-06-07 21:57:32 +00001120
1121 exportVersion = cleanupVersion(exportVersion);
1122
1123 try {
1124 setProperty("@", exportVersion);
1125
1126 if (importRange != null) {
1127 importRange = cleanupVersion(importRange);
1128 importRange = getReplacer().process(importRange);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001129 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001130 importRange = getVersionPolicy(provider);
1131
1132 }
1133 finally {
1134 unsetProperty("@");
1135 }
1136 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1137 }
1138
1139 //
1140 // Check if exporter has mandatory attributes
1141 //
1142 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1143 if (mandatory != null) {
1144 String[] attrs = mandatory.split("\\s*,\\s*");
1145 for (int i = 0; i < attrs.length; i++) {
1146 if (!importAttributes.containsKey(attrs[i]))
1147 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1148 }
1149 }
1150
1151 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1152 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1153
1154 fixupAttributes(importAttributes);
1155 removeAttributes(importAttributes);
1156
1157 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1158 if (result == null)
1159 noimports.add(packageRef);
1160 }
1161 finally {
1162 unsetProperty(CURRENT_PACKAGE);
1163 }
1164 }
1165
1166 if (isPedantic() && noimports.size() != 0) {
1167 warning("Imports that lack version ranges: %s", noimports);
1168 }
1169 }
1170
1171 /**
1172 * Find the packages we depend on, where we implement an interface that is a
1173 * Provider Type. These packages, when we import them, must use the provider
1174 * policy.
1175 *
1176 * @throws Exception
1177 */
1178 Set<PackageRef> findProvidedPackages() throws Exception {
1179 Set<PackageRef> providers = Create.set();
1180 Set<TypeRef> cached = Create.set();
1181
1182 for (Clazz c : classspace.values()) {
1183 TypeRef[] interfaces = c.getInterfaces();
1184 if (interfaces != null)
1185 for (TypeRef t : interfaces)
1186 if (cached.contains(t) || isProvider(t)) {
1187 cached.add(t);
1188 providers.add(t.getPackageRef());
1189 }
1190 }
1191 return providers;
1192 }
1193
1194 private boolean isProvider(TypeRef t) throws Exception {
1195 Clazz c = findClass(t);
1196 if (c == null)
1197 return false;
1198
1199 if (c.annotations == null)
1200 return false;
1201
1202 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1203 boolean result = c.annotations.contains(pt);
1204 return result;
1205 }
1206
1207 /**
1208 * Provide any macro substitutions and versions for exported packages.
1209 */
1210
1211 void augmentExports(Packages exports) {
1212 for (PackageRef packageRef : exports.keySet()) {
1213 String packageName = packageRef.getFQN();
1214 setProperty(CURRENT_PACKAGE, packageName);
1215 try {
1216 Attrs attributes = exports.get(packageRef);
1217 Attrs exporterAttributes = classpathExports.get(packageRef);
1218 if (exporterAttributes == null)
1219 continue;
1220
Stuart McCulloch4482c702012-06-15 13:27:53 +00001221 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001222 String key = entry.getKey();
1223 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1224 key = VERSION_ATTRIBUTE;
1225
1226 // dont overwrite and no directives
1227 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1228 attributes.put(key, entry.getValue());
1229 }
1230 }
1231
1232 fixupAttributes(attributes);
1233 removeAttributes(attributes);
1234
1235 }
1236 finally {
1237 unsetProperty(CURRENT_PACKAGE);
1238 }
1239 }
1240 }
1241
1242 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001243 * Fixup Attributes Execute any macros on an export and
Stuart McCullochf3173222012-06-07 21:57:32 +00001244 */
1245
1246 void fixupAttributes(Attrs attributes) {
1247 // Convert any attribute values that have macros.
1248 for (String key : attributes.keySet()) {
1249 String value = attributes.get(key);
1250 if (value.indexOf('$') >= 0) {
1251 value = getReplacer().process(value);
1252 attributes.put(key, value);
1253 }
1254 }
1255
1256 }
1257
1258 /**
1259 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1260 * can add a remove-attribute: directive with a regular expression for
1261 * attributes that need to be removed. We also remove all attributes that
1262 * have a value of !. This allows you to use macros with ${if} to remove
1263 * values.
1264 */
1265
1266 void removeAttributes(Attrs attributes) {
1267 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1268
1269 if (remove != null) {
1270 Instructions removeInstr = new Instructions(remove);
1271 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1272 }
1273
1274 // Remove any ! valued attributes
Stuart McCulloch4482c702012-06-15 13:27:53 +00001275 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001276 String v = i.next().getValue();
1277 if (v.equals("!"))
1278 i.remove();
1279 }
1280 }
1281
1282 /**
1283 * Calculate a version from a version policy.
1284 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001285 * @param version
1286 * The actual exported version
1287 * @param impl
1288 * true for implementations and false for clients
Stuart McCullochf3173222012-06-07 21:57:32 +00001289 */
1290
1291 String calculateVersionRange(String version, boolean impl) {
1292 setProperty("@", version);
1293 try {
1294 return getVersionPolicy(impl);
1295 }
1296 finally {
1297 unsetProperty("@");
1298 }
1299 }
1300
1301 /**
1302 * Add the uses clauses. This method iterates over the exports and cal
1303 *
1304 * @param exports
1305 * @param uses
1306 * @throws MojoExecutionException
1307 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001308 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001309 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1310 return;
1311
1312 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1313 PackageRef packageRef = i.next();
1314 String packageName = packageRef.getFQN();
1315 setProperty(CURRENT_PACKAGE, packageName);
1316 try {
1317 doUses(packageRef, exports, uses, imports);
1318 }
1319 finally {
1320 unsetProperty(CURRENT_PACKAGE);
1321 }
1322
1323 }
1324 }
1325
1326 /**
1327 * @param packageName
1328 * @param exports
1329 * @param uses
1330 * @param imports
1331 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001332 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch4482c702012-06-15 13:27:53 +00001333 Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001334 Attrs clause = exports.get(packageRef);
1335
1336 // Check if someone already set the uses: directive
1337 String override = clause.get(USES_DIRECTIVE);
1338 if (override == null)
1339 override = USES_USES;
1340
1341 // Get the used packages
1342 Collection<PackageRef> usedPackages = uses.get(packageRef);
1343
1344 if (usedPackages != null) {
1345
1346 // Only do a uses on exported or imported packages
1347 // and uses should also not contain our own package
1348 // name
1349 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1350 sharedPackages.addAll(imports.keySet());
1351 sharedPackages.addAll(exports.keySet());
1352 sharedPackages.retainAll(usedPackages);
1353 sharedPackages.remove(packageRef);
1354
1355 StringBuilder sb = new StringBuilder();
1356 String del = "";
1357 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1358 PackageRef usedPackage = u.next();
1359 if (!usedPackage.isJava()) {
1360 sb.append(del);
1361 sb.append(usedPackage.getFQN());
1362 del = ",";
1363 }
1364 }
1365 if (override.indexOf('$') >= 0) {
1366 setProperty(CURRENT_USES, sb.toString());
1367 override = getReplacer().process(override);
1368 unsetProperty(CURRENT_USES);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001369 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001370 // This is for backward compatibility 0.0.287
1371 // can be deprecated over time
Stuart McCulloch4482c702012-06-15 13:27:53 +00001372 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochf3173222012-06-07 21:57:32 +00001373
1374 if (override.endsWith(","))
1375 override = override.substring(0, override.length() - 1);
1376 if (override.startsWith(","))
1377 override = override.substring(1);
1378 if (override.length() > 0) {
1379 clause.put(USES_DIRECTIVE, override);
1380 }
1381 }
1382 }
1383
1384 /**
1385 * Transitively remove all elemens from unreachable through the uses link.
1386 *
1387 * @param name
1388 * @param unreachable
1389 */
1390 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1391 if (!unreachable.contains(name))
1392 return;
1393
1394 unreachable.remove(name);
1395
1396 List<PackageRef> ref = uses.get(name);
1397 if (ref != null) {
1398 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1399 PackageRef element = r.next();
1400 removeTransitive(element, unreachable);
1401 }
1402 }
1403 }
1404
1405 /**
1406 * Helper method to set the package info resource
1407 *
1408 * @param dir
1409 * @param key
1410 * @param value
1411 * @throws Exception
1412 */
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001413 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1414 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001415 if (r == null)
1416 return;
1417
1418 Properties p = new Properties();
Stuart McCullochf3173222012-06-07 21:57:32 +00001419 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001420 InputStream in = r.openInputStream();
1421 try {
1422 p.load(in);
Stuart McCullochf3173222012-06-07 21:57:32 +00001423 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001424 finally {
1425 in.close();
1426 }
1427 Attrs map = classpathExports.get(packageRef);
1428 if (map == null) {
1429 classpathExports.put(packageRef, map = new Attrs());
1430 }
1431 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1432 String key = t.nextElement();
1433 String value = map.get(key);
1434 if (value == null) {
1435 value = p.getProperty(key);
1436
1437 // Messy, to allow directives we need to
1438 // allow the value to start with a ':' since we cannot
1439 // encode this in a property name
1440
1441 if (value.startsWith(":")) {
1442 key = key + ":";
1443 value = value.substring(1);
1444 }
1445 map.put(key, value);
1446 }
1447 }
1448 }
1449 catch (Exception e) {
1450 msgs.NoSuchFile_(r);
Stuart McCullochf3173222012-06-07 21:57:32 +00001451 }
1452 }
1453
1454 public void close() {
1455 if (diagnostics) {
1456 PrintStream out = System.err;
1457 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1458 out.println("Classpath used");
1459 for (Jar jar : getClasspath()) {
1460 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001461 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochf3173222012-06-07 21:57:32 +00001462 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001463 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1464 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1465 Map<String,Resource> dir = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +00001466 String name = entry.getKey().replace('/', '.');
1467 if (dir != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001468 out.printf(" %-30s %d%n", name, dir.size());
1469 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001470 out.printf(" %-30s <<empty>>%n", name);
1471 }
1472 }
1473 }
1474 }
1475
1476 super.close();
1477 if (dot != null)
1478 dot.close();
1479
1480 if (classpath != null)
1481 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1482 Jar jar = j.next();
1483 jar.close();
1484 }
1485 }
1486
1487 /**
1488 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch4482c702012-06-15 13:27:53 +00001489 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1490 * )? }
Stuart McCullochf3173222012-06-07 21:57:32 +00001491 *
1492 * @param args
1493 * @return
1494 */
1495 public String _findpath(String args[]) {
1496 return findPath("findpath", args, true);
1497 }
1498
1499 public String _findname(String args[]) {
1500 return findPath("findname", args, false);
1501 }
1502
1503 String findPath(String name, String[] args, boolean fullPathName) {
1504 if (args.length > 3) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001505 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1506 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochf3173222012-06-07 21:57:32 +00001507 return null;
1508 }
1509
1510 String regexp = ".*";
1511 String replace = null;
1512
1513 switch (args.length) {
1514 case 3 :
1515 replace = args[2];
1516 //$FALL-THROUGH$
1517 case 2 :
1518 regexp = args[1];
1519 }
1520 StringBuilder sb = new StringBuilder();
1521 String del = "";
1522
1523 Pattern expr = Pattern.compile(regexp);
1524 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1525 String path = e.next();
1526 if (!fullPathName) {
1527 int n = path.lastIndexOf('/');
1528 if (n >= 0) {
1529 path = path.substring(n + 1);
1530 }
1531 }
1532
1533 Matcher m = expr.matcher(path);
1534 if (m.matches()) {
1535 if (replace != null)
1536 path = m.replaceAll(replace);
1537
1538 sb.append(del);
1539 sb.append(path);
1540 del = ", ";
1541 }
1542 }
1543 return sb.toString();
1544 }
1545
Stuart McCulloch4482c702012-06-15 13:27:53 +00001546 public void putAll(Map<String,String> additional, boolean force) {
1547 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1548 Map.Entry<String,String> entry = i.next();
Stuart McCullochf3173222012-06-07 21:57:32 +00001549 if (force || getProperties().get(entry.getKey()) == null)
1550 setProperty(entry.getKey(), entry.getValue());
1551 }
1552 }
1553
1554 boolean firstUse = true;
1555
1556 public List<Jar> getClasspath() {
1557 if (firstUse) {
1558 firstUse = false;
1559 String cp = getProperty(CLASSPATH);
1560 if (cp != null)
1561 for (String s : split(cp)) {
1562 Jar jar = getJarFromName(s, "getting classpath");
1563 if (jar != null)
1564 addClasspath(jar);
1565 else
1566 warning("Cannot find entry on -classpath: %s", s);
1567 }
1568 }
1569 return classpath;
1570 }
1571
1572 public void addClasspath(Jar jar) {
1573 if (isPedantic() && jar.getResources().isEmpty())
1574 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1575
1576 classpath.add(jar);
1577 }
1578
1579 public void addClasspath(Collection< ? > jars) throws IOException {
1580 for (Object jar : jars) {
1581 if (jar instanceof Jar)
1582 addClasspath((Jar) jar);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001583 else if (jar instanceof File)
1584 addClasspath((File) jar);
1585 else if (jar instanceof String)
1586 addClasspath(getFile((String) jar));
Stuart McCullochf3173222012-06-07 21:57:32 +00001587 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001588 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001589 }
1590 }
1591
1592 public void addClasspath(File cp) throws IOException {
1593 if (!cp.exists())
1594 warning("File on classpath that does not exist: " + cp);
1595 Jar jar = new Jar(cp);
1596 addClose(jar);
1597 classpath.add(jar);
1598 }
1599
1600 public void clear() {
1601 classpath.clear();
1602 }
1603
1604 public Jar getTarget() {
1605 return dot;
1606 }
1607
1608 private void analyzeBundleClasspath() throws Exception {
1609 Parameters bcp = getBundleClasspath();
1610
1611 if (bcp.isEmpty()) {
1612 analyzeJar(dot, "", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001613 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001614 boolean okToIncludeDirs = true;
1615
1616 for (String path : bcp.keySet()) {
1617 if (dot.getDirectories().containsKey(path)) {
1618 okToIncludeDirs = false;
1619 break;
1620 }
1621 }
1622
1623 for (String path : bcp.keySet()) {
1624 Attrs info = bcp.get(path);
1625
1626 if (path.equals(".")) {
1627 analyzeJar(dot, "", okToIncludeDirs);
1628 continue;
1629 }
1630 //
1631 // There are 3 cases:
1632 // - embedded JAR file
1633 // - directory
1634 // - error
1635 //
1636
1637 Resource resource = dot.getResource(path);
1638 if (resource != null) {
1639 try {
1640 Jar jar = new Jar(path);
1641 addClose(jar);
1642 EmbeddedResource.build(jar, resource);
1643 analyzeJar(jar, "", true);
1644 }
1645 catch (Exception e) {
1646 warning("Invalid bundle classpath entry: " + path + " " + e);
1647 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001648 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001649 if (dot.getDirectories().containsKey(path)) {
1650 // if directories are used, we should not have dot as we
1651 // would have the classes in these directories on the
1652 // class path twice.
1653 if (bcp.containsKey("."))
1654 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1655 path, path);
1656 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001657 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001658 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1659 warning("No sub JAR or directory " + path);
1660 }
1661 }
1662 }
1663
1664 }
1665 }
1666
1667 /**
1668 * We traverse through all the classes that we can find and calculate the
1669 * contained and referred set and uses. This method ignores the Bundle
1670 * classpath.
1671 *
1672 * @param jar
1673 * @param contained
1674 * @param referred
1675 * @param uses
1676 * @throws IOException
1677 */
1678 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001679 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochf3173222012-06-07 21:57:32 +00001680
1681 next: for (String path : jar.getResources().keySet()) {
1682 if (path.startsWith(prefix)) {
1683
1684 String relativePath = path.substring(prefix.length());
1685
1686 if (okToIncludeDirs) {
1687 int n = relativePath.lastIndexOf('/');
1688 if (n < 0)
1689 n = relativePath.length();
1690 String relativeDir = relativePath.substring(0, n);
1691
1692 PackageRef packageRef = getPackageRef(relativeDir);
1693 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1694 contained.put(packageRef);
1695
1696 // For each package we encounter for the first
1697 // time. Unfortunately we can only do this once
1698 // we found a class since the bcp has a tendency
1699 // to overlap
1700 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001701 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001702 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001703 }
1704 }
1705 }
1706
1707 // Check class resources, we need to analyze them
1708 if (path.endsWith(".class")) {
1709 Resource resource = jar.getResource(path);
1710 Clazz clazz;
1711 Attrs info = null;
1712
1713 try {
1714 InputStream in = resource.openInputStream();
1715 clazz = new Clazz(this, path, resource);
1716 try {
1717 // Check if we have a package-info
1718 if (relativePath.endsWith("/package-info.class")) {
1719 // package-info can contain an Export annotation
1720 info = new Attrs();
1721 parsePackageInfoClass(clazz, info);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001722 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001723 // Otherwise we just parse it simply
1724 clazz.parseClassFile();
1725 }
1726 }
1727 finally {
1728 in.close();
1729 }
1730 }
1731 catch (Throwable e) {
1732 error("Invalid class file %s (%s)", e, relativePath, e);
1733 e.printStackTrace();
1734 continue next;
1735 }
1736
1737 String calculatedPath = clazz.getClassName().getPath();
1738 if (!calculatedPath.equals(relativePath)) {
1739 // If there is a mismatch we
1740 // warning
1741 if (okToIncludeDirs) // assume already reported
1742 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001743 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001744 classspace.put(clazz.getClassName(), clazz);
1745 PackageRef packageRef = clazz.getClassName().getPackageRef();
1746
1747 if (!contained.containsKey(packageRef)) {
1748 contained.put(packageRef);
1749 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001750 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001751 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001752 }
1753 }
1754 if (info != null)
1755 contained.merge(packageRef, false, info);
1756
Stuart McCullochf3173222012-06-07 21:57:32 +00001757 // Look at the referred packages
1758 // and copy them to our baseline
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001759 Set<PackageRef> refs = Create.set();
Stuart McCullochf3173222012-06-07 21:57:32 +00001760 for (PackageRef p : clazz.getReferred()) {
1761 referred.put(p);
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001762 refs.add(p);
Stuart McCullochf3173222012-06-07 21:57:32 +00001763 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001764 refs.remove(packageRef);
1765 uses.addAll(packageRef, refs);
Stuart McCullochb32291a2012-07-16 14:10:57 +00001766
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001767 // Collect the API
1768 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochf3173222012-06-07 21:57:32 +00001769 }
1770 }
1771 }
1772 }
1773
1774 if (mismatched.size() > 0) {
1775 error("Classes found in the wrong directory: %s", mismatched);
1776 return false;
1777 }
1778 return true;
1779 }
1780
1781 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1782
1783 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1784 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1785 @Override
1786 public void annotation(Annotation a) {
1787 String name = a.name.getFQN();
1788 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1789
1790 // Check version
1791 String version = a.get("value");
1792 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1793 if (version != null) {
1794 version = getReplacer().process(version);
1795 if (Verifier.VERSION.matcher(version).matches())
1796 info.put(VERSION_ATTRIBUTE, version);
1797 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001798 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochf3173222012-06-07 21:57:32 +00001799 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001800 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001801 // Verify this matches with packageinfo
1802 String presentVersion = info.get(VERSION_ATTRIBUTE);
1803 try {
1804 Version av = new Version(presentVersion);
1805 Version bv = new Version(version);
1806 if (!av.equals(bv)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001807 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1808 .getClassName().getFQN());
Stuart McCullochf3173222012-06-07 21:57:32 +00001809 }
1810 }
1811 catch (Exception e) {
1812 // Ignore
1813 }
1814 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001815 } else if (name.equals(Export.class.getName())) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001816
Stuart McCulloch4482c702012-06-15 13:27:53 +00001817 // Check mandatory attributes
1818 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1819 if (!attrs.isEmpty()) {
1820 info.putAll(attrs);
1821 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1822 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001823
Stuart McCulloch4482c702012-06-15 13:27:53 +00001824 // Check optional attributes
1825 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1826 if (!attrs.isEmpty()) {
1827 info.putAll(attrs);
1828 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001829
Stuart McCulloch4482c702012-06-15 13:27:53 +00001830 // Check Included classes
1831 Object[] included = a.get(Export.INCLUDE);
1832 if (included != null && included.length > 0) {
1833 StringBuilder sb = new StringBuilder();
1834 String del = "";
1835 for (Object i : included) {
1836 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1837 if (m.matches()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001838 sb.append(del);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001839 sb.append(m.group(2));
Stuart McCullochf3173222012-06-07 21:57:32 +00001840 del = ",";
1841 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001842 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001843 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +00001844 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001845
1846 // Check Excluded classes
1847 Object[] excluded = a.get(Export.EXCLUDE);
1848 if (excluded != null && excluded.length > 0) {
1849 StringBuilder sb = new StringBuilder();
1850 String del = "";
1851 for (Object i : excluded) {
1852 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1853 if (m.matches()) {
1854 sb.append(del);
1855 sb.append(m.group(2));
1856 del = ",";
1857 }
1858 }
1859 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1860 }
1861
1862 // Check Uses
1863 Object[] uses = a.get(Export.USES);
1864 if (uses != null && uses.length > 0) {
1865 String old = info.get(USES_DIRECTIVE);
1866 if (old == null)
1867 old = "";
1868 StringBuilder sb = new StringBuilder(old);
1869 String del = sb.length() == 0 ? "" : ",";
1870
1871 for (Object use : uses) {
1872 sb.append(del);
1873 sb.append(use);
1874 del = ",";
1875 }
1876 info.put(USES_DIRECTIVE, sb.toString());
1877 }
1878 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001879 }
1880
1881 });
1882 }
1883
1884 /**
1885 * Clean up version parameters. Other builders use more fuzzy definitions of
1886 * the version syntax. This method cleans up such a version to match an OSGi
1887 * version.
1888 *
1889 * @param VERSION_STRING
1890 * @return
1891 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001892 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1893 Pattern.DOTALL);
1894 static Pattern fuzzyVersionRange = Pattern.compile(
1895 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1896 Pattern.DOTALL);
Stuart McCullochf3173222012-06-07 21:57:32 +00001897 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1898
1899 static Pattern nummeric = Pattern.compile("\\d*");
1900
1901 static public String cleanupVersion(String version) {
1902 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1903
1904 if (m.matches()) {
1905 return version;
1906 }
1907
1908 m = fuzzyVersionRange.matcher(version);
1909 if (m.matches()) {
1910 String prefix = m.group(1);
1911 String first = m.group(2);
1912 String last = m.group(3);
1913 String suffix = m.group(4);
1914 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1915 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001916
Stuart McCulloch4482c702012-06-15 13:27:53 +00001917 m = fuzzyVersion.matcher(version);
1918 if (m.matches()) {
1919 StringBuilder result = new StringBuilder();
1920 String major = removeLeadingZeroes(m.group(1));
1921 String minor = removeLeadingZeroes(m.group(3));
1922 String micro = removeLeadingZeroes(m.group(5));
1923 String qualifier = m.group(7);
1924
1925 if (major != null) {
1926 result.append(major);
1927 if (minor != null) {
1928 result.append(".");
1929 result.append(minor);
1930 if (micro != null) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001931 result.append(".");
Stuart McCulloch4482c702012-06-15 13:27:53 +00001932 result.append(micro);
Stuart McCullochf3173222012-06-07 21:57:32 +00001933 if (qualifier != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001934 result.append(".");
Stuart McCullochf3173222012-06-07 21:57:32 +00001935 cleanupModifier(result, qualifier);
1936 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001937 } else if (qualifier != null) {
1938 result.append(".0.");
1939 cleanupModifier(result, qualifier);
1940 }
1941 } else if (qualifier != null) {
1942 result.append(".0.0.");
1943 cleanupModifier(result, qualifier);
Stuart McCullochf3173222012-06-07 21:57:32 +00001944 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001945 return result.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +00001946 }
1947 }
1948 return version;
1949 }
1950
1951 private static String removeLeadingZeroes(String group) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001952 if (group == null)
1953 return null;
1954
Stuart McCullochf3173222012-06-07 21:57:32 +00001955 int n = 0;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001956 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochf3173222012-06-07 21:57:32 +00001957 n++;
1958 if (n == 0)
1959 return group;
1960
1961 return group.substring(n);
1962 }
1963
1964 static void cleanupModifier(StringBuilder result, String modifier) {
1965 Matcher m = fuzzyModifier.matcher(modifier);
1966 if (m.matches())
1967 modifier = m.group(2);
1968
1969 for (int i = 0; i < modifier.length(); i++) {
1970 char c = modifier.charAt(i);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001971 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochf3173222012-06-07 21:57:32 +00001972 result.append(c);
1973 }
1974 }
1975
1976 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
1977 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
1978
Stuart McCullochf3173222012-06-07 21:57:32 +00001979 public String getVersionPolicy(boolean implemented) {
1980 if (implemented) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001981 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00001982 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001983
Stuart McCulloch669423b2012-06-26 16:34:24 +00001984 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00001985 }
1986
1987 /**
1988 * The extends macro traverses all classes and returns a list of class names
1989 * that extend a base class.
1990 */
1991
1992 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";
1993
1994 public String _classes(String... args) throws Exception {
1995 // Macro.verifyCommand(args, _classesHelp, new
1996 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
1997 // null}, 3,3);
1998
1999 Collection<Clazz> matched = getClasses(args);
2000 if (matched.isEmpty())
2001 return "";
2002
2003 return join(matched);
2004 }
2005
2006 public Collection<Clazz> getClasses(String... args) throws Exception {
2007
2008 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2009 for (int i = 1; i < args.length; i++) {
2010 if (args.length < i + 1)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002011 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2012 + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002013
2014 String typeName = args[i];
2015 if (typeName.equalsIgnoreCase("extending"))
2016 typeName = "extends";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002017 else if (typeName.equalsIgnoreCase("importing"))
2018 typeName = "imports";
2019 else if (typeName.equalsIgnoreCase("implementing"))
2020 typeName = "implements";
Stuart McCullochf3173222012-06-07 21:57:32 +00002021
2022 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2023
2024 if (type == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002025 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002026
2027 Instruction instr = null;
2028 if (Clazz.HAS_ARGUMENT.contains(type)) {
2029 String s = args[++i];
2030 instr = new Instruction(s);
2031 }
2032 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2033 Clazz clazz = c.next();
2034 if (!clazz.is(type, instr, this)) {
2035 c.remove();
2036 }
2037 }
2038 }
2039 return matched;
2040 }
2041
2042 /**
2043 * Get the exporter of a package ...
2044 */
2045
2046 public String _exporters(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002047 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochf3173222012-06-07 21:57:32 +00002048 null, 2, 2);
2049 StringBuilder sb = new StringBuilder();
2050 String del = "";
2051 String pack = args[1].replace('.', '/');
2052 for (Jar jar : classpath) {
2053 if (jar.getDirectories().containsKey(pack)) {
2054 sb.append(del);
2055 sb.append(jar.getName());
2056 }
2057 }
2058 return sb.toString();
2059 }
2060
Stuart McCulloch4482c702012-06-15 13:27:53 +00002061 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochf3173222012-06-07 21:57:32 +00002062 return classspace;
2063 }
2064
2065 /**
2066 * Locate a resource on the class path.
2067 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002068 * @param path
2069 * Path of the reosurce
Stuart McCullochf3173222012-06-07 21:57:32 +00002070 * @return A resource or <code>null</code>
2071 */
2072 public Resource findResource(String path) {
2073 for (Jar entry : getClasspath()) {
2074 Resource r = entry.getResource(path);
2075 if (r != null)
2076 return r;
2077 }
2078 return null;
2079 }
2080
2081 /**
2082 * Find a clazz on the class path. This class has been parsed.
2083 *
2084 * @param path
2085 * @return
2086 */
2087 public Clazz findClass(TypeRef typeRef) throws Exception {
2088 Clazz c = classspace.get(typeRef);
2089 if (c != null)
2090 return c;
2091
2092 c = importedClassesCache.get(typeRef);
2093 if (c != null)
2094 return c;
2095
2096 Resource r = findResource(typeRef.getPath());
2097 if (r == null) {
2098 getClass().getClassLoader();
2099 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2100 if (url != null)
2101 r = new URLResource(url);
2102 }
2103 if (r != null) {
2104 c = new Clazz(this, typeRef.getPath(), r);
2105 c.parseClassFile();
2106 importedClassesCache.put(typeRef, c);
2107 }
2108 return c;
2109 }
2110
2111 /**
2112 * Answer the bundle version.
2113 *
2114 * @return
2115 */
2116 public String getVersion() {
2117 String version = getProperty(BUNDLE_VERSION);
2118 if (version == null)
2119 version = "0.0.0";
2120 return version;
2121 }
2122
2123 public boolean isNoBundle() {
2124 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2125 }
2126
2127 public void referTo(TypeRef ref) {
2128 PackageRef pack = ref.getPackageRef();
2129 if (!referred.containsKey(pack))
2130 referred.put(pack, new Attrs());
2131 }
2132
2133 public void referToByBinaryName(String binaryClassName) {
2134 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2135 referTo(ref);
2136 }
2137
2138 /**
2139 * Ensure that we are running on the correct bnd.
2140 */
2141 void doRequireBnd() {
2142 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2143 if (require == null || require.isEmpty())
2144 return;
2145
Stuart McCulloch4482c702012-06-15 13:27:53 +00002146 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +00002147 map.put(Constants.VERSION_FILTER, getBndVersion());
2148
2149 for (String filter : require.keySet()) {
2150 try {
2151 Filter f = new Filter(filter);
2152 if (f.match(map))
2153 continue;
2154 error("%s fails %s", REQUIRE_BND, require.get(filter));
2155 }
2156 catch (Exception t) {
2157 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2158 }
2159 }
2160 }
2161
2162 /**
2163 * md5 macro
2164 */
2165
2166 static String _md5Help = "${md5;path}";
2167
2168 public String _md5(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002169 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2170 null, null, Pattern.compile("base64|hex")
2171 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002172
2173 Digester<MD5> digester = MD5.getDigester();
2174 Resource r = dot.getResource(args[1]);
2175 if (r == null)
2176 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2177
2178 IO.copy(r.openInputStream(), digester);
2179 boolean hex = args.length > 2 && args[2].equals("hex");
2180 if (hex)
2181 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch4482c702012-06-15 13:27:53 +00002182
2183 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochf3173222012-06-07 21:57:32 +00002184 }
2185
2186 /**
2187 * SHA1 macro
2188 */
2189
2190 static String _sha1Help = "${sha1;path}";
2191
2192 public String _sha1(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002193 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2194 null, null, Pattern.compile("base64|hex")
2195 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002196 Digester<SHA1> digester = SHA1.getDigester();
2197 Resource r = dot.getResource(args[1]);
2198 if (r == null)
2199 throw new FileNotFoundException("From sha1, not found " + args[1]);
2200
2201 IO.copy(r.openInputStream(), digester);
2202 return Base64.encodeBase64(digester.digest().digest());
2203 }
2204
2205 public Descriptor getDescriptor(String descriptor) {
2206 return descriptors.getDescriptor(descriptor);
2207 }
2208
2209 public TypeRef getTypeRef(String binaryClassName) {
2210 return descriptors.getTypeRef(binaryClassName);
2211 }
2212
2213 public PackageRef getPackageRef(String binaryName) {
2214 return descriptors.getPackageRef(binaryName);
2215 }
2216
2217 public TypeRef getTypeRefFromFQN(String fqn) {
2218 return descriptors.getTypeRefFromFQN(fqn);
2219 }
2220
2221 public TypeRef getTypeRefFromPath(String path) {
2222 return descriptors.getTypeRefFromPath(path);
2223 }
2224
2225 public boolean isImported(PackageRef packageRef) {
2226 return imports.containsKey(packageRef);
2227 }
2228
2229 /**
2230 * Merge the attributes of two maps, where the first map can contain
2231 * wildcarded names. The idea is that the first map contains instructions
2232 * (for example *) with a set of attributes. These patterns are matched
2233 * against the found packages in actual. If they match, the result is set
2234 * with the merged set of attributes. It is expected that the instructions
2235 * are ordered so that the instructor can define which pattern matches
2236 * first. Attributes in the instructions override any attributes from the
2237 * actual.<br/>
Stuart McCullochf3173222012-06-07 21:57:32 +00002238 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2239 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2240 * if the pattern starts with an exclamation mark, it will remove that
2241 * matches for that pattern (- the !) from the working set. So the following
2242 * patterns should work:
2243 * <ul>
2244 * <li>com.foo.bar</li>
2245 * <li>com.foo.*</li>
2246 * <li>com.foo.???</li>
2247 * <li>com.*.[^b][^a][^r]</li>
2248 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2249 * </ul>
2250 * Enough rope to hang the average developer I would say.
2251 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002252 * @param instructions
2253 * the instructions with patterns.
2254 * @param source
2255 * the actual found packages, contains no duplicates
Stuart McCullochf3173222012-06-07 21:57:32 +00002256 * @return Only the packages that were filtered by the given instructions
2257 */
2258
2259 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2260 Packages result = new Packages();
2261 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2262 Collections.sort(refs);
2263
2264 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2265 if (nomatch == null)
2266 nomatch = Create.set();
2267
2268 for (Instruction instruction : filters) {
2269 boolean match = false;
2270
2271 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2272 PackageRef packageRef = i.next();
2273
2274 if (packageRef.isMetaData()) {
2275 i.remove(); // no use checking it again
2276 continue;
2277 }
2278
2279 String packageName = packageRef.getFQN();
2280
2281 if (instruction.matches(packageName)) {
2282 match = true;
2283 if (!instruction.isNegated()) {
2284 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2285 instructions.get(instruction));
2286 }
2287 i.remove(); // Can never match again for another pattern
2288 }
2289 }
2290 if (!match && !instruction.isAny())
2291 nomatch.add(instruction);
2292 }
2293
2294 /*
2295 * Tricky. If we have umatched instructions they might indicate that we
2296 * want to have multiple decorators for the same package. So we check
2297 * the unmatched against the result list. If then then match and have
2298 * actually interesting properties then we merge them
2299 */
2300
2301 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2302 Instruction instruction = i.next();
2303
2304 // We assume the user knows what he is
2305 // doing and inserted a literal. So
2306 // we ignore any not matched literals
2307 if (instruction.isLiteral()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002308 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochf3173222012-06-07 21:57:32 +00002309 i.remove();
2310 continue;
2311 }
2312
2313 // Not matching a negated instruction looks
2314 // like an error ...
2315 if (instruction.isNegated()) {
2316 continue;
2317 }
2318
2319 // An optional instruction should not generate
2320 // an error
2321 if (instruction.isOptional()) {
2322 i.remove();
2323 continue;
2324 }
2325
2326 // boolean matched = false;
2327 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2328 // for (PackageRef ref : prefs) {
2329 // if (instruction.matches(ref.getFQN())) {
2330 // result.merge(ref, true, source.get(ref),
2331 // instructions.get(instruction));
2332 // matched = true;
2333 // }
2334 // }
2335 // if (matched)
2336 // i.remove();
2337 }
2338 return result;
2339 }
2340
2341 public void setDiagnostics(boolean b) {
2342 diagnostics = b;
2343 }
2344
2345 public Clazz.JAVA getLowestEE() {
2346 if (ees.isEmpty())
2347 return Clazz.JAVA.JDK1_4;
2348
2349 return ees.first();
2350 }
2351
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002352 public String _ee(@SuppressWarnings("unused")
2353 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00002354 return getLowestEE().getEE();
2355 }
2356
2357 /**
2358 * Calculate the output file for the given target. The strategy is:
2359 *
2360 * <pre>
2361 * parameter given if not null and not directory
2362 * if directory, this will be the output directory
2363 * based on bsn-version.jar
2364 * name of the source file if exists
2365 * Untitled-[n]
2366 * </pre>
2367 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002368 * @param output
2369 * may be null, otherwise a file path relative to base
Stuart McCullochf3173222012-06-07 21:57:32 +00002370 */
2371 public File getOutputFile(String output) {
2372
2373 if (output == null)
2374 output = get(Constants.OUTPUT);
2375
2376 File outputDir;
2377
2378 if (output != null) {
2379 File outputFile = getFile(output);
2380 if (outputFile.isDirectory())
2381 outputDir = outputFile;
2382 else
2383 return outputFile;
Stuart McCulloch4482c702012-06-15 13:27:53 +00002384 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002385 outputDir = getBase();
2386
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002387 Entry<String,Attrs> name = getBundleSymbolicName();
2388 if (name != null) {
2389 String bsn = name.getKey();
Stuart McCullochf3173222012-06-07 21:57:32 +00002390 String version = getBundleVersion();
2391 Version v = Version.parseVersion(version);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002392 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochf3173222012-06-07 21:57:32 +00002393 return new File(outputDir, outputName);
2394 }
2395
2396 File source = getJar().getSource();
2397 if (source != null) {
2398 String outputName = source.getName();
2399 return new File(outputDir, outputName);
2400 }
2401
Stuart McCulloch4482c702012-06-15 13:27:53 +00002402 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochf3173222012-06-07 21:57:32 +00002403 int n = 0;
2404 File f = getFile(outputDir, "Untitled");
2405 while (f.isFile()) {
2406 f = getFile(outputDir, "Untitled-" + n++);
2407 }
2408 return f;
2409 }
2410
2411 /**
2412 * Utility function to carefully save the file. Will create a backup if the
2413 * source file has the same path as the output. It will also only save if
2414 * the file was modified or the force flag is true
2415 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002416 * @param output
2417 * the output file, if null {@link #getOutputFile(String)} is
2418 * used.
2419 * @param force
2420 * if it needs to be overwritten
Stuart McCullochf3173222012-06-07 21:57:32 +00002421 * @throws Exception
2422 */
2423
2424 public boolean save(File output, boolean force) throws Exception {
2425 if (output == null)
2426 output = getOutputFile(null);
2427
2428 Jar jar = getJar();
2429 File source = jar.getSource();
2430
Stuart McCulloch4482c702012-06-15 13:27:53 +00002431 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2432 jar.lastModified() - output.lastModified());
Stuart McCullochf3173222012-06-07 21:57:32 +00002433
2434 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
2435 output.getParentFile().mkdirs();
2436 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2437 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2438 if (!source.renameTo(bak)) {
2439 error("Could not create backup file %s", bak);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002440 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002441 source.delete();
2442 }
2443 try {
2444 trace("Saving jar to %s", output);
2445 getJar().write(output);
2446 }
2447 catch (Exception e) {
2448 output.delete();
2449 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2450 }
2451 return true;
2452 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002453 trace("Not modified %s", output);
2454 return false;
2455
Stuart McCullochf3173222012-06-07 21:57:32 +00002456 }
2457
2458 /**
2459 * Set default import and export instructions if none are set
2460 */
2461 public void setDefaults(String bsn, Version version) {
2462 if (getExportPackage() == null)
2463 setExportPackage("*");
2464 if (getImportPackage() == null)
2465 setExportPackage("*");
2466 if (bsn != null && getBundleSymbolicName() == null)
2467 setBundleSymbolicName(bsn);
2468 if (version != null && getBundleVersion() == null)
2469 setBundleVersion(version);
2470 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002471
2472 /**
Stuart McCullochb32291a2012-07-16 14:10:57 +00002473 * Remove the own references and optional java references from the uses lib
2474 *
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002475 * @param apiUses
2476 * @param removeJava
2477 * @return
2478 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00002479 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002480 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002481 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002482 e.getValue().remove(e.getKey());
2483 if (!removeJava)
2484 continue;
Stuart McCullochb32291a2012-07-16 14:10:57 +00002485
2486 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2487 if (i.next().isJava())
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002488 i.remove();
2489 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002490 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002491 return map;
2492 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002493
2494 /**
2495 * Return the classes for a given source package.
2496 *
2497 * @param source
2498 * the source package
2499 * @return a set of classes for the requested package.
2500 */
2501 public Set<Clazz> getClassspace(PackageRef source) {
2502 Set<Clazz> result = new HashSet<Clazz>();
2503 for (Clazz c : getClassspace().values()) {
2504 if (c.getClassName().getPackageRef() == source)
2505 result.add(c);
2506 }
2507 return result;
2508 }
2509
2510 /**
2511 * Create a cross reference from package source, to packages in dest
2512 * @param source
2513 * @param dest
2514 * @param sourceModifiers
2515 * @return
2516 * @throws Exception
2517 */
2518 public Map<Clazz.Def, List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest, final int sourceModifiers)
2519 throws Exception {
2520 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def, TypeRef>(Clazz.Def.class, TypeRef.class, true);
2521
2522 for (final Clazz clazz : getClassspace().values()) {
2523 if ((clazz.accessx & sourceModifiers) == 0)
2524 continue;
2525
2526 if (source!=null && source != clazz.getClassName().getPackageRef())
2527 continue;
2528
2529 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2530 Clazz.Def member;
2531
2532 public void extendsClass(TypeRef zuper) throws Exception {
2533 if (dest.contains(zuper.getPackageRef()))
2534 xref.add(clazz.getExtends(zuper), zuper);
2535 }
2536
2537 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2538 for (TypeRef i : interfaces) {
2539 if (dest.contains(i.getPackageRef()))
2540 xref.add(clazz.getImplements(i), i);
2541 }
2542 }
2543
2544 public void referTo(TypeRef to, int modifiers) {
2545 if (to.isJava())
2546 return;
2547
2548 if (!dest.contains(to.getPackageRef()))
2549 return;
2550
2551 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2552 xref.add(member, to);
2553 }
2554
2555 }
2556
2557 public void method(Clazz.MethodDef defined) {
2558 member = defined;
2559 }
2560
2561 public void field(Clazz.FieldDef defined) {
2562 member = defined;
2563 }
2564
2565 });
2566
2567 }
2568 return xref;
2569 }
2570
Stuart McCullochf3173222012-06-07 21:57:32 +00002571}