blob: 7548f51526ff82b78f255187468dc2f779d90fa4 [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.*;
Stuart McCullochc73903f2012-10-18 20:01:21 +000026import java.text.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000027import java.util.*;
28import java.util.Map.Entry;
29import java.util.jar.*;
30import java.util.jar.Attributes.Name;
31import java.util.regex.*;
32
33import aQute.bnd.annotation.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000034import aQute.bnd.header.*;
35import aQute.bnd.osgi.Descriptors.Descriptor;
36import aQute.bnd.osgi.Descriptors.PackageRef;
37import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochf3173222012-06-07 21:57:32 +000038import aQute.bnd.service.*;
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +000039import aQute.bnd.version.*;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000040import aQute.bnd.version.Version;
Stuart McCullochf3173222012-06-07 21:57:32 +000041import aQute.lib.base64.*;
42import aQute.lib.collections.*;
43import aQute.lib.filter.*;
44import aQute.lib.hex.*;
45import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000046import aQute.libg.cryptography.*;
47import aQute.libg.generics.*;
Stuart McCulloch4482c702012-06-15 13:27:53 +000048import aQute.libg.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000049
50public class Analyzer extends Processor {
51 private final SortedSet<Clazz.JAVA> ees = new TreeSet<Clazz.JAVA>();
Stuart McCulloch669423b2012-06-26 16:34:24 +000052 static Properties bndInfo;
Stuart McCullochf3173222012-06-07 21:57:32 +000053
54 // Bundle parameters
55 private Jar dot;
56 private final Packages contained = new Packages();
57 private final Packages referred = new Packages();
58 private Packages exports;
59 private Packages imports;
60 private TypeRef activator;
61
62 // Global parameters
Stuart McCulloch4482c702012-06-15 13:27:53 +000063 private final MultiMap<PackageRef,PackageRef> uses = new MultiMap<PackageRef,PackageRef>(
Stuart McCullochb32291a2012-07-16 14:10:57 +000064 PackageRef.class, PackageRef.class,
65 true);
Stuart McCulloch42151ee2012-07-16 13:43:38 +000066 private final MultiMap<PackageRef,PackageRef> apiUses = new MultiMap<PackageRef,PackageRef>(
Stuart McCullochb32291a2012-07-16 14:10:57 +000067 PackageRef.class, PackageRef.class,
68 true);
Stuart McCullochf3173222012-06-07 21:57:32 +000069 private final Packages classpathExports = new Packages();
70 private final Descriptors descriptors = new Descriptors();
71 private final List<Jar> classpath = list();
Stuart McCulloch4482c702012-06-15 13:27:53 +000072 private final Map<TypeRef,Clazz> classspace = map();
73 private final Map<TypeRef,Clazz> importedClassesCache = map();
Stuart McCullochf3173222012-06-07 21:57:32 +000074 private boolean analyzed = false;
75 private boolean diagnostics = false;
76 private boolean inited = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +000077 final protected AnalyzerMessages msgs = ReporterMessages.base(this,
78 AnalyzerMessages.class);
Stuart McCullochf3173222012-06-07 21:57:32 +000079
80 public Analyzer(Processor parent) {
81 super(parent);
82 }
83
Stuart McCulloch4482c702012-06-15 13:27:53 +000084 public Analyzer() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000085
86 /**
87 * Specifically for Maven
88 *
Stuart McCulloch4482c702012-06-15 13:27:53 +000089 * @param properties
90 * the properties
Stuart McCullochf3173222012-06-07 21:57:32 +000091 */
92
93 public static Properties getManifest(File dirOrJar) throws Exception {
94 Analyzer analyzer = new Analyzer();
95 try {
96 analyzer.setJar(dirOrJar);
97 Properties properties = new Properties();
98 properties.put(IMPORT_PACKAGE, "*");
99 properties.put(EXPORT_PACKAGE, "*");
100 analyzer.setProperties(properties);
101 Manifest m = analyzer.calcManifest();
102 Properties result = new Properties();
103 for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i.hasNext();) {
104 Attributes.Name name = (Attributes.Name) i.next();
105 result.put(name.toString(), m.getMainAttributes().getValue(name));
106 }
107 return result;
108 }
109 finally {
110 analyzer.close();
111 }
112 }
113
114 /**
115 * Calculates the data structures for generating a manifest.
116 *
117 * @throws IOException
118 */
119 public void analyze() throws Exception {
120 if (!analyzed) {
121 analyzed = true;
122 uses.clear();
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000123 apiUses.clear();
Stuart McCullochf3173222012-06-07 21:57:32 +0000124 classspace.clear();
125 classpathExports.clear();
126
127 // Parse all the class in the
128 // the jar according to the OSGi bcp
129 analyzeBundleClasspath();
130
131 //
132 // calculate class versions in use
133 //
134 for (Clazz c : classspace.values()) {
135 ees.add(c.getFormat());
136 }
137
138 //
139 // Get exported packages from the
140 // entries on the classpath
141 //
142
143 for (Jar current : getClasspath()) {
144 getExternalExports(current, classpathExports);
145 for (String dir : current.getDirectories().keySet()) {
146 PackageRef packageRef = getPackageRef(dir);
147 Resource resource = current.getResource(dir + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +0000148 getExportVersionsFromPackageInfo(packageRef, resource, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +0000149 }
150 }
151
152 // Handle the bundle activator
153
154 String s = getProperty(BUNDLE_ACTIVATOR);
155 if (s != null) {
156 activator = getTypeRefFromFQN(s);
157 referTo(activator);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000158 trace("activator %s %s", s, activator);
Stuart McCullochf3173222012-06-07 21:57:32 +0000159 }
160
161 // Execute any plugins
162 // TODO handle better reanalyze
163 doPlugins();
164
165 Jar extra = getExtra();
166 while (extra != null) {
167 dot.addAll(extra);
168 analyzeJar(extra, "", true);
169 extra = getExtra();
170 }
171
172 referred.keySet().removeAll(contained.keySet());
173
174 //
175 // EXPORTS
176 //
177 {
178 Set<Instruction> unused = Create.set();
179
180 Instructions filter = new Instructions(getExportPackage());
181 filter.append(getExportContents());
182
183 exports = filter(filter, contained, unused);
184
185 if (!unused.isEmpty()) {
186 warning("Unused Export-Package instructions: %s ", unused);
187 }
188
189 // See what information we can find to augment the
190 // exports. I.e. look on the classpath
191 augmentExports(exports);
192 }
193
194 //
195 // IMPORTS
196 // Imports MUST come after exports because we use information from
197 // the exports
198 //
199 {
200 // Add all exports that do not have an -noimport: directive
201 // to the imports.
202 Packages referredAndExported = new Packages(referred);
203 referredAndExported.putAll(doExportsToImports(exports));
204
205 removeDynamicImports(referredAndExported);
206
207 // Remove any Java references ... where are the closures???
208 for (Iterator<PackageRef> i = referredAndExported.keySet().iterator(); i.hasNext();) {
209 if (i.next().isJava())
210 i.remove();
211 }
212
213 Set<Instruction> unused = Create.set();
214 String h = getProperty(IMPORT_PACKAGE);
215 if (h == null) // If not set use a default
216 h = "*";
217
218 if (isPedantic() && h.trim().length() == 0)
219 warning("Empty Import-Package header");
220
221 Instructions filter = new Instructions(h);
222 imports = filter(filter, referredAndExported, unused);
223 if (!unused.isEmpty()) {
224 // We ignore the end wildcard catch
225 if (!(unused.size() == 1 && unused.iterator().next().toString().equals("*")))
226 warning("Unused Import-Package instructions: %s ", unused);
227 }
228
229 // See what information we can find to augment the
230 // imports. I.e. look in the exports
231 augmentImports(imports, exports);
232 }
233
234 //
235 // USES
236 //
237 // Add the uses clause to the exports
Stuart McCullochb32291a2012-07-16 14:10:57 +0000238
239 boolean api = isTrue(getProperty(EXPERIMENTS)) || true; // brave,
240 // lets see
241
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000242 doUses(exports, api ? apiUses : uses, imports);
Stuart McCullochb32291a2012-07-16 14:10:57 +0000243
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000244 //
245 // Verify that no exported package has a reference to a private
246 // package
247 // This can cause a lot of harm.
248 // TODO restrict the check to public API only, but even then
249 // exported packages
250 // should preferably not refer to private packages.
251 //
Stuart McCullochb32291a2012-07-16 14:10:57 +0000252 Set<PackageRef> privatePackages = getPrivates();
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000253
254 // References to java are not imported so they would show up as
255 // private
256 // packages, lets kill them as well.
257
258 for (Iterator<PackageRef> p = privatePackages.iterator(); p.hasNext();)
259 if (p.next().isJava())
260 p.remove();
261
262 for (PackageRef exported : exports.keySet()) {
263 List<PackageRef> used = uses.get(exported);
264 if (used != null) {
265 Set<PackageRef> privateReferences = new HashSet<PackageRef>(apiUses.get(exported));
266 privateReferences.retainAll(privatePackages);
267 if (!privateReferences.isEmpty())
268 msgs.Export_Has_PrivateReferences_(exported, privateReferences.size(), privateReferences);
269 }
270 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000271
272 //
273 // Checks
274 //
275 if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
276 error("The default package '.' is not permitted by the Import-Package syntax. \n"
277 + " This can be caused by compile errors in Eclipse because Eclipse creates \n"
278 + "valid class files regardless of compile errors.\n"
279 + "The following package(s) import from the default package "
280 + uses.transpose().get(Descriptors.DEFAULT_PACKAGE));
281 }
282
283 }
284 }
285
286 /**
287 * Discussed with BJ and decided to kill the .
288 *
289 * @param referredAndExported
290 */
291 void removeDynamicImports(Packages referredAndExported) {
292
293 // // Remove any matching a dynamic import package instruction
294 // Instructions dynamicImports = new
295 // Instructions(getDynamicImportPackage());
296 // Collection<PackageRef> dynamic = dynamicImports.select(
297 // referredAndExported.keySet(), false);
298 // referredAndExported.keySet().removeAll(dynamic);
299 }
300
301 protected Jar getExtra() throws Exception {
302 return null;
303 }
304
305 /**
306 *
307 */
308 void doPlugins() {
309 for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
310 try {
Stuart McCulloch5515a832012-07-22 00:19:13 +0000311 Processor previous = beginHandleErrors(plugin.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +0000312 boolean reanalyze = plugin.analyzeJar(this);
Stuart McCulloch5515a832012-07-22 00:19:13 +0000313 endHandleErrors(previous);
Stuart McCullochf3173222012-06-07 21:57:32 +0000314 if (reanalyze) {
315 classspace.clear();
316 analyzeBundleClasspath();
317 }
318 }
319 catch (Exception e) {
320 error("Analyzer Plugin %s failed %s", plugin, e);
321 }
322 }
323 }
324
325 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000326 * @return
327 */
328 boolean isResourceOnly() {
329 return isTrue(getProperty(RESOURCEONLY));
330 }
331
332 /**
333 * One of the main workhorses of this class. This will analyze the current
334 * setp and calculate a new manifest according to this setup. This method
335 * will also set the manifest on the main jar dot
336 *
337 * @return
338 * @throws IOException
339 */
340 public Manifest calcManifest() throws Exception {
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000341 try {
342 analyze();
343 Manifest manifest = new Manifest();
344 Attributes main = manifest.getMainAttributes();
Stuart McCullochf3173222012-06-07 21:57:32 +0000345
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000346 main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
347 main.putValue(BUNDLE_MANIFESTVERSION, "2");
Stuart McCullochf3173222012-06-07 21:57:32 +0000348
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000349 boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
Stuart McCullochf3173222012-06-07 21:57:32 +0000350
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000351 if (!noExtraHeaders) {
352 main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
353 + ")");
354 main.putValue(TOOL, "Bnd-" + getBndVersion());
355 main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
Stuart McCullochf3173222012-06-07 21:57:32 +0000356 }
357
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000358 String exportHeader = printClauses(exports, true);
Stuart McCullochf3173222012-06-07 21:57:32 +0000359
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000360 if (exportHeader.length() > 0)
361 main.putValue(EXPORT_PACKAGE, exportHeader);
362 else
363 main.remove(EXPORT_PACKAGE);
Stuart McCullochf3173222012-06-07 21:57:32 +0000364
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000365 // Remove all the Java packages from the imports
366 if (!imports.isEmpty()) {
367 main.putValue(IMPORT_PACKAGE, printClauses(imports));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000368 } else {
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000369 main.remove(IMPORT_PACKAGE);
Stuart McCullochf3173222012-06-07 21:57:32 +0000370 }
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000371
372 Packages temp = new Packages(contained);
373 temp.keySet().removeAll(exports.keySet());
374
375 if (!temp.isEmpty())
376 main.putValue(PRIVATE_PACKAGE, printClauses(temp));
377 else
378 main.remove(PRIVATE_PACKAGE);
379
380 Parameters bcp = getBundleClasspath();
381 if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
382 main.remove(BUNDLE_CLASSPATH);
383 else
384 main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
385
386 doNamesection(dot, manifest);
387
388 for (Enumeration< ? > h = getProperties().propertyNames(); h.hasMoreElements();) {
389 String header = (String) h.nextElement();
390 if (header.trim().length() == 0) {
391 warning("Empty property set with value: " + getProperties().getProperty(header));
392 continue;
393 }
394
395 if (isMissingPlugin(header.trim())) {
396 error("Missing plugin for command %s", header);
397 }
398 if (!Character.isUpperCase(header.charAt(0))) {
399 if (header.charAt(0) == '@')
400 doNameSection(manifest, header);
401 continue;
402 }
403
404 if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE) || header.equals(IMPORT_PACKAGE))
405 continue;
406
407 if (header.equalsIgnoreCase("Name")) {
408 error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
409 continue;
410 }
411
412 if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
413 String value = getProperty(header);
414 if (value != null && main.getValue(header) == null) {
415 if (value.trim().length() == 0)
416 main.remove(header);
417 else if (value.trim().equals(EMPTY_HEADER))
418 main.putValue(header, "");
419 else
420 main.putValue(header, value);
421 }
422 } else {
423 // TODO should we report?
424 }
425 }
426
427 // Copy old values into new manifest, when they
428 // exist in the old one, but not in the new one
429 merge(manifest, dot.getManifest());
430
431 //
432 // Calculate the bundle symbolic name if it is
433 // not set.
434 // 1. set
435 // 2. name of properties file (must be != bnd.bnd)
436 // 3. name of directory, which is usualy project name
437 //
438 String bsn = getBsn();
439 if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
440 main.putValue(BUNDLE_SYMBOLICNAME, bsn);
441 }
442
443 //
444 // Use the same name for the bundle name as BSN when
445 // the bundle name is not set
446 //
447 if (main.getValue(BUNDLE_NAME) == null) {
448 main.putValue(BUNDLE_NAME, bsn);
449 }
450
451 if (main.getValue(BUNDLE_VERSION) == null)
452 main.putValue(BUNDLE_VERSION, "0");
453
454 // Remove all the headers mentioned in -removeheaders
455 Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
456 Collection<Object> result = instructions.select(main.keySet(), false);
457 main.keySet().removeAll(result);
458
459 // We should not set the manifest here, this is in general done
460 // by the caller.
461 // dot.setManifest(manifest);
462 return manifest;
Stuart McCullochf3173222012-06-07 21:57:32 +0000463 }
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000464 catch (Exception e) {
465 // This should not really happen. The code should never throw
466 // exceptions in normal situations. So if it happens we need more
467 // information. So to help diagnostics. We do a full property dump
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +0000468 throw new IllegalStateException("Calc manifest failed, state=\n" + getFlattenedProperties(), e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000469 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000470 }
471
472 /**
473 * Parse the namesection as instructions and then match them against the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000474 * current set of resources For example:
Stuart McCullochf3173222012-06-07 21:57:32 +0000475 *
476 * <pre>
477 * -namesection: *;baz=true, abc/def/bar/X.class=3
478 * </pre>
479 *
480 * The raw value of {@link Constants#NAMESECTION} is used but the values of
481 * the attributes are replaced where @ is set to the resource name. This
482 * allows macro to operate on the resource
Stuart McCullochf3173222012-06-07 21:57:32 +0000483 */
484
485 private void doNamesection(Jar dot, Manifest manifest) {
486
487 Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
488 Instructions instructions = new Instructions(namesection);
489 Set<String> resources = new HashSet<String>(dot.getResources().keySet());
490
491 //
492 // For each instruction, iterator over the resources and filter
493 // them. If a resource matches, it must be removed even if the
494 // instruction is negative. If positive, add a name section
495 // to the manifest for the given resource name. Then add all
496 // attributes from the instruction to that name section.
497 //
Stuart McCulloch4482c702012-06-15 13:27:53 +0000498 for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000499 boolean matched = false;
500
501 // For each instruction
502
503 for (Iterator<String> i = resources.iterator(); i.hasNext();) {
504 String path = i.next();
505 // For each resource
506
507 if (instr.getKey().matches(path)) {
508
509 // Instruction matches the resource
510
511 matched = true;
512 if (!instr.getKey().isNegated()) {
513
514 // Positive match, add the attributes
515
516 Attributes attrs = manifest.getAttributes(path);
517 if (attrs == null) {
518 attrs = new Attributes();
519 manifest.getEntries().put(path, attrs);
520 }
521
522 //
523 // Add all the properties from the instruction to the
524 // name section
525 //
526
Stuart McCulloch4482c702012-06-15 13:27:53 +0000527 for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000528 setProperty("@", path);
529 try {
530 String processed = getReplacer().process(property.getValue());
531 attrs.putValue(property.getKey(), processed);
532 }
533 finally {
534 unsetProperty("@");
535 }
536 }
537 }
538 i.remove();
539 }
540 }
541
542 if (!matched && resources.size() > 0)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000543 warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
Stuart McCullochf3173222012-06-07 21:57:32 +0000544 }
545
546 }
547
548 /**
549 * This method is called when the header starts with a @, signifying a name
550 * section header. The name part is defined by replacing all the @ signs to
551 * a /, removing the first and the last, and using the last part as header
552 * name:
553 *
554 * <pre>
555 * &#064;org@osgi@service@event@Implementation-Title
556 * </pre>
557 *
558 * This will be the header Implementation-Title in the
559 * org/osgi/service/event named section.
560 *
561 * @param manifest
562 * @param header
563 */
564 private void doNameSection(Manifest manifest, String header) {
565 String path = header.replace('@', '/');
566 int n = path.lastIndexOf('/');
567 // Must succeed because we start with @
568 String name = path.substring(n + 1);
569 // Skip first /
570 path = path.substring(1, n);
571 if (name.length() != 0 && path.length() != 0) {
572 Attributes attrs = manifest.getAttributes(path);
573 if (attrs == null) {
574 attrs = new Attributes();
575 manifest.getEntries().put(path, attrs);
576 }
577 attrs.putValue(name, getProperty(header));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000578 } else {
579 warning("Invalid header (starts with @ but does not seem to be for the Name section): %s", header);
Stuart McCullochf3173222012-06-07 21:57:32 +0000580 }
581 }
582
583 /**
584 * Clear the key part of a header. I.e. remove everything from the first ';'
585 *
586 * @param value
587 * @return
588 */
589 public String getBsn() {
590 String value = getProperty(BUNDLE_SYMBOLICNAME);
591 if (value == null) {
592 if (getPropertiesFile() != null)
593 value = getPropertiesFile().getName();
594
595 String projectName = getBase().getName();
596 if (value == null || value.equals("bnd.bnd")) {
597 value = projectName;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000598 } else if (value.endsWith(".bnd")) {
599 value = value.substring(0, value.length() - 4);
600 if (!value.startsWith(getBase().getName()))
601 value = projectName + "." + value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000602 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000603 }
604
605 if (value == null)
606 return "untitled";
607
608 int n = value.indexOf(';');
609 if (n > 0)
610 value = value.substring(0, n);
611 return value.trim();
612 }
613
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000614 public String _bsn(@SuppressWarnings("unused")
615 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000616 return getBsn();
617 }
618
619 /**
620 * Calculate an export header solely based on the contents of a JAR file
621 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000622 * @param bundle
623 * The jar file to analyze
Stuart McCullochf3173222012-06-07 21:57:32 +0000624 * @return
625 */
626 public String calculateExportsFromContents(Jar bundle) {
627 String ddel = "";
628 StringBuilder sb = new StringBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000629 Map<String,Map<String,Resource>> map = bundle.getDirectories();
Stuart McCullochf3173222012-06-07 21:57:32 +0000630 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
631 String directory = i.next();
632 if (directory.equals("META-INF") || directory.startsWith("META-INF/"))
633 continue;
634 if (directory.equals("OSGI-OPT") || directory.startsWith("OSGI-OPT/"))
635 continue;
636 if (directory.equals("/"))
637 continue;
638
639 if (directory.endsWith("/"))
640 directory = directory.substring(0, directory.length() - 1);
641
642 directory = directory.replace('/', '.');
643 sb.append(ddel);
644 sb.append(directory);
645 ddel = ",";
646 }
647 return sb.toString();
648 }
649
650 public Packages getContained() {
651 return contained;
652 }
653
654 public Packages getExports() {
655 return exports;
656 }
657
658 public Packages getImports() {
659 return imports;
660 }
661
Stuart McCullochb32291a2012-07-16 14:10:57 +0000662 public Set<PackageRef> getPrivates() {
663 HashSet<PackageRef> privates = new HashSet<PackageRef>(contained.keySet());
664 privates.removeAll(exports.keySet());
665 privates.removeAll(imports.keySet());
666 return privates;
667 }
668
Stuart McCullochf3173222012-06-07 21:57:32 +0000669 public Jar getJar() {
670 return dot;
671 }
672
673 public Packages getReferred() {
674 return referred;
675 }
676
677 /**
678 * Return the set of unreachable code depending on exports and the bundle
679 * activator.
680 *
681 * @return
682 */
683 public Set<PackageRef> getUnreachable() {
684 Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
685 for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
686 PackageRef packageRef = r.next();
687 removeTransitive(packageRef, unreachable);
688 }
689 if (activator != null) {
690 removeTransitive(activator.getPackageRef(), unreachable);
691 }
692 return unreachable;
693 }
694
Stuart McCullochb32291a2012-07-16 14:10:57 +0000695 public Map<PackageRef,List<PackageRef>> getUses() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000696 return uses;
697 }
698
Stuart McCullochb32291a2012-07-16 14:10:57 +0000699 public Map<PackageRef,List<PackageRef>> getAPIUses() {
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000700 return apiUses;
701 }
702
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000703 public Packages getClasspathExports() {
704 return classpathExports;
705 }
706
Stuart McCullochf3173222012-06-07 21:57:32 +0000707 /**
708 * Get the version for this bnd
709 *
710 * @return version or unknown.
711 */
712 public String getBndVersion() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000713 return getBndInfo("version", "<unknown>");
Stuart McCullochf3173222012-06-07 21:57:32 +0000714 }
715
Stuart McCullochc73903f2012-10-18 20:01:21 +0000716 static SimpleDateFormat df = new SimpleDateFormat("EEE MMM dd hh:mm:ss z yyyy");
Stuart McCullochf3173222012-06-07 21:57:32 +0000717 public long getBndLastModified() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000718 String time = getBndInfo("lastmodified", "0");
Stuart McCullochc73903f2012-10-18 20:01:21 +0000719 if ( time.matches("\\d+"))
Stuart McCullochf3173222012-06-07 21:57:32 +0000720 return Long.parseLong(time);
Stuart McCullochc73903f2012-10-18 20:01:21 +0000721
722 try {
723 Date parse = df.parse(time);
724 if ( parse != null)
725 return parse.getTime();
726 } catch( ParseException e) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000727 // Ignore
Stuart McCullochf3173222012-06-07 21:57:32 +0000728 }
729 return 0;
730 }
731
732 public String getBndInfo(String key, String defaultValue) {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000733 if (bndInfo == null) {
734 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000735 Properties bndInfoLocal = new Properties();
736 URL url = Analyzer.class.getResource("bnd.info");
737 if (url != null) {
738 InputStream in = url.openStream();
739 try {
740 bndInfoLocal.load(in);
741 }
742 finally {
743 in.close();
744 }
745 }
746 bndInfo = bndInfoLocal;
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000747 }
748 catch (Exception e) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000749 e.printStackTrace();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000750 return defaultValue;
Stuart McCullochf3173222012-06-07 21:57:32 +0000751 }
752 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000753 String value = bndInfo.getProperty(key);
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000754 if (value == null)
755 return defaultValue;
756 return value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000757 }
758
759 /**
760 * Merge the existing manifest with the instructions but do not override
761 * existing properties.
762 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000763 * @param manifest
764 * The manifest to merge with
Stuart McCullochf3173222012-06-07 21:57:32 +0000765 * @throws IOException
766 */
767 public void mergeManifest(Manifest manifest) throws IOException {
768 if (manifest != null) {
769 Attributes attributes = manifest.getMainAttributes();
770 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
771 Name name = (Name) i.next();
772 String key = name.toString();
773 // Dont want instructions
774 if (key.startsWith("-"))
775 continue;
776
777 if (getProperty(key) == null)
778 setProperty(key, attributes.getValue(name));
779 }
780 }
781 }
782
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000783 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000784 public void setBase(File file) {
785 super.setBase(file);
786 getProperties().put("project.dir", getBase().getAbsolutePath());
787 }
788
789 /**
790 * Set the classpath for this analyzer by file.
791 *
792 * @param classpath
793 * @throws IOException
794 */
795 public void setClasspath(File[] classpath) throws IOException {
796 List<Jar> list = new ArrayList<Jar>();
797 for (int i = 0; i < classpath.length; i++) {
798 if (classpath[i].exists()) {
799 Jar current = new Jar(classpath[i]);
800 list.add(current);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000801 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000802 error("Missing file on classpath: %s", classpath[i]);
803 }
804 }
805 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
806 addClasspath(i.next());
807 }
808 }
809
810 public void setClasspath(Jar[] classpath) {
811 for (int i = 0; i < classpath.length; i++) {
812 addClasspath(classpath[i]);
813 }
814 }
815
816 public void setClasspath(String[] classpath) {
817 for (int i = 0; i < classpath.length; i++) {
818 Jar jar = getJarFromName(classpath[i], " setting classpath");
819 if (jar != null)
820 addClasspath(jar);
821 }
822 }
823
824 /**
825 * Set the JAR file we are going to work in. This will read the JAR in
826 * memory.
827 *
828 * @param jar
829 * @return
830 * @throws IOException
831 */
832 public Jar setJar(File jar) throws IOException {
833 Jar jarx = new Jar(jar);
834 addClose(jarx);
835 return setJar(jarx);
836 }
837
838 /**
839 * Set the JAR directly we are going to work on.
840 *
841 * @param jar
842 * @return
843 */
844 public Jar setJar(Jar jar) {
845 if (dot != null)
846 removeClose(dot);
847
848 this.dot = jar;
849 if (dot != null)
850 addClose(dot);
851
852 return jar;
853 }
854
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000855 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000856 protected void begin() {
857 if (inited == false) {
858 inited = true;
859 super.begin();
860
861 updateModified(getBndLastModified(), "bnd last modified");
862 verifyManifestHeadersCase(getProperties());
863
864 }
865 }
866
867 /**
868 * Try to get a Jar from a file name/path or a url, or in last resort from
869 * the classpath name part of their files.
870 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000871 * @param name
872 * URL or filename relative to the base
873 * @param from
874 * Message identifying the caller for errors
Stuart McCullochf3173222012-06-07 21:57:32 +0000875 * @return null or a Jar with the contents for the name
876 */
877 Jar getJarFromName(String name, String from) {
878 File file = new File(name);
879 if (!file.isAbsolute())
880 file = new File(getBase(), name);
881
882 if (file.exists())
883 try {
884 Jar jar = new Jar(file);
885 addClose(jar);
886 return jar;
887 }
888 catch (Exception e) {
889 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
890 }
891 // It is not a file ...
892 try {
893 // Lets try a URL
894 URL url = new URL(name);
895 Jar jar = new Jar(fileName(url.getPath()));
896 addClose(jar);
897 URLConnection connection = url.openConnection();
898 InputStream in = connection.getInputStream();
899 long lastModified = connection.getLastModified();
900 if (lastModified == 0)
901 // We assume the worst :-(
902 lastModified = System.currentTimeMillis();
903 EmbeddedResource.build(jar, in, lastModified);
904 in.close();
905 return jar;
906 }
907 catch (IOException ee) {
908 // Check if we have files on the classpath
909 // that have the right name, allows us to specify those
910 // names instead of the full path.
911 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
912 Jar entry = cp.next();
913 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
914 return entry;
915 }
916 }
917 // error("Can not find jar file for " + from + ": " + name);
918 }
919 return null;
920 }
921
922 private String fileName(String path) {
923 int n = path.lastIndexOf('/');
924 if (n > 0)
925 return path.substring(n + 1);
926 return path;
927 }
928
929 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000930 * @param manifests
931 * @throws Exception
932 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000933 private void merge(Manifest result, Manifest old) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000934 if (old != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000935 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
936 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000937 Attributes.Name name = (Attributes.Name) entry.getKey();
938 String value = (String) entry.getValue();
939 if (name.toString().equalsIgnoreCase("Created-By"))
940 name = new Attributes.Name("Originally-Created-By");
941 if (!result.getMainAttributes().containsKey(name))
942 result.getMainAttributes().put(name, value);
943 }
944
945 // do not overwrite existing entries
Stuart McCulloch4482c702012-06-15 13:27:53 +0000946 Map<String,Attributes> oldEntries = old.getEntries();
947 Map<String,Attributes> newEntries = result.getEntries();
948 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
949 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000950 if (!newEntries.containsKey(entry.getKey())) {
951 newEntries.put(entry.getKey(), entry.getValue());
952 }
953 }
954 }
955 }
956
957 /**
958 * Bnd is case sensitive for the instructions so we better check people are
959 * not using an invalid case. We do allow this to set headers that should
960 * not be processed by us but should be used by the framework.
961 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000962 * @param properties
963 * Properties to verify.
Stuart McCullochf3173222012-06-07 21:57:32 +0000964 */
965
966 void verifyManifestHeadersCase(Properties properties) {
967 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
968 String header = (String) i.next();
969 for (int j = 0; j < headers.length; j++) {
970 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
971 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
972 + header + " and expecting: " + headers[j]);
973 break;
974 }
975 }
976 }
977 }
978
979 /**
980 * We will add all exports to the imports unless there is a -noimport
981 * directive specified on an export. This directive is skipped for the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000982 * manifest. We also remove any version parameter so that augmentImports can
983 * do the version policy. The following method is really tricky and evolved
984 * over time. Coming from the original background of OSGi, it was a weird
985 * idea for me to have a public package that should not be substitutable. I
986 * was so much convinced that this was the right rule that I rücksichtlos
987 * imported them all. Alas, the real world was more subtle than that. It
988 * turns out that it is not a good idea to always import. First, there must
989 * be a need to import, i.e. there must be a contained package that refers
990 * to the exported package for it to make use importing that package.
991 * Second, if an exported package refers to an internal package than it
992 * should not be imported. Additionally, it is necessary to treat the
993 * exports in groups. If an exported package refers to another exported
994 * packages than it must be in the same group. A framework can only
995 * substitute exports for imports for the whole of such a group. WHY?????
996 * Not clear anymore ...
Stuart McCullochf3173222012-06-07 21:57:32 +0000997 */
998 Packages doExportsToImports(Packages exports) {
999
1000 // private packages = contained - exported.
1001 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
1002 privatePackages.removeAll(exports.keySet());
1003
1004 // private references = ∀ p : private packages | uses(p)
1005 Set<PackageRef> privateReferences = newSet();
1006 for (PackageRef p : privatePackages) {
1007 Collection<PackageRef> uses = this.uses.get(p);
1008 if (uses != null)
1009 privateReferences.addAll(uses);
1010 }
1011
1012 // Assume we are going to export all exported packages
1013 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
1014
1015 // Remove packages that are not referenced privately
1016 toBeImported.retainAll(privateReferences);
1017
1018 // Not necessary to import anything that is already
1019 // imported in the Import-Package statement.
1020 // TODO toBeImported.removeAll(imports.keySet());
1021
1022 // Remove exported packages that are referring to
1023 // private packages.
1024 // Each exported package has a uses clause. We just use
1025 // the used packages for each exported package to find out
1026 // if it refers to an internal package.
1027 //
1028
1029 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1030 PackageRef next = i.next();
1031 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1032
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00001033 // We had an NPE on usedByExportedPackage in GF.
1034 // I guess this can happen with hard coded
1035 // imports that do not match reality ...
1036 if (usedByExportedPackage == null || usedByExportedPackage.isEmpty()) {
1037 continue;
1038 }
1039
Stuart McCullochf3173222012-06-07 21:57:32 +00001040 for (PackageRef privatePackage : privatePackages) {
1041 if (usedByExportedPackage.contains(privatePackage)) {
1042 i.remove();
1043 break;
1044 }
1045 }
1046 }
1047
1048 // Clean up attributes and generate result map
1049 Packages result = new Packages();
1050 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1051 PackageRef ep = i.next();
1052 Attrs parameters = exports.get(ep);
1053
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001054 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochf3173222012-06-07 21:57:32 +00001055 if (noimport != null && noimport.equalsIgnoreCase("true"))
1056 continue;
1057
1058 // // we can't substitute when there is no version
1059 // String version = parameters.get(VERSION_ATTRIBUTE);
1060 // if (version == null) {
1061 // if (isPedantic())
1062 // warning(
1063 // "Cannot automatically import exported package %s because it has no version defined",
1064 // ep);
1065 // continue;
1066 // }
1067
1068 parameters = new Attrs();
1069 parameters.remove(VERSION_ATTRIBUTE);
1070 result.put(ep, parameters);
1071 }
1072 return result;
1073 }
1074
1075 public boolean referred(PackageRef packageName) {
1076 // return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001077 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001078 if (!contained.getKey().equals(packageName)) {
1079 if (contained.getValue().contains(packageName))
1080 return true;
1081 }
1082 }
1083 return false;
1084 }
1085
1086 /**
Stuart McCullochf3173222012-06-07 21:57:32 +00001087 * @param jar
1088 */
1089 private void getExternalExports(Jar jar, Packages classpathExports) {
1090 try {
1091 Manifest m = jar.getManifest();
1092 if (m != null) {
1093 Domain domain = Domain.domain(m);
1094 Parameters exported = domain.getExportPackage();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001095 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001096 PackageRef ref = getPackageRef(e.getKey());
1097 if (!classpathExports.containsKey(ref)) {
1098 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1099 // jar.getBsn()+"-"+jar.getVersion());
1100
1101 classpathExports.put(ref, e.getValue());
1102 }
1103 }
1104 }
1105 }
1106 catch (Exception e) {
1107 warning("Erroneous Manifest for " + jar + " " + e);
1108 }
1109 }
1110
1111 /**
1112 * Find some more information about imports in manifest and other places. It
1113 * is assumed that the augmentsExports has already copied external attrs
1114 * from the classpathExports.
1115 *
1116 * @throws Exception
1117 */
1118 void augmentImports(Packages imports, Packages exports) throws Exception {
1119 List<PackageRef> noimports = Create.list();
1120 Set<PackageRef> provided = findProvidedPackages();
1121
1122 for (PackageRef packageRef : imports.keySet()) {
1123 String packageName = packageRef.getFQN();
1124
1125 setProperty(CURRENT_PACKAGE, packageName);
1126 try {
1127 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001128 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochf3173222012-06-07 21:57:32 +00001129
1130 String exportVersion = exportAttributes.getVersion();
1131 String importRange = importAttributes.getVersion();
1132
1133 if (exportVersion == null) {
1134 // TODO Should check if the source is from a bundle.
1135
Stuart McCulloch4482c702012-06-15 13:27:53 +00001136 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001137
1138 //
1139 // Version Policy - Import version substitution. We
1140 // calculate the export version and then allow the
1141 // import version attribute to use it in a substitution
1142 // by using a ${@} macro. The export version can
1143 // be defined externally or locally
1144 //
1145
1146 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch4482c702012-06-15 13:27:53 +00001147 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochf3173222012-06-07 21:57:32 +00001148
1149 exportVersion = cleanupVersion(exportVersion);
1150
1151 try {
1152 setProperty("@", exportVersion);
1153
1154 if (importRange != null) {
1155 importRange = cleanupVersion(importRange);
1156 importRange = getReplacer().process(importRange);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001157 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001158 importRange = getVersionPolicy(provider);
1159
1160 }
1161 finally {
1162 unsetProperty("@");
1163 }
1164 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1165 }
1166
1167 //
1168 // Check if exporter has mandatory attributes
1169 //
1170 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1171 if (mandatory != null) {
1172 String[] attrs = mandatory.split("\\s*,\\s*");
1173 for (int i = 0; i < attrs.length; i++) {
1174 if (!importAttributes.containsKey(attrs[i]))
1175 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1176 }
1177 }
1178
1179 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1180 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1181
1182 fixupAttributes(importAttributes);
1183 removeAttributes(importAttributes);
1184
1185 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1186 if (result == null)
1187 noimports.add(packageRef);
1188 }
1189 finally {
1190 unsetProperty(CURRENT_PACKAGE);
1191 }
1192 }
1193
1194 if (isPedantic() && noimports.size() != 0) {
1195 warning("Imports that lack version ranges: %s", noimports);
1196 }
1197 }
1198
1199 /**
1200 * Find the packages we depend on, where we implement an interface that is a
1201 * Provider Type. These packages, when we import them, must use the provider
1202 * policy.
1203 *
1204 * @throws Exception
1205 */
1206 Set<PackageRef> findProvidedPackages() throws Exception {
1207 Set<PackageRef> providers = Create.set();
1208 Set<TypeRef> cached = Create.set();
1209
1210 for (Clazz c : classspace.values()) {
1211 TypeRef[] interfaces = c.getInterfaces();
1212 if (interfaces != null)
1213 for (TypeRef t : interfaces)
1214 if (cached.contains(t) || isProvider(t)) {
1215 cached.add(t);
1216 providers.add(t.getPackageRef());
1217 }
1218 }
1219 return providers;
1220 }
1221
1222 private boolean isProvider(TypeRef t) throws Exception {
1223 Clazz c = findClass(t);
1224 if (c == null)
1225 return false;
1226
1227 if (c.annotations == null)
1228 return false;
1229
1230 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1231 boolean result = c.annotations.contains(pt);
1232 return result;
1233 }
1234
1235 /**
1236 * Provide any macro substitutions and versions for exported packages.
1237 */
1238
1239 void augmentExports(Packages exports) {
1240 for (PackageRef packageRef : exports.keySet()) {
1241 String packageName = packageRef.getFQN();
1242 setProperty(CURRENT_PACKAGE, packageName);
1243 try {
1244 Attrs attributes = exports.get(packageRef);
1245 Attrs exporterAttributes = classpathExports.get(packageRef);
1246 if (exporterAttributes == null)
1247 continue;
1248
Stuart McCulloch4482c702012-06-15 13:27:53 +00001249 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001250 String key = entry.getKey();
1251 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1252 key = VERSION_ATTRIBUTE;
1253
1254 // dont overwrite and no directives
1255 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1256 attributes.put(key, entry.getValue());
1257 }
1258 }
1259
1260 fixupAttributes(attributes);
1261 removeAttributes(attributes);
1262
1263 }
1264 finally {
1265 unsetProperty(CURRENT_PACKAGE);
1266 }
1267 }
1268 }
1269
1270 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001271 * Fixup Attributes Execute any macros on an export and
Stuart McCullochf3173222012-06-07 21:57:32 +00001272 */
1273
1274 void fixupAttributes(Attrs attributes) {
1275 // Convert any attribute values that have macros.
1276 for (String key : attributes.keySet()) {
1277 String value = attributes.get(key);
1278 if (value.indexOf('$') >= 0) {
1279 value = getReplacer().process(value);
1280 attributes.put(key, value);
1281 }
1282 }
1283
1284 }
1285
1286 /**
1287 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1288 * can add a remove-attribute: directive with a regular expression for
1289 * attributes that need to be removed. We also remove all attributes that
1290 * have a value of !. This allows you to use macros with ${if} to remove
1291 * values.
1292 */
1293
1294 void removeAttributes(Attrs attributes) {
1295 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1296
1297 if (remove != null) {
1298 Instructions removeInstr = new Instructions(remove);
1299 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1300 }
1301
1302 // Remove any ! valued attributes
Stuart McCulloch4482c702012-06-15 13:27:53 +00001303 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001304 String v = i.next().getValue();
1305 if (v.equals("!"))
1306 i.remove();
1307 }
1308 }
1309
1310 /**
1311 * Calculate a version from a version policy.
1312 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001313 * @param version
1314 * The actual exported version
1315 * @param impl
1316 * true for implementations and false for clients
Stuart McCullochf3173222012-06-07 21:57:32 +00001317 */
1318
1319 String calculateVersionRange(String version, boolean impl) {
1320 setProperty("@", version);
1321 try {
1322 return getVersionPolicy(impl);
1323 }
1324 finally {
1325 unsetProperty("@");
1326 }
1327 }
1328
1329 /**
1330 * Add the uses clauses. This method iterates over the exports and cal
1331 *
1332 * @param exports
1333 * @param uses
1334 * @throws MojoExecutionException
1335 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001336 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001337 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1338 return;
1339
1340 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1341 PackageRef packageRef = i.next();
1342 String packageName = packageRef.getFQN();
1343 setProperty(CURRENT_PACKAGE, packageName);
1344 try {
1345 doUses(packageRef, exports, uses, imports);
1346 }
1347 finally {
1348 unsetProperty(CURRENT_PACKAGE);
1349 }
1350
1351 }
1352 }
1353
1354 /**
1355 * @param packageName
1356 * @param exports
1357 * @param uses
1358 * @param imports
1359 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001360 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch4482c702012-06-15 13:27:53 +00001361 Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001362 Attrs clause = exports.get(packageRef);
1363
1364 // Check if someone already set the uses: directive
1365 String override = clause.get(USES_DIRECTIVE);
1366 if (override == null)
1367 override = USES_USES;
1368
1369 // Get the used packages
1370 Collection<PackageRef> usedPackages = uses.get(packageRef);
1371
1372 if (usedPackages != null) {
1373
1374 // Only do a uses on exported or imported packages
1375 // and uses should also not contain our own package
1376 // name
1377 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1378 sharedPackages.addAll(imports.keySet());
1379 sharedPackages.addAll(exports.keySet());
1380 sharedPackages.retainAll(usedPackages);
1381 sharedPackages.remove(packageRef);
1382
1383 StringBuilder sb = new StringBuilder();
1384 String del = "";
1385 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1386 PackageRef usedPackage = u.next();
1387 if (!usedPackage.isJava()) {
1388 sb.append(del);
1389 sb.append(usedPackage.getFQN());
1390 del = ",";
1391 }
1392 }
1393 if (override.indexOf('$') >= 0) {
1394 setProperty(CURRENT_USES, sb.toString());
1395 override = getReplacer().process(override);
1396 unsetProperty(CURRENT_USES);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001397 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001398 // This is for backward compatibility 0.0.287
1399 // can be deprecated over time
Stuart McCulloch4482c702012-06-15 13:27:53 +00001400 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochf3173222012-06-07 21:57:32 +00001401
1402 if (override.endsWith(","))
1403 override = override.substring(0, override.length() - 1);
1404 if (override.startsWith(","))
1405 override = override.substring(1);
1406 if (override.length() > 0) {
1407 clause.put(USES_DIRECTIVE, override);
1408 }
1409 }
1410 }
1411
1412 /**
1413 * Transitively remove all elemens from unreachable through the uses link.
1414 *
1415 * @param name
1416 * @param unreachable
1417 */
1418 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1419 if (!unreachable.contains(name))
1420 return;
1421
1422 unreachable.remove(name);
1423
1424 List<PackageRef> ref = uses.get(name);
1425 if (ref != null) {
1426 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1427 PackageRef element = r.next();
1428 removeTransitive(element, unreachable);
1429 }
1430 }
1431 }
1432
1433 /**
1434 * Helper method to set the package info resource
1435 *
1436 * @param dir
1437 * @param key
1438 * @param value
1439 * @throws Exception
1440 */
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001441 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1442 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001443 if (r == null)
1444 return;
1445
1446 Properties p = new Properties();
Stuart McCullochf3173222012-06-07 21:57:32 +00001447 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001448 InputStream in = r.openInputStream();
1449 try {
1450 p.load(in);
Stuart McCullochf3173222012-06-07 21:57:32 +00001451 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001452 finally {
1453 in.close();
1454 }
1455 Attrs map = classpathExports.get(packageRef);
1456 if (map == null) {
1457 classpathExports.put(packageRef, map = new Attrs());
1458 }
1459 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1460 String key = t.nextElement();
1461 String value = map.get(key);
1462 if (value == null) {
1463 value = p.getProperty(key);
1464
1465 // Messy, to allow directives we need to
1466 // allow the value to start with a ':' since we cannot
1467 // encode this in a property name
1468
1469 if (value.startsWith(":")) {
1470 key = key + ":";
1471 value = value.substring(1);
1472 }
1473 map.put(key, value);
1474 }
1475 }
1476 }
1477 catch (Exception e) {
1478 msgs.NoSuchFile_(r);
Stuart McCullochf3173222012-06-07 21:57:32 +00001479 }
1480 }
1481
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001482 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001483 public void close() {
1484 if (diagnostics) {
1485 PrintStream out = System.err;
1486 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1487 out.println("Classpath used");
1488 for (Jar jar : getClasspath()) {
1489 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001490 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochf3173222012-06-07 21:57:32 +00001491 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001492 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1493 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1494 Map<String,Resource> dir = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +00001495 String name = entry.getKey().replace('/', '.');
1496 if (dir != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001497 out.printf(" %-30s %d%n", name, dir.size());
1498 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001499 out.printf(" %-30s <<empty>>%n", name);
1500 }
1501 }
1502 }
1503 }
1504
1505 super.close();
1506 if (dot != null)
1507 dot.close();
1508
1509 if (classpath != null)
1510 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1511 Jar jar = j.next();
1512 jar.close();
1513 }
1514 }
1515
1516 /**
1517 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch4482c702012-06-15 13:27:53 +00001518 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1519 * )? }
Stuart McCullochf3173222012-06-07 21:57:32 +00001520 *
1521 * @param args
1522 * @return
1523 */
1524 public String _findpath(String args[]) {
1525 return findPath("findpath", args, true);
1526 }
1527
1528 public String _findname(String args[]) {
1529 return findPath("findname", args, false);
1530 }
1531
1532 String findPath(String name, String[] args, boolean fullPathName) {
1533 if (args.length > 3) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001534 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1535 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochf3173222012-06-07 21:57:32 +00001536 return null;
1537 }
1538
1539 String regexp = ".*";
1540 String replace = null;
1541
1542 switch (args.length) {
1543 case 3 :
1544 replace = args[2];
1545 //$FALL-THROUGH$
1546 case 2 :
1547 regexp = args[1];
1548 }
1549 StringBuilder sb = new StringBuilder();
1550 String del = "";
1551
1552 Pattern expr = Pattern.compile(regexp);
1553 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1554 String path = e.next();
1555 if (!fullPathName) {
1556 int n = path.lastIndexOf('/');
1557 if (n >= 0) {
1558 path = path.substring(n + 1);
1559 }
1560 }
1561
1562 Matcher m = expr.matcher(path);
1563 if (m.matches()) {
1564 if (replace != null)
1565 path = m.replaceAll(replace);
1566
1567 sb.append(del);
1568 sb.append(path);
1569 del = ", ";
1570 }
1571 }
1572 return sb.toString();
1573 }
1574
Stuart McCulloch4482c702012-06-15 13:27:53 +00001575 public void putAll(Map<String,String> additional, boolean force) {
1576 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1577 Map.Entry<String,String> entry = i.next();
Stuart McCullochf3173222012-06-07 21:57:32 +00001578 if (force || getProperties().get(entry.getKey()) == null)
1579 setProperty(entry.getKey(), entry.getValue());
1580 }
1581 }
1582
1583 boolean firstUse = true;
1584
1585 public List<Jar> getClasspath() {
1586 if (firstUse) {
1587 firstUse = false;
1588 String cp = getProperty(CLASSPATH);
1589 if (cp != null)
1590 for (String s : split(cp)) {
1591 Jar jar = getJarFromName(s, "getting classpath");
1592 if (jar != null)
1593 addClasspath(jar);
1594 else
1595 warning("Cannot find entry on -classpath: %s", s);
1596 }
1597 }
1598 return classpath;
1599 }
1600
1601 public void addClasspath(Jar jar) {
1602 if (isPedantic() && jar.getResources().isEmpty())
1603 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1604
1605 classpath.add(jar);
1606 }
1607
1608 public void addClasspath(Collection< ? > jars) throws IOException {
1609 for (Object jar : jars) {
1610 if (jar instanceof Jar)
1611 addClasspath((Jar) jar);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001612 else if (jar instanceof File)
1613 addClasspath((File) jar);
1614 else if (jar instanceof String)
1615 addClasspath(getFile((String) jar));
Stuart McCullochf3173222012-06-07 21:57:32 +00001616 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001617 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001618 }
1619 }
1620
1621 public void addClasspath(File cp) throws IOException {
1622 if (!cp.exists())
1623 warning("File on classpath that does not exist: " + cp);
1624 Jar jar = new Jar(cp);
1625 addClose(jar);
1626 classpath.add(jar);
1627 }
1628
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001629 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001630 public void clear() {
1631 classpath.clear();
1632 }
1633
1634 public Jar getTarget() {
1635 return dot;
1636 }
1637
1638 private void analyzeBundleClasspath() throws Exception {
1639 Parameters bcp = getBundleClasspath();
1640
1641 if (bcp.isEmpty()) {
1642 analyzeJar(dot, "", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001643 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001644 boolean okToIncludeDirs = true;
1645
1646 for (String path : bcp.keySet()) {
1647 if (dot.getDirectories().containsKey(path)) {
1648 okToIncludeDirs = false;
1649 break;
1650 }
1651 }
1652
1653 for (String path : bcp.keySet()) {
1654 Attrs info = bcp.get(path);
1655
1656 if (path.equals(".")) {
1657 analyzeJar(dot, "", okToIncludeDirs);
1658 continue;
1659 }
1660 //
1661 // There are 3 cases:
1662 // - embedded JAR file
1663 // - directory
1664 // - error
1665 //
1666
1667 Resource resource = dot.getResource(path);
1668 if (resource != null) {
1669 try {
1670 Jar jar = new Jar(path);
1671 addClose(jar);
1672 EmbeddedResource.build(jar, resource);
1673 analyzeJar(jar, "", true);
1674 }
1675 catch (Exception e) {
1676 warning("Invalid bundle classpath entry: " + path + " " + e);
1677 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001678 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001679 if (dot.getDirectories().containsKey(path)) {
1680 // if directories are used, we should not have dot as we
1681 // would have the classes in these directories on the
1682 // class path twice.
1683 if (bcp.containsKey("."))
1684 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1685 path, path);
1686 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001687 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001688 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1689 warning("No sub JAR or directory " + path);
1690 }
1691 }
1692 }
1693
1694 }
1695 }
1696
1697 /**
1698 * We traverse through all the classes that we can find and calculate the
1699 * contained and referred set and uses. This method ignores the Bundle
1700 * classpath.
1701 *
1702 * @param jar
1703 * @param contained
1704 * @param referred
1705 * @param uses
1706 * @throws IOException
1707 */
1708 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001709 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochf3173222012-06-07 21:57:32 +00001710
1711 next: for (String path : jar.getResources().keySet()) {
1712 if (path.startsWith(prefix)) {
1713
1714 String relativePath = path.substring(prefix.length());
1715
1716 if (okToIncludeDirs) {
1717 int n = relativePath.lastIndexOf('/');
1718 if (n < 0)
1719 n = relativePath.length();
1720 String relativeDir = relativePath.substring(0, n);
1721
1722 PackageRef packageRef = getPackageRef(relativeDir);
1723 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1724 contained.put(packageRef);
1725
1726 // For each package we encounter for the first
1727 // time. Unfortunately we can only do this once
1728 // we found a class since the bcp has a tendency
1729 // to overlap
1730 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001731 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001732 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001733 }
1734 }
1735 }
1736
1737 // Check class resources, we need to analyze them
1738 if (path.endsWith(".class")) {
1739 Resource resource = jar.getResource(path);
1740 Clazz clazz;
1741 Attrs info = null;
1742
1743 try {
1744 InputStream in = resource.openInputStream();
1745 clazz = new Clazz(this, path, resource);
1746 try {
1747 // Check if we have a package-info
1748 if (relativePath.endsWith("/package-info.class")) {
1749 // package-info can contain an Export annotation
1750 info = new Attrs();
1751 parsePackageInfoClass(clazz, info);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001752 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001753 // Otherwise we just parse it simply
1754 clazz.parseClassFile();
1755 }
1756 }
1757 finally {
1758 in.close();
1759 }
1760 }
1761 catch (Throwable e) {
1762 error("Invalid class file %s (%s)", e, relativePath, e);
1763 e.printStackTrace();
1764 continue next;
1765 }
1766
1767 String calculatedPath = clazz.getClassName().getPath();
1768 if (!calculatedPath.equals(relativePath)) {
1769 // If there is a mismatch we
1770 // warning
1771 if (okToIncludeDirs) // assume already reported
1772 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001773 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001774 classspace.put(clazz.getClassName(), clazz);
1775 PackageRef packageRef = clazz.getClassName().getPackageRef();
1776
1777 if (!contained.containsKey(packageRef)) {
1778 contained.put(packageRef);
1779 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001780 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001781 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001782 }
1783 }
1784 if (info != null)
1785 contained.merge(packageRef, false, info);
1786
Stuart McCullochf3173222012-06-07 21:57:32 +00001787 // Look at the referred packages
1788 // and copy them to our baseline
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001789 Set<PackageRef> refs = Create.set();
Stuart McCullochf3173222012-06-07 21:57:32 +00001790 for (PackageRef p : clazz.getReferred()) {
1791 referred.put(p);
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001792 refs.add(p);
Stuart McCullochf3173222012-06-07 21:57:32 +00001793 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001794 refs.remove(packageRef);
1795 uses.addAll(packageRef, refs);
Stuart McCullochb32291a2012-07-16 14:10:57 +00001796
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001797 // Collect the API
1798 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochf3173222012-06-07 21:57:32 +00001799 }
1800 }
1801 }
1802 }
1803
1804 if (mismatched.size() > 0) {
1805 error("Classes found in the wrong directory: %s", mismatched);
1806 return false;
1807 }
1808 return true;
1809 }
1810
1811 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1812
1813 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1814 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1815 @Override
1816 public void annotation(Annotation a) {
1817 String name = a.name.getFQN();
1818 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1819
1820 // Check version
1821 String version = a.get("value");
1822 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1823 if (version != null) {
1824 version = getReplacer().process(version);
1825 if (Verifier.VERSION.matcher(version).matches())
1826 info.put(VERSION_ATTRIBUTE, version);
1827 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001828 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochf3173222012-06-07 21:57:32 +00001829 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001830 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001831 // Verify this matches with packageinfo
1832 String presentVersion = info.get(VERSION_ATTRIBUTE);
1833 try {
1834 Version av = new Version(presentVersion);
1835 Version bv = new Version(version);
1836 if (!av.equals(bv)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001837 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1838 .getClassName().getFQN());
Stuart McCullochf3173222012-06-07 21:57:32 +00001839 }
1840 }
1841 catch (Exception e) {
1842 // Ignore
1843 }
1844 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001845 } else if (name.equals(Export.class.getName())) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001846
Stuart McCulloch4482c702012-06-15 13:27:53 +00001847 // Check mandatory attributes
1848 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1849 if (!attrs.isEmpty()) {
1850 info.putAll(attrs);
1851 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1852 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001853
Stuart McCulloch4482c702012-06-15 13:27:53 +00001854 // Check optional attributes
1855 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1856 if (!attrs.isEmpty()) {
1857 info.putAll(attrs);
1858 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001859
Stuart McCulloch4482c702012-06-15 13:27:53 +00001860 // Check Included classes
1861 Object[] included = a.get(Export.INCLUDE);
1862 if (included != null && included.length > 0) {
1863 StringBuilder sb = new StringBuilder();
1864 String del = "";
1865 for (Object i : included) {
1866 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1867 if (m.matches()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001868 sb.append(del);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001869 sb.append(m.group(2));
Stuart McCullochf3173222012-06-07 21:57:32 +00001870 del = ",";
1871 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001872 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001873 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +00001874 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001875
1876 // Check Excluded classes
1877 Object[] excluded = a.get(Export.EXCLUDE);
1878 if (excluded != null && excluded.length > 0) {
1879 StringBuilder sb = new StringBuilder();
1880 String del = "";
1881 for (Object i : excluded) {
1882 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1883 if (m.matches()) {
1884 sb.append(del);
1885 sb.append(m.group(2));
1886 del = ",";
1887 }
1888 }
1889 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1890 }
1891
1892 // Check Uses
1893 Object[] uses = a.get(Export.USES);
1894 if (uses != null && uses.length > 0) {
1895 String old = info.get(USES_DIRECTIVE);
1896 if (old == null)
1897 old = "";
1898 StringBuilder sb = new StringBuilder(old);
1899 String del = sb.length() == 0 ? "" : ",";
1900
1901 for (Object use : uses) {
1902 sb.append(del);
1903 sb.append(use);
1904 del = ",";
1905 }
1906 info.put(USES_DIRECTIVE, sb.toString());
1907 }
1908 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001909 }
1910
1911 });
1912 }
1913
1914 /**
1915 * Clean up version parameters. Other builders use more fuzzy definitions of
1916 * the version syntax. This method cleans up such a version to match an OSGi
1917 * version.
1918 *
1919 * @param VERSION_STRING
1920 * @return
1921 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001922 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1923 Pattern.DOTALL);
1924 static Pattern fuzzyVersionRange = Pattern.compile(
1925 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1926 Pattern.DOTALL);
Stuart McCullochf3173222012-06-07 21:57:32 +00001927 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1928
1929 static Pattern nummeric = Pattern.compile("\\d*");
1930
1931 static public String cleanupVersion(String version) {
1932 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1933
1934 if (m.matches()) {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00001935 try {
1936 VersionRange vr = new VersionRange(version);
1937 return version;
1938 } catch( Exception e) {
1939 // ignore
1940 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001941 }
1942
1943 m = fuzzyVersionRange.matcher(version);
1944 if (m.matches()) {
1945 String prefix = m.group(1);
1946 String first = m.group(2);
1947 String last = m.group(3);
1948 String suffix = m.group(4);
1949 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1950 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001951
Stuart McCulloch4482c702012-06-15 13:27:53 +00001952 m = fuzzyVersion.matcher(version);
1953 if (m.matches()) {
1954 StringBuilder result = new StringBuilder();
1955 String major = removeLeadingZeroes(m.group(1));
1956 String minor = removeLeadingZeroes(m.group(3));
1957 String micro = removeLeadingZeroes(m.group(5));
1958 String qualifier = m.group(7);
1959
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00001960 if (qualifier == null) {
1961 if (!isInteger(minor)) {
1962 qualifier = minor;
1963 minor = "0";
1964 } else if (!isInteger(micro)) {
1965 qualifier = micro;
1966 micro = "0";
1967 }
1968 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001969 if (major != null) {
1970 result.append(major);
1971 if (minor != null) {
1972 result.append(".");
1973 result.append(minor);
1974 if (micro != null) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001975 result.append(".");
Stuart McCulloch4482c702012-06-15 13:27:53 +00001976 result.append(micro);
Stuart McCullochf3173222012-06-07 21:57:32 +00001977 if (qualifier != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001978 result.append(".");
Stuart McCullochf3173222012-06-07 21:57:32 +00001979 cleanupModifier(result, qualifier);
1980 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001981 } else if (qualifier != null) {
1982 result.append(".0.");
1983 cleanupModifier(result, qualifier);
1984 }
1985 } else if (qualifier != null) {
1986 result.append(".0.0.");
1987 cleanupModifier(result, qualifier);
Stuart McCullochf3173222012-06-07 21:57:32 +00001988 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001989 return result.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +00001990 }
1991 }
1992 return version;
1993 }
1994
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00001995 /**
1996 * TRhe cleanup version got confused when people used numeric dates like
1997 * 201209091230120 as qualifiers. These are too large for Integers. This
1998 * method checks if the all digit string fits in an integer.
1999 * <pre>
2000 * maxint = 2,147,483,647 = 10 digits
2001 * </pre>
2002 * @param integer
2003 * @return if this fits in an integer
2004 */
2005 private static boolean isInteger(String minor) {
2006 return minor.length() < 10 || (minor.length() == 10 && minor.compareTo("2147483647") < 0);
2007 }
2008
Stuart McCullochf3173222012-06-07 21:57:32 +00002009 private static String removeLeadingZeroes(String group) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002010 if (group == null)
Stuart McCullochc990fb02012-11-22 00:42:55 +00002011 return "0";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002012
Stuart McCullochf3173222012-06-07 21:57:32 +00002013 int n = 0;
Stuart McCulloch4482c702012-06-15 13:27:53 +00002014 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochf3173222012-06-07 21:57:32 +00002015 n++;
2016 if (n == 0)
2017 return group;
2018
2019 return group.substring(n);
2020 }
2021
2022 static void cleanupModifier(StringBuilder result, String modifier) {
2023 Matcher m = fuzzyModifier.matcher(modifier);
2024 if (m.matches())
2025 modifier = m.group(2);
2026
2027 for (int i = 0; i < modifier.length(); i++) {
2028 char c = modifier.charAt(i);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002029 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochf3173222012-06-07 21:57:32 +00002030 result.append(c);
2031 }
2032 }
2033
2034 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
2035 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
2036
Stuart McCullochf3173222012-06-07 21:57:32 +00002037 public String getVersionPolicy(boolean implemented) {
2038 if (implemented) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00002039 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002040 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002041
Stuart McCulloch669423b2012-06-26 16:34:24 +00002042 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002043 }
2044
2045 /**
2046 * The extends macro traverses all classes and returns a list of class names
2047 * that extend a base class.
2048 */
2049
2050 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";
2051
2052 public String _classes(String... args) throws Exception {
2053 // Macro.verifyCommand(args, _classesHelp, new
2054 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
2055 // null}, 3,3);
2056
2057 Collection<Clazz> matched = getClasses(args);
2058 if (matched.isEmpty())
2059 return "";
2060
2061 return join(matched);
2062 }
2063
2064 public Collection<Clazz> getClasses(String... args) throws Exception {
2065
2066 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2067 for (int i = 1; i < args.length; i++) {
2068 if (args.length < i + 1)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002069 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2070 + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002071
2072 String typeName = args[i];
2073 if (typeName.equalsIgnoreCase("extending"))
2074 typeName = "extends";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002075 else if (typeName.equalsIgnoreCase("importing"))
2076 typeName = "imports";
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002077 else if (typeName.equalsIgnoreCase("annotation"))
2078 typeName = "annotated";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002079 else if (typeName.equalsIgnoreCase("implementing"))
2080 typeName = "implements";
Stuart McCullochf3173222012-06-07 21:57:32 +00002081
2082 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2083
2084 if (type == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002085 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002086
2087 Instruction instr = null;
2088 if (Clazz.HAS_ARGUMENT.contains(type)) {
2089 String s = args[++i];
2090 instr = new Instruction(s);
2091 }
2092 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2093 Clazz clazz = c.next();
2094 if (!clazz.is(type, instr, this)) {
2095 c.remove();
2096 }
2097 }
2098 }
2099 return matched;
2100 }
2101
2102 /**
2103 * Get the exporter of a package ...
2104 */
2105
2106 public String _exporters(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002107 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochf3173222012-06-07 21:57:32 +00002108 null, 2, 2);
2109 StringBuilder sb = new StringBuilder();
2110 String del = "";
2111 String pack = args[1].replace('.', '/');
2112 for (Jar jar : classpath) {
2113 if (jar.getDirectories().containsKey(pack)) {
2114 sb.append(del);
2115 sb.append(jar.getName());
2116 }
2117 }
2118 return sb.toString();
2119 }
2120
Stuart McCulloch4482c702012-06-15 13:27:53 +00002121 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochf3173222012-06-07 21:57:32 +00002122 return classspace;
2123 }
2124
2125 /**
2126 * Locate a resource on the class path.
2127 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002128 * @param path
2129 * Path of the reosurce
Stuart McCullochf3173222012-06-07 21:57:32 +00002130 * @return A resource or <code>null</code>
2131 */
2132 public Resource findResource(String path) {
2133 for (Jar entry : getClasspath()) {
2134 Resource r = entry.getResource(path);
2135 if (r != null)
2136 return r;
2137 }
2138 return null;
2139 }
2140
2141 /**
2142 * Find a clazz on the class path. This class has been parsed.
2143 *
2144 * @param path
2145 * @return
2146 */
2147 public Clazz findClass(TypeRef typeRef) throws Exception {
2148 Clazz c = classspace.get(typeRef);
2149 if (c != null)
2150 return c;
2151
2152 c = importedClassesCache.get(typeRef);
2153 if (c != null)
2154 return c;
2155
2156 Resource r = findResource(typeRef.getPath());
2157 if (r == null) {
2158 getClass().getClassLoader();
2159 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2160 if (url != null)
2161 r = new URLResource(url);
2162 }
2163 if (r != null) {
2164 c = new Clazz(this, typeRef.getPath(), r);
2165 c.parseClassFile();
2166 importedClassesCache.put(typeRef, c);
2167 }
2168 return c;
2169 }
2170
2171 /**
2172 * Answer the bundle version.
2173 *
2174 * @return
2175 */
2176 public String getVersion() {
2177 String version = getProperty(BUNDLE_VERSION);
2178 if (version == null)
2179 version = "0.0.0";
2180 return version;
2181 }
2182
2183 public boolean isNoBundle() {
2184 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2185 }
2186
2187 public void referTo(TypeRef ref) {
2188 PackageRef pack = ref.getPackageRef();
2189 if (!referred.containsKey(pack))
2190 referred.put(pack, new Attrs());
2191 }
2192
2193 public void referToByBinaryName(String binaryClassName) {
2194 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2195 referTo(ref);
2196 }
2197
2198 /**
2199 * Ensure that we are running on the correct bnd.
2200 */
2201 void doRequireBnd() {
2202 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2203 if (require == null || require.isEmpty())
2204 return;
2205
Stuart McCulloch4482c702012-06-15 13:27:53 +00002206 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +00002207 map.put(Constants.VERSION_FILTER, getBndVersion());
2208
2209 for (String filter : require.keySet()) {
2210 try {
2211 Filter f = new Filter(filter);
2212 if (f.match(map))
2213 continue;
2214 error("%s fails %s", REQUIRE_BND, require.get(filter));
2215 }
2216 catch (Exception t) {
2217 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2218 }
2219 }
2220 }
2221
2222 /**
2223 * md5 macro
2224 */
2225
2226 static String _md5Help = "${md5;path}";
2227
2228 public String _md5(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002229 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2230 null, null, Pattern.compile("base64|hex")
2231 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002232
2233 Digester<MD5> digester = MD5.getDigester();
2234 Resource r = dot.getResource(args[1]);
2235 if (r == null)
2236 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2237
2238 IO.copy(r.openInputStream(), digester);
2239 boolean hex = args.length > 2 && args[2].equals("hex");
2240 if (hex)
2241 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch4482c702012-06-15 13:27:53 +00002242
2243 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochf3173222012-06-07 21:57:32 +00002244 }
2245
2246 /**
2247 * SHA1 macro
2248 */
2249
2250 static String _sha1Help = "${sha1;path}";
2251
2252 public String _sha1(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002253 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2254 null, null, Pattern.compile("base64|hex")
2255 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002256 Digester<SHA1> digester = SHA1.getDigester();
2257 Resource r = dot.getResource(args[1]);
2258 if (r == null)
2259 throw new FileNotFoundException("From sha1, not found " + args[1]);
2260
2261 IO.copy(r.openInputStream(), digester);
2262 return Base64.encodeBase64(digester.digest().digest());
2263 }
2264
2265 public Descriptor getDescriptor(String descriptor) {
2266 return descriptors.getDescriptor(descriptor);
2267 }
2268
2269 public TypeRef getTypeRef(String binaryClassName) {
2270 return descriptors.getTypeRef(binaryClassName);
2271 }
2272
2273 public PackageRef getPackageRef(String binaryName) {
2274 return descriptors.getPackageRef(binaryName);
2275 }
2276
2277 public TypeRef getTypeRefFromFQN(String fqn) {
2278 return descriptors.getTypeRefFromFQN(fqn);
2279 }
2280
2281 public TypeRef getTypeRefFromPath(String path) {
2282 return descriptors.getTypeRefFromPath(path);
2283 }
2284
2285 public boolean isImported(PackageRef packageRef) {
2286 return imports.containsKey(packageRef);
2287 }
2288
2289 /**
2290 * Merge the attributes of two maps, where the first map can contain
2291 * wildcarded names. The idea is that the first map contains instructions
2292 * (for example *) with a set of attributes. These patterns are matched
2293 * against the found packages in actual. If they match, the result is set
2294 * with the merged set of attributes. It is expected that the instructions
2295 * are ordered so that the instructor can define which pattern matches
2296 * first. Attributes in the instructions override any attributes from the
2297 * actual.<br/>
Stuart McCullochf3173222012-06-07 21:57:32 +00002298 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2299 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2300 * if the pattern starts with an exclamation mark, it will remove that
2301 * matches for that pattern (- the !) from the working set. So the following
2302 * patterns should work:
2303 * <ul>
2304 * <li>com.foo.bar</li>
2305 * <li>com.foo.*</li>
2306 * <li>com.foo.???</li>
2307 * <li>com.*.[^b][^a][^r]</li>
2308 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2309 * </ul>
2310 * Enough rope to hang the average developer I would say.
2311 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002312 * @param instructions
2313 * the instructions with patterns.
2314 * @param source
2315 * the actual found packages, contains no duplicates
Stuart McCullochf3173222012-06-07 21:57:32 +00002316 * @return Only the packages that were filtered by the given instructions
2317 */
2318
2319 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2320 Packages result = new Packages();
2321 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2322 Collections.sort(refs);
2323
2324 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2325 if (nomatch == null)
2326 nomatch = Create.set();
2327
2328 for (Instruction instruction : filters) {
2329 boolean match = false;
2330
2331 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2332 PackageRef packageRef = i.next();
2333
2334 if (packageRef.isMetaData()) {
2335 i.remove(); // no use checking it again
2336 continue;
2337 }
2338
2339 String packageName = packageRef.getFQN();
2340
2341 if (instruction.matches(packageName)) {
2342 match = true;
2343 if (!instruction.isNegated()) {
2344 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2345 instructions.get(instruction));
2346 }
2347 i.remove(); // Can never match again for another pattern
2348 }
2349 }
2350 if (!match && !instruction.isAny())
2351 nomatch.add(instruction);
2352 }
2353
2354 /*
2355 * Tricky. If we have umatched instructions they might indicate that we
2356 * want to have multiple decorators for the same package. So we check
2357 * the unmatched against the result list. If then then match and have
2358 * actually interesting properties then we merge them
2359 */
2360
2361 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2362 Instruction instruction = i.next();
2363
2364 // We assume the user knows what he is
2365 // doing and inserted a literal. So
2366 // we ignore any not matched literals
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00002367 // #252, we should not be negated to make it a constant
2368 if (instruction.isLiteral() && !instruction.isNegated()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002369 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochf3173222012-06-07 21:57:32 +00002370 i.remove();
2371 continue;
2372 }
2373
2374 // Not matching a negated instruction looks
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00002375 // like an error ... Though so, but
2376 // in the second phase of Export-Package
2377 // the !package will never match anymore.
Stuart McCullochf3173222012-06-07 21:57:32 +00002378 if (instruction.isNegated()) {
Stuart McCullochb7f3d7b2012-09-19 12:56:05 +00002379 i.remove();
Stuart McCullochf3173222012-06-07 21:57:32 +00002380 continue;
2381 }
2382
2383 // An optional instruction should not generate
2384 // an error
2385 if (instruction.isOptional()) {
2386 i.remove();
2387 continue;
2388 }
2389
2390 // boolean matched = false;
2391 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2392 // for (PackageRef ref : prefs) {
2393 // if (instruction.matches(ref.getFQN())) {
2394 // result.merge(ref, true, source.get(ref),
2395 // instructions.get(instruction));
2396 // matched = true;
2397 // }
2398 // }
2399 // if (matched)
2400 // i.remove();
2401 }
2402 return result;
2403 }
2404
2405 public void setDiagnostics(boolean b) {
2406 diagnostics = b;
2407 }
2408
2409 public Clazz.JAVA getLowestEE() {
2410 if (ees.isEmpty())
2411 return Clazz.JAVA.JDK1_4;
2412
2413 return ees.first();
2414 }
2415
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002416 public String _ee(@SuppressWarnings("unused")
2417 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00002418 return getLowestEE().getEE();
2419 }
2420
2421 /**
2422 * Calculate the output file for the given target. The strategy is:
2423 *
2424 * <pre>
2425 * parameter given if not null and not directory
2426 * if directory, this will be the output directory
2427 * based on bsn-version.jar
2428 * name of the source file if exists
2429 * Untitled-[n]
2430 * </pre>
2431 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002432 * @param output
2433 * may be null, otherwise a file path relative to base
Stuart McCullochf3173222012-06-07 21:57:32 +00002434 */
2435 public File getOutputFile(String output) {
2436
2437 if (output == null)
2438 output = get(Constants.OUTPUT);
2439
2440 File outputDir;
2441
2442 if (output != null) {
2443 File outputFile = getFile(output);
2444 if (outputFile.isDirectory())
2445 outputDir = outputFile;
2446 else
2447 return outputFile;
Stuart McCulloch4482c702012-06-15 13:27:53 +00002448 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002449 outputDir = getBase();
2450
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002451 Entry<String,Attrs> name = getBundleSymbolicName();
2452 if (name != null) {
2453 String bsn = name.getKey();
Stuart McCullochf3173222012-06-07 21:57:32 +00002454 String version = getBundleVersion();
2455 Version v = Version.parseVersion(version);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002456 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochf3173222012-06-07 21:57:32 +00002457 return new File(outputDir, outputName);
2458 }
2459
2460 File source = getJar().getSource();
2461 if (source != null) {
2462 String outputName = source.getName();
2463 return new File(outputDir, outputName);
2464 }
2465
Stuart McCulloch4482c702012-06-15 13:27:53 +00002466 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochf3173222012-06-07 21:57:32 +00002467 int n = 0;
2468 File f = getFile(outputDir, "Untitled");
2469 while (f.isFile()) {
2470 f = getFile(outputDir, "Untitled-" + n++);
2471 }
2472 return f;
2473 }
2474
2475 /**
2476 * Utility function to carefully save the file. Will create a backup if the
2477 * source file has the same path as the output. It will also only save if
2478 * the file was modified or the force flag is true
2479 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002480 * @param output
2481 * the output file, if null {@link #getOutputFile(String)} is
2482 * used.
2483 * @param force
2484 * if it needs to be overwritten
Stuart McCullochf3173222012-06-07 21:57:32 +00002485 * @throws Exception
2486 */
2487
2488 public boolean save(File output, boolean force) throws Exception {
2489 if (output == null)
2490 output = getOutputFile(null);
2491
2492 Jar jar = getJar();
2493 File source = jar.getSource();
2494
Stuart McCulloch4482c702012-06-15 13:27:53 +00002495 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2496 jar.lastModified() - output.lastModified());
Stuart McCullochf3173222012-06-07 21:57:32 +00002497
2498 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002499 File op = output.getParentFile();
2500 if (!op.exists() && !op.mkdirs()) {
2501 throw new IOException("Could not create directory " + op);
2502 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002503 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2504 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2505 if (!source.renameTo(bak)) {
2506 error("Could not create backup file %s", bak);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002507 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002508 source.delete();
2509 }
2510 try {
2511 trace("Saving jar to %s", output);
2512 getJar().write(output);
2513 }
2514 catch (Exception e) {
2515 output.delete();
2516 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2517 }
2518 return true;
2519 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002520 trace("Not modified %s", output);
2521 return false;
2522
Stuart McCullochf3173222012-06-07 21:57:32 +00002523 }
2524
2525 /**
2526 * Set default import and export instructions if none are set
2527 */
2528 public void setDefaults(String bsn, Version version) {
2529 if (getExportPackage() == null)
2530 setExportPackage("*");
2531 if (getImportPackage() == null)
2532 setExportPackage("*");
2533 if (bsn != null && getBundleSymbolicName() == null)
2534 setBundleSymbolicName(bsn);
2535 if (version != null && getBundleVersion() == null)
2536 setBundleVersion(version);
2537 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002538
2539 /**
Stuart McCullochb32291a2012-07-16 14:10:57 +00002540 * Remove the own references and optional java references from the uses lib
2541 *
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002542 * @param apiUses
2543 * @param removeJava
2544 * @return
2545 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00002546 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002547 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002548 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002549 e.getValue().remove(e.getKey());
2550 if (!removeJava)
2551 continue;
Stuart McCullochb32291a2012-07-16 14:10:57 +00002552
2553 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2554 if (i.next().isJava())
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002555 i.remove();
2556 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002557 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002558 return map;
2559 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002560
2561 /**
2562 * Return the classes for a given source package.
2563 *
2564 * @param source
2565 * the source package
2566 * @return a set of classes for the requested package.
2567 */
2568 public Set<Clazz> getClassspace(PackageRef source) {
2569 Set<Clazz> result = new HashSet<Clazz>();
2570 for (Clazz c : getClassspace().values()) {
2571 if (c.getClassName().getPackageRef() == source)
2572 result.add(c);
2573 }
2574 return result;
2575 }
2576
2577 /**
2578 * Create a cross reference from package source, to packages in dest
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002579 *
Stuart McCullochb32291a2012-07-16 14:10:57 +00002580 * @param source
2581 * @param dest
2582 * @param sourceModifiers
2583 * @return
2584 * @throws Exception
2585 */
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002586 public Map<Clazz.Def,List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest,
2587 final int sourceModifiers) throws Exception {
2588 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def,TypeRef>(Clazz.Def.class, TypeRef.class, true);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002589
2590 for (final Clazz clazz : getClassspace().values()) {
2591 if ((clazz.accessx & sourceModifiers) == 0)
2592 continue;
2593
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002594 if (source != null && source != clazz.getClassName().getPackageRef())
Stuart McCullochb32291a2012-07-16 14:10:57 +00002595 continue;
2596
2597 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2598 Clazz.Def member;
2599
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002600 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002601 public void extendsClass(TypeRef zuper) throws Exception {
2602 if (dest.contains(zuper.getPackageRef()))
2603 xref.add(clazz.getExtends(zuper), zuper);
2604 }
2605
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002606 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002607 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2608 for (TypeRef i : interfaces) {
2609 if (dest.contains(i.getPackageRef()))
2610 xref.add(clazz.getImplements(i), i);
2611 }
2612 }
2613
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002614 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002615 public void referTo(TypeRef to, int modifiers) {
2616 if (to.isJava())
2617 return;
2618
2619 if (!dest.contains(to.getPackageRef()))
2620 return;
2621
2622 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2623 xref.add(member, to);
2624 }
2625
2626 }
2627
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002628 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002629 public void method(Clazz.MethodDef defined) {
2630 member = defined;
2631 }
2632
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002633 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002634 public void field(Clazz.FieldDef defined) {
2635 member = defined;
2636 }
2637
2638 });
2639
2640 }
2641 return xref;
2642 }
2643
Stuart McCullochf3173222012-06-07 21:57:32 +00002644}