blob: 135f96c5fbf858f2a72f0a2396d55139ef1b1680 [file] [log] [blame]
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochf3173222012-06-07 21:57:32 +00002
3/**
4 * This class can calculate the required headers for a (potential) JAR file. It
5 * analyzes a directory or JAR for the packages that are contained and that are
6 * referred to by the bytecodes. The user can the use regular expressions to
7 * define the attributes and directives. The matching is not fully regex for
8 * convenience. A * and ? get a . prefixed and dots are escaped.
9 *
10 * <pre>
11 * *;auto=true any
12 * org.acme.*;auto=true org.acme.xyz
13 * org.[abc]*;auto=true org.acme.xyz
14 * </pre>
15 *
16 * Additional, the package instruction can start with a '=' or a '!'. The '!'
17 * indicates negation. Any matching package is removed. The '=' is literal, the
18 * expression will be copied verbatim and no matching will take place.
19 *
20 * Any headers in the given properties are used in the output properties.
21 */
22import static aQute.libg.generics.Create.*;
23
24import java.io.*;
25import java.net.*;
26import java.util.*;
27import java.util.Map.Entry;
28import java.util.jar.*;
29import java.util.jar.Attributes.Name;
30import java.util.regex.*;
31
32import aQute.bnd.annotation.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000033import aQute.bnd.header.*;
34import aQute.bnd.osgi.Descriptors.Descriptor;
35import aQute.bnd.osgi.Descriptors.PackageRef;
36import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochf3173222012-06-07 21:57:32 +000037import aQute.bnd.service.*;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000038import aQute.bnd.version.Version;
Stuart McCullochf3173222012-06-07 21:57:32 +000039import aQute.lib.base64.*;
40import aQute.lib.collections.*;
41import aQute.lib.filter.*;
42import aQute.lib.hex.*;
43import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000044import aQute.libg.cryptography.*;
45import aQute.libg.generics.*;
Stuart McCulloch4482c702012-06-15 13:27:53 +000046import aQute.libg.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000047
48public class Analyzer extends Processor {
49 private final SortedSet<Clazz.JAVA> ees = new TreeSet<Clazz.JAVA>();
Stuart McCulloch669423b2012-06-26 16:34:24 +000050 static Properties bndInfo;
Stuart McCullochf3173222012-06-07 21:57:32 +000051
52 // Bundle parameters
53 private Jar dot;
54 private final Packages contained = new Packages();
55 private final Packages referred = new Packages();
56 private Packages exports;
57 private Packages imports;
58 private TypeRef activator;
59
60 // Global parameters
Stuart McCulloch4482c702012-06-15 13:27:53 +000061 private final MultiMap<PackageRef,PackageRef> uses = new MultiMap<PackageRef,PackageRef>(
Stuart McCullochb32291a2012-07-16 14:10:57 +000062 PackageRef.class, PackageRef.class,
63 true);
Stuart McCulloch42151ee2012-07-16 13:43:38 +000064 private final MultiMap<PackageRef,PackageRef> apiUses = new MultiMap<PackageRef,PackageRef>(
Stuart McCullochb32291a2012-07-16 14:10:57 +000065 PackageRef.class, PackageRef.class,
66 true);
Stuart McCullochf3173222012-06-07 21:57:32 +000067 private final Packages classpathExports = new Packages();
68 private final Descriptors descriptors = new Descriptors();
69 private final List<Jar> classpath = list();
Stuart McCulloch4482c702012-06-15 13:27:53 +000070 private final Map<TypeRef,Clazz> classspace = map();
71 private final Map<TypeRef,Clazz> importedClassesCache = map();
Stuart McCullochf3173222012-06-07 21:57:32 +000072 private boolean analyzed = false;
73 private boolean diagnostics = false;
74 private boolean inited = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +000075 final protected AnalyzerMessages msgs = ReporterMessages.base(this,
76 AnalyzerMessages.class);
Stuart McCullochf3173222012-06-07 21:57:32 +000077
78 public Analyzer(Processor parent) {
79 super(parent);
80 }
81
Stuart McCulloch4482c702012-06-15 13:27:53 +000082 public Analyzer() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000083
84 /**
85 * Specifically for Maven
86 *
Stuart McCulloch4482c702012-06-15 13:27:53 +000087 * @param properties
88 * the properties
Stuart McCullochf3173222012-06-07 21:57:32 +000089 */
90
91 public static Properties getManifest(File dirOrJar) throws Exception {
92 Analyzer analyzer = new Analyzer();
93 try {
94 analyzer.setJar(dirOrJar);
95 Properties properties = new Properties();
96 properties.put(IMPORT_PACKAGE, "*");
97 properties.put(EXPORT_PACKAGE, "*");
98 analyzer.setProperties(properties);
99 Manifest m = analyzer.calcManifest();
100 Properties result = new Properties();
101 for (Iterator<Object> i = m.getMainAttributes().keySet().iterator(); i.hasNext();) {
102 Attributes.Name name = (Attributes.Name) i.next();
103 result.put(name.toString(), m.getMainAttributes().getValue(name));
104 }
105 return result;
106 }
107 finally {
108 analyzer.close();
109 }
110 }
111
112 /**
113 * Calculates the data structures for generating a manifest.
114 *
115 * @throws IOException
116 */
117 public void analyze() throws Exception {
118 if (!analyzed) {
119 analyzed = true;
120 uses.clear();
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000121 apiUses.clear();
Stuart McCullochf3173222012-06-07 21:57:32 +0000122 classspace.clear();
123 classpathExports.clear();
124
125 // Parse all the class in the
126 // the jar according to the OSGi bcp
127 analyzeBundleClasspath();
128
129 //
130 // calculate class versions in use
131 //
132 for (Clazz c : classspace.values()) {
133 ees.add(c.getFormat());
134 }
135
136 //
137 // Get exported packages from the
138 // entries on the classpath
139 //
140
141 for (Jar current : getClasspath()) {
142 getExternalExports(current, classpathExports);
143 for (String dir : current.getDirectories().keySet()) {
144 PackageRef packageRef = getPackageRef(dir);
145 Resource resource = current.getResource(dir + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +0000146 getExportVersionsFromPackageInfo(packageRef, resource, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +0000147 }
148 }
149
150 // Handle the bundle activator
151
152 String s = getProperty(BUNDLE_ACTIVATOR);
153 if (s != null) {
154 activator = getTypeRefFromFQN(s);
155 referTo(activator);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000156 trace("activator %s %s", s, activator);
Stuart McCullochf3173222012-06-07 21:57:32 +0000157 }
158
159 // Execute any plugins
160 // TODO handle better reanalyze
161 doPlugins();
162
163 Jar extra = getExtra();
164 while (extra != null) {
165 dot.addAll(extra);
166 analyzeJar(extra, "", true);
167 extra = getExtra();
168 }
169
170 referred.keySet().removeAll(contained.keySet());
171
172 //
173 // EXPORTS
174 //
175 {
176 Set<Instruction> unused = Create.set();
177
178 Instructions filter = new Instructions(getExportPackage());
179 filter.append(getExportContents());
180
181 exports = filter(filter, contained, unused);
182
183 if (!unused.isEmpty()) {
184 warning("Unused Export-Package instructions: %s ", unused);
185 }
186
187 // See what information we can find to augment the
188 // exports. I.e. look on the classpath
189 augmentExports(exports);
190 }
191
192 //
193 // IMPORTS
194 // Imports MUST come after exports because we use information from
195 // the exports
196 //
197 {
198 // Add all exports that do not have an -noimport: directive
199 // to the imports.
200 Packages referredAndExported = new Packages(referred);
201 referredAndExported.putAll(doExportsToImports(exports));
202
203 removeDynamicImports(referredAndExported);
204
205 // Remove any Java references ... where are the closures???
206 for (Iterator<PackageRef> i = referredAndExported.keySet().iterator(); i.hasNext();) {
207 if (i.next().isJava())
208 i.remove();
209 }
210
211 Set<Instruction> unused = Create.set();
212 String h = getProperty(IMPORT_PACKAGE);
213 if (h == null) // If not set use a default
214 h = "*";
215
216 if (isPedantic() && h.trim().length() == 0)
217 warning("Empty Import-Package header");
218
219 Instructions filter = new Instructions(h);
220 imports = filter(filter, referredAndExported, unused);
221 if (!unused.isEmpty()) {
222 // We ignore the end wildcard catch
223 if (!(unused.size() == 1 && unused.iterator().next().toString().equals("*")))
224 warning("Unused Import-Package instructions: %s ", unused);
225 }
226
227 // See what information we can find to augment the
228 // imports. I.e. look in the exports
229 augmentImports(imports, exports);
230 }
231
232 //
233 // USES
234 //
235 // Add the uses clause to the exports
Stuart McCullochb32291a2012-07-16 14:10:57 +0000236
237 boolean api = isTrue(getProperty(EXPERIMENTS)) || true; // brave,
238 // lets see
239
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000240 doUses(exports, api ? apiUses : uses, imports);
Stuart McCullochb32291a2012-07-16 14:10:57 +0000241
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000242 //
243 // Verify that no exported package has a reference to a private
244 // package
245 // This can cause a lot of harm.
246 // TODO restrict the check to public API only, but even then
247 // exported packages
248 // should preferably not refer to private packages.
249 //
Stuart McCullochb32291a2012-07-16 14:10:57 +0000250 Set<PackageRef> privatePackages = getPrivates();
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000251
252 // References to java are not imported so they would show up as
253 // private
254 // packages, lets kill them as well.
255
256 for (Iterator<PackageRef> p = privatePackages.iterator(); p.hasNext();)
257 if (p.next().isJava())
258 p.remove();
259
260 for (PackageRef exported : exports.keySet()) {
261 List<PackageRef> used = uses.get(exported);
262 if (used != null) {
263 Set<PackageRef> privateReferences = new HashSet<PackageRef>(apiUses.get(exported));
264 privateReferences.retainAll(privatePackages);
265 if (!privateReferences.isEmpty())
266 msgs.Export_Has_PrivateReferences_(exported, privateReferences.size(), privateReferences);
267 }
268 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000269
270 //
271 // Checks
272 //
273 if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
274 error("The default package '.' is not permitted by the Import-Package syntax. \n"
275 + " This can be caused by compile errors in Eclipse because Eclipse creates \n"
276 + "valid class files regardless of compile errors.\n"
277 + "The following package(s) import from the default package "
278 + uses.transpose().get(Descriptors.DEFAULT_PACKAGE));
279 }
280
281 }
282 }
283
284 /**
285 * Discussed with BJ and decided to kill the .
286 *
287 * @param referredAndExported
288 */
289 void removeDynamicImports(Packages referredAndExported) {
290
291 // // Remove any matching a dynamic import package instruction
292 // Instructions dynamicImports = new
293 // Instructions(getDynamicImportPackage());
294 // Collection<PackageRef> dynamic = dynamicImports.select(
295 // referredAndExported.keySet(), false);
296 // referredAndExported.keySet().removeAll(dynamic);
297 }
298
299 protected Jar getExtra() throws Exception {
300 return null;
301 }
302
303 /**
304 *
305 */
306 void doPlugins() {
307 for (AnalyzerPlugin plugin : getPlugins(AnalyzerPlugin.class)) {
308 try {
Stuart McCulloch5515a832012-07-22 00:19:13 +0000309 Processor previous = beginHandleErrors(plugin.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +0000310 boolean reanalyze = plugin.analyzeJar(this);
Stuart McCulloch5515a832012-07-22 00:19:13 +0000311 endHandleErrors(previous);
Stuart McCullochf3173222012-06-07 21:57:32 +0000312 if (reanalyze) {
313 classspace.clear();
314 analyzeBundleClasspath();
315 }
316 }
317 catch (Exception e) {
318 error("Analyzer Plugin %s failed %s", plugin, e);
319 }
320 }
321 }
322
323 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000324 * @return
325 */
326 boolean isResourceOnly() {
327 return isTrue(getProperty(RESOURCEONLY));
328 }
329
330 /**
331 * One of the main workhorses of this class. This will analyze the current
332 * setp and calculate a new manifest according to this setup. This method
333 * will also set the manifest on the main jar dot
334 *
335 * @return
336 * @throws IOException
337 */
338 public Manifest calcManifest() throws Exception {
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000339 try {
340 analyze();
341 Manifest manifest = new Manifest();
342 Attributes main = manifest.getMainAttributes();
Stuart McCullochf3173222012-06-07 21:57:32 +0000343
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000344 main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
345 main.putValue(BUNDLE_MANIFESTVERSION, "2");
Stuart McCullochf3173222012-06-07 21:57:32 +0000346
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000347 boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
Stuart McCullochf3173222012-06-07 21:57:32 +0000348
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000349 if (!noExtraHeaders) {
350 main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
351 + ")");
352 main.putValue(TOOL, "Bnd-" + getBndVersion());
353 main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
Stuart McCullochf3173222012-06-07 21:57:32 +0000354 }
355
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000356 String exportHeader = printClauses(exports, true);
Stuart McCullochf3173222012-06-07 21:57:32 +0000357
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000358 if (exportHeader.length() > 0)
359 main.putValue(EXPORT_PACKAGE, exportHeader);
360 else
361 main.remove(EXPORT_PACKAGE);
Stuart McCullochf3173222012-06-07 21:57:32 +0000362
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000363 // Remove all the Java packages from the imports
364 if (!imports.isEmpty()) {
365 main.putValue(IMPORT_PACKAGE, printClauses(imports));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000366 } else {
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000367 main.remove(IMPORT_PACKAGE);
Stuart McCullochf3173222012-06-07 21:57:32 +0000368 }
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000369
370 Packages temp = new Packages(contained);
371 temp.keySet().removeAll(exports.keySet());
372
373 if (!temp.isEmpty())
374 main.putValue(PRIVATE_PACKAGE, printClauses(temp));
375 else
376 main.remove(PRIVATE_PACKAGE);
377
378 Parameters bcp = getBundleClasspath();
379 if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
380 main.remove(BUNDLE_CLASSPATH);
381 else
382 main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
383
384 doNamesection(dot, manifest);
385
386 for (Enumeration< ? > h = getProperties().propertyNames(); h.hasMoreElements();) {
387 String header = (String) h.nextElement();
388 if (header.trim().length() == 0) {
389 warning("Empty property set with value: " + getProperties().getProperty(header));
390 continue;
391 }
392
393 if (isMissingPlugin(header.trim())) {
394 error("Missing plugin for command %s", header);
395 }
396 if (!Character.isUpperCase(header.charAt(0))) {
397 if (header.charAt(0) == '@')
398 doNameSection(manifest, header);
399 continue;
400 }
401
402 if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE) || header.equals(IMPORT_PACKAGE))
403 continue;
404
405 if (header.equalsIgnoreCase("Name")) {
406 error("Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
407 continue;
408 }
409
410 if (Verifier.HEADER_PATTERN.matcher(header).matches()) {
411 String value = getProperty(header);
412 if (value != null && main.getValue(header) == null) {
413 if (value.trim().length() == 0)
414 main.remove(header);
415 else if (value.trim().equals(EMPTY_HEADER))
416 main.putValue(header, "");
417 else
418 main.putValue(header, value);
419 }
420 } else {
421 // TODO should we report?
422 }
423 }
424
425 // Copy old values into new manifest, when they
426 // exist in the old one, but not in the new one
427 merge(manifest, dot.getManifest());
428
429 //
430 // Calculate the bundle symbolic name if it is
431 // not set.
432 // 1. set
433 // 2. name of properties file (must be != bnd.bnd)
434 // 3. name of directory, which is usualy project name
435 //
436 String bsn = getBsn();
437 if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
438 main.putValue(BUNDLE_SYMBOLICNAME, bsn);
439 }
440
441 //
442 // Use the same name for the bundle name as BSN when
443 // the bundle name is not set
444 //
445 if (main.getValue(BUNDLE_NAME) == null) {
446 main.putValue(BUNDLE_NAME, bsn);
447 }
448
449 if (main.getValue(BUNDLE_VERSION) == null)
450 main.putValue(BUNDLE_VERSION, "0");
451
452 // Remove all the headers mentioned in -removeheaders
453 Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
454 Collection<Object> result = instructions.select(main.keySet(), false);
455 main.keySet().removeAll(result);
456
457 // We should not set the manifest here, this is in general done
458 // by the caller.
459 // dot.setManifest(manifest);
460 return manifest;
Stuart McCullochf3173222012-06-07 21:57:32 +0000461 }
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000462 catch (Exception e) {
463 // This should not really happen. The code should never throw
464 // exceptions in normal situations. So if it happens we need more
465 // information. So to help diagnostics. We do a full property dump
466 throw new IllegalStateException("Calc manifest failed, state=\n"+getFlattenedProperties(), e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000467 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000468 }
469
470 /**
471 * Parse the namesection as instructions and then match them against the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000472 * current set of resources For example:
Stuart McCullochf3173222012-06-07 21:57:32 +0000473 *
474 * <pre>
475 * -namesection: *;baz=true, abc/def/bar/X.class=3
476 * </pre>
477 *
478 * The raw value of {@link Constants#NAMESECTION} is used but the values of
479 * the attributes are replaced where @ is set to the resource name. This
480 * allows macro to operate on the resource
Stuart McCullochf3173222012-06-07 21:57:32 +0000481 */
482
483 private void doNamesection(Jar dot, Manifest manifest) {
484
485 Parameters namesection = parseHeader(getProperties().getProperty(NAMESECTION));
486 Instructions instructions = new Instructions(namesection);
487 Set<String> resources = new HashSet<String>(dot.getResources().keySet());
488
489 //
490 // For each instruction, iterator over the resources and filter
491 // them. If a resource matches, it must be removed even if the
492 // instruction is negative. If positive, add a name section
493 // to the manifest for the given resource name. Then add all
494 // attributes from the instruction to that name section.
495 //
Stuart McCulloch4482c702012-06-15 13:27:53 +0000496 for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000497 boolean matched = false;
498
499 // For each instruction
500
501 for (Iterator<String> i = resources.iterator(); i.hasNext();) {
502 String path = i.next();
503 // For each resource
504
505 if (instr.getKey().matches(path)) {
506
507 // Instruction matches the resource
508
509 matched = true;
510 if (!instr.getKey().isNegated()) {
511
512 // Positive match, add the attributes
513
514 Attributes attrs = manifest.getAttributes(path);
515 if (attrs == null) {
516 attrs = new Attributes();
517 manifest.getEntries().put(path, attrs);
518 }
519
520 //
521 // Add all the properties from the instruction to the
522 // name section
523 //
524
Stuart McCulloch4482c702012-06-15 13:27:53 +0000525 for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000526 setProperty("@", path);
527 try {
528 String processed = getReplacer().process(property.getValue());
529 attrs.putValue(property.getKey(), processed);
530 }
531 finally {
532 unsetProperty("@");
533 }
534 }
535 }
536 i.remove();
537 }
538 }
539
540 if (!matched && resources.size() > 0)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000541 warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
Stuart McCullochf3173222012-06-07 21:57:32 +0000542 }
543
544 }
545
546 /**
547 * This method is called when the header starts with a @, signifying a name
548 * section header. The name part is defined by replacing all the @ signs to
549 * a /, removing the first and the last, and using the last part as header
550 * name:
551 *
552 * <pre>
553 * &#064;org@osgi@service@event@Implementation-Title
554 * </pre>
555 *
556 * This will be the header Implementation-Title in the
557 * org/osgi/service/event named section.
558 *
559 * @param manifest
560 * @param header
561 */
562 private void doNameSection(Manifest manifest, String header) {
563 String path = header.replace('@', '/');
564 int n = path.lastIndexOf('/');
565 // Must succeed because we start with @
566 String name = path.substring(n + 1);
567 // Skip first /
568 path = path.substring(1, n);
569 if (name.length() != 0 && path.length() != 0) {
570 Attributes attrs = manifest.getAttributes(path);
571 if (attrs == null) {
572 attrs = new Attributes();
573 manifest.getEntries().put(path, attrs);
574 }
575 attrs.putValue(name, getProperty(header));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000576 } else {
577 warning("Invalid header (starts with @ but does not seem to be for the Name section): %s", header);
Stuart McCullochf3173222012-06-07 21:57:32 +0000578 }
579 }
580
581 /**
582 * Clear the key part of a header. I.e. remove everything from the first ';'
583 *
584 * @param value
585 * @return
586 */
587 public String getBsn() {
588 String value = getProperty(BUNDLE_SYMBOLICNAME);
589 if (value == null) {
590 if (getPropertiesFile() != null)
591 value = getPropertiesFile().getName();
592
593 String projectName = getBase().getName();
594 if (value == null || value.equals("bnd.bnd")) {
595 value = projectName;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000596 } else if (value.endsWith(".bnd")) {
597 value = value.substring(0, value.length() - 4);
598 if (!value.startsWith(getBase().getName()))
599 value = projectName + "." + value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000600 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000601 }
602
603 if (value == null)
604 return "untitled";
605
606 int n = value.indexOf(';');
607 if (n > 0)
608 value = value.substring(0, n);
609 return value.trim();
610 }
611
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000612 public String _bsn(@SuppressWarnings("unused")
613 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000614 return getBsn();
615 }
616
617 /**
618 * Calculate an export header solely based on the contents of a JAR file
619 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000620 * @param bundle
621 * The jar file to analyze
Stuart McCullochf3173222012-06-07 21:57:32 +0000622 * @return
623 */
624 public String calculateExportsFromContents(Jar bundle) {
625 String ddel = "";
626 StringBuilder sb = new StringBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000627 Map<String,Map<String,Resource>> map = bundle.getDirectories();
Stuart McCullochf3173222012-06-07 21:57:32 +0000628 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
629 String directory = i.next();
630 if (directory.equals("META-INF") || directory.startsWith("META-INF/"))
631 continue;
632 if (directory.equals("OSGI-OPT") || directory.startsWith("OSGI-OPT/"))
633 continue;
634 if (directory.equals("/"))
635 continue;
636
637 if (directory.endsWith("/"))
638 directory = directory.substring(0, directory.length() - 1);
639
640 directory = directory.replace('/', '.');
641 sb.append(ddel);
642 sb.append(directory);
643 ddel = ",";
644 }
645 return sb.toString();
646 }
647
648 public Packages getContained() {
649 return contained;
650 }
651
652 public Packages getExports() {
653 return exports;
654 }
655
656 public Packages getImports() {
657 return imports;
658 }
659
Stuart McCullochb32291a2012-07-16 14:10:57 +0000660 public Set<PackageRef> getPrivates() {
661 HashSet<PackageRef> privates = new HashSet<PackageRef>(contained.keySet());
662 privates.removeAll(exports.keySet());
663 privates.removeAll(imports.keySet());
664 return privates;
665 }
666
Stuart McCullochf3173222012-06-07 21:57:32 +0000667 public Jar getJar() {
668 return dot;
669 }
670
671 public Packages getReferred() {
672 return referred;
673 }
674
675 /**
676 * Return the set of unreachable code depending on exports and the bundle
677 * activator.
678 *
679 * @return
680 */
681 public Set<PackageRef> getUnreachable() {
682 Set<PackageRef> unreachable = new HashSet<PackageRef>(uses.keySet()); // all
683 for (Iterator<PackageRef> r = exports.keySet().iterator(); r.hasNext();) {
684 PackageRef packageRef = r.next();
685 removeTransitive(packageRef, unreachable);
686 }
687 if (activator != null) {
688 removeTransitive(activator.getPackageRef(), unreachable);
689 }
690 return unreachable;
691 }
692
Stuart McCullochb32291a2012-07-16 14:10:57 +0000693 public Map<PackageRef,List<PackageRef>> getUses() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000694 return uses;
695 }
696
Stuart McCullochb32291a2012-07-16 14:10:57 +0000697 public Map<PackageRef,List<PackageRef>> getAPIUses() {
Stuart McCulloch42151ee2012-07-16 13:43:38 +0000698 return apiUses;
699 }
700
Stuart McCullochf3173222012-06-07 21:57:32 +0000701 /**
702 * Get the version for this bnd
703 *
704 * @return version or unknown.
705 */
706 public String getBndVersion() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000707 return getBndInfo("version", "<unknown>");
Stuart McCullochf3173222012-06-07 21:57:32 +0000708 }
709
710 public long getBndLastModified() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000711 String time = getBndInfo("lastmodified", "0");
Stuart McCullochf3173222012-06-07 21:57:32 +0000712 try {
713 return Long.parseLong(time);
714 }
715 catch (Exception e) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000716 // Ignore
Stuart McCullochf3173222012-06-07 21:57:32 +0000717 }
718 return 0;
719 }
720
721 public String getBndInfo(String key, String defaultValue) {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000722 if (bndInfo == null) {
723 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000724 Properties bndInfoLocal = new Properties();
725 URL url = Analyzer.class.getResource("bnd.info");
726 if (url != null) {
727 InputStream in = url.openStream();
728 try {
729 bndInfoLocal.load(in);
730 }
731 finally {
732 in.close();
733 }
734 }
735 bndInfo = bndInfoLocal;
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000736 }
737 catch (Exception e) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000738 e.printStackTrace();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000739 return defaultValue;
Stuart McCullochf3173222012-06-07 21:57:32 +0000740 }
741 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000742 String value = bndInfo.getProperty(key);
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000743 if (value == null)
744 return defaultValue;
745 return value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000746 }
747
748 /**
749 * Merge the existing manifest with the instructions but do not override
750 * existing properties.
751 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000752 * @param manifest
753 * The manifest to merge with
Stuart McCullochf3173222012-06-07 21:57:32 +0000754 * @throws IOException
755 */
756 public void mergeManifest(Manifest manifest) throws IOException {
757 if (manifest != null) {
758 Attributes attributes = manifest.getMainAttributes();
759 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
760 Name name = (Name) i.next();
761 String key = name.toString();
762 // Dont want instructions
763 if (key.startsWith("-"))
764 continue;
765
766 if (getProperty(key) == null)
767 setProperty(key, attributes.getValue(name));
768 }
769 }
770 }
771
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000772 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000773 public void setBase(File file) {
774 super.setBase(file);
775 getProperties().put("project.dir", getBase().getAbsolutePath());
776 }
777
778 /**
779 * Set the classpath for this analyzer by file.
780 *
781 * @param classpath
782 * @throws IOException
783 */
784 public void setClasspath(File[] classpath) throws IOException {
785 List<Jar> list = new ArrayList<Jar>();
786 for (int i = 0; i < classpath.length; i++) {
787 if (classpath[i].exists()) {
788 Jar current = new Jar(classpath[i]);
789 list.add(current);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000790 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000791 error("Missing file on classpath: %s", classpath[i]);
792 }
793 }
794 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
795 addClasspath(i.next());
796 }
797 }
798
799 public void setClasspath(Jar[] classpath) {
800 for (int i = 0; i < classpath.length; i++) {
801 addClasspath(classpath[i]);
802 }
803 }
804
805 public void setClasspath(String[] classpath) {
806 for (int i = 0; i < classpath.length; i++) {
807 Jar jar = getJarFromName(classpath[i], " setting classpath");
808 if (jar != null)
809 addClasspath(jar);
810 }
811 }
812
813 /**
814 * Set the JAR file we are going to work in. This will read the JAR in
815 * memory.
816 *
817 * @param jar
818 * @return
819 * @throws IOException
820 */
821 public Jar setJar(File jar) throws IOException {
822 Jar jarx = new Jar(jar);
823 addClose(jarx);
824 return setJar(jarx);
825 }
826
827 /**
828 * Set the JAR directly we are going to work on.
829 *
830 * @param jar
831 * @return
832 */
833 public Jar setJar(Jar jar) {
834 if (dot != null)
835 removeClose(dot);
836
837 this.dot = jar;
838 if (dot != null)
839 addClose(dot);
840
841 return jar;
842 }
843
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000844 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000845 protected void begin() {
846 if (inited == false) {
847 inited = true;
848 super.begin();
849
850 updateModified(getBndLastModified(), "bnd last modified");
851 verifyManifestHeadersCase(getProperties());
852
853 }
854 }
855
856 /**
857 * Try to get a Jar from a file name/path or a url, or in last resort from
858 * the classpath name part of their files.
859 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000860 * @param name
861 * URL or filename relative to the base
862 * @param from
863 * Message identifying the caller for errors
Stuart McCullochf3173222012-06-07 21:57:32 +0000864 * @return null or a Jar with the contents for the name
865 */
866 Jar getJarFromName(String name, String from) {
867 File file = new File(name);
868 if (!file.isAbsolute())
869 file = new File(getBase(), name);
870
871 if (file.exists())
872 try {
873 Jar jar = new Jar(file);
874 addClose(jar);
875 return jar;
876 }
877 catch (Exception e) {
878 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
879 }
880 // It is not a file ...
881 try {
882 // Lets try a URL
883 URL url = new URL(name);
884 Jar jar = new Jar(fileName(url.getPath()));
885 addClose(jar);
886 URLConnection connection = url.openConnection();
887 InputStream in = connection.getInputStream();
888 long lastModified = connection.getLastModified();
889 if (lastModified == 0)
890 // We assume the worst :-(
891 lastModified = System.currentTimeMillis();
892 EmbeddedResource.build(jar, in, lastModified);
893 in.close();
894 return jar;
895 }
896 catch (IOException ee) {
897 // Check if we have files on the classpath
898 // that have the right name, allows us to specify those
899 // names instead of the full path.
900 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
901 Jar entry = cp.next();
902 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
903 return entry;
904 }
905 }
906 // error("Can not find jar file for " + from + ": " + name);
907 }
908 return null;
909 }
910
911 private String fileName(String path) {
912 int n = path.lastIndexOf('/');
913 if (n > 0)
914 return path.substring(n + 1);
915 return path;
916 }
917
918 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000919 * @param manifests
920 * @throws Exception
921 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000922 private void merge(Manifest result, Manifest old) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000923 if (old != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000924 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
925 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000926 Attributes.Name name = (Attributes.Name) entry.getKey();
927 String value = (String) entry.getValue();
928 if (name.toString().equalsIgnoreCase("Created-By"))
929 name = new Attributes.Name("Originally-Created-By");
930 if (!result.getMainAttributes().containsKey(name))
931 result.getMainAttributes().put(name, value);
932 }
933
934 // do not overwrite existing entries
Stuart McCulloch4482c702012-06-15 13:27:53 +0000935 Map<String,Attributes> oldEntries = old.getEntries();
936 Map<String,Attributes> newEntries = result.getEntries();
937 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
938 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000939 if (!newEntries.containsKey(entry.getKey())) {
940 newEntries.put(entry.getKey(), entry.getValue());
941 }
942 }
943 }
944 }
945
946 /**
947 * Bnd is case sensitive for the instructions so we better check people are
948 * not using an invalid case. We do allow this to set headers that should
949 * not be processed by us but should be used by the framework.
950 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000951 * @param properties
952 * Properties to verify.
Stuart McCullochf3173222012-06-07 21:57:32 +0000953 */
954
955 void verifyManifestHeadersCase(Properties properties) {
956 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
957 String header = (String) i.next();
958 for (int j = 0; j < headers.length; j++) {
959 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
960 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
961 + header + " and expecting: " + headers[j]);
962 break;
963 }
964 }
965 }
966 }
967
968 /**
969 * We will add all exports to the imports unless there is a -noimport
970 * directive specified on an export. This directive is skipped for the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000971 * manifest. We also remove any version parameter so that augmentImports can
972 * do the version policy. The following method is really tricky and evolved
973 * over time. Coming from the original background of OSGi, it was a weird
974 * idea for me to have a public package that should not be substitutable. I
975 * was so much convinced that this was the right rule that I rücksichtlos
976 * imported them all. Alas, the real world was more subtle than that. It
977 * turns out that it is not a good idea to always import. First, there must
978 * be a need to import, i.e. there must be a contained package that refers
979 * to the exported package for it to make use importing that package.
980 * Second, if an exported package refers to an internal package than it
981 * should not be imported. Additionally, it is necessary to treat the
982 * exports in groups. If an exported package refers to another exported
983 * packages than it must be in the same group. A framework can only
984 * substitute exports for imports for the whole of such a group. WHY?????
985 * Not clear anymore ...
Stuart McCullochf3173222012-06-07 21:57:32 +0000986 */
987 Packages doExportsToImports(Packages exports) {
988
989 // private packages = contained - exported.
990 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
991 privatePackages.removeAll(exports.keySet());
992
993 // private references = ∀ p : private packages | uses(p)
994 Set<PackageRef> privateReferences = newSet();
995 for (PackageRef p : privatePackages) {
996 Collection<PackageRef> uses = this.uses.get(p);
997 if (uses != null)
998 privateReferences.addAll(uses);
999 }
1000
1001 // Assume we are going to export all exported packages
1002 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
1003
1004 // Remove packages that are not referenced privately
1005 toBeImported.retainAll(privateReferences);
1006
1007 // Not necessary to import anything that is already
1008 // imported in the Import-Package statement.
1009 // TODO toBeImported.removeAll(imports.keySet());
1010
1011 // Remove exported packages that are referring to
1012 // private packages.
1013 // Each exported package has a uses clause. We just use
1014 // the used packages for each exported package to find out
1015 // if it refers to an internal package.
1016 //
1017
1018 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1019 PackageRef next = i.next();
1020 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1021
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00001022 // We had an NPE on usedByExportedPackage in GF.
1023 // I guess this can happen with hard coded
1024 // imports that do not match reality ...
1025 if (usedByExportedPackage == null || usedByExportedPackage.isEmpty()) {
1026 continue;
1027 }
1028
Stuart McCullochf3173222012-06-07 21:57:32 +00001029 for (PackageRef privatePackage : privatePackages) {
1030 if (usedByExportedPackage.contains(privatePackage)) {
1031 i.remove();
1032 break;
1033 }
1034 }
1035 }
1036
1037 // Clean up attributes and generate result map
1038 Packages result = new Packages();
1039 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1040 PackageRef ep = i.next();
1041 Attrs parameters = exports.get(ep);
1042
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001043 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochf3173222012-06-07 21:57:32 +00001044 if (noimport != null && noimport.equalsIgnoreCase("true"))
1045 continue;
1046
1047 // // we can't substitute when there is no version
1048 // String version = parameters.get(VERSION_ATTRIBUTE);
1049 // if (version == null) {
1050 // if (isPedantic())
1051 // warning(
1052 // "Cannot automatically import exported package %s because it has no version defined",
1053 // ep);
1054 // continue;
1055 // }
1056
1057 parameters = new Attrs();
1058 parameters.remove(VERSION_ATTRIBUTE);
1059 result.put(ep, parameters);
1060 }
1061 return result;
1062 }
1063
1064 public boolean referred(PackageRef packageName) {
1065 // return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001066 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001067 if (!contained.getKey().equals(packageName)) {
1068 if (contained.getValue().contains(packageName))
1069 return true;
1070 }
1071 }
1072 return false;
1073 }
1074
1075 /**
Stuart McCullochf3173222012-06-07 21:57:32 +00001076 * @param jar
1077 */
1078 private void getExternalExports(Jar jar, Packages classpathExports) {
1079 try {
1080 Manifest m = jar.getManifest();
1081 if (m != null) {
1082 Domain domain = Domain.domain(m);
1083 Parameters exported = domain.getExportPackage();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001084 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001085 PackageRef ref = getPackageRef(e.getKey());
1086 if (!classpathExports.containsKey(ref)) {
1087 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1088 // jar.getBsn()+"-"+jar.getVersion());
1089
1090 classpathExports.put(ref, e.getValue());
1091 }
1092 }
1093 }
1094 }
1095 catch (Exception e) {
1096 warning("Erroneous Manifest for " + jar + " " + e);
1097 }
1098 }
1099
1100 /**
1101 * Find some more information about imports in manifest and other places. It
1102 * is assumed that the augmentsExports has already copied external attrs
1103 * from the classpathExports.
1104 *
1105 * @throws Exception
1106 */
1107 void augmentImports(Packages imports, Packages exports) throws Exception {
1108 List<PackageRef> noimports = Create.list();
1109 Set<PackageRef> provided = findProvidedPackages();
1110
1111 for (PackageRef packageRef : imports.keySet()) {
1112 String packageName = packageRef.getFQN();
1113
1114 setProperty(CURRENT_PACKAGE, packageName);
1115 try {
1116 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001117 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochf3173222012-06-07 21:57:32 +00001118
1119 String exportVersion = exportAttributes.getVersion();
1120 String importRange = importAttributes.getVersion();
1121
1122 if (exportVersion == null) {
1123 // TODO Should check if the source is from a bundle.
1124
Stuart McCulloch4482c702012-06-15 13:27:53 +00001125 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001126
1127 //
1128 // Version Policy - Import version substitution. We
1129 // calculate the export version and then allow the
1130 // import version attribute to use it in a substitution
1131 // by using a ${@} macro. The export version can
1132 // be defined externally or locally
1133 //
1134
1135 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch4482c702012-06-15 13:27:53 +00001136 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochf3173222012-06-07 21:57:32 +00001137
1138 exportVersion = cleanupVersion(exportVersion);
1139
1140 try {
1141 setProperty("@", exportVersion);
1142
1143 if (importRange != null) {
1144 importRange = cleanupVersion(importRange);
1145 importRange = getReplacer().process(importRange);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001146 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001147 importRange = getVersionPolicy(provider);
1148
1149 }
1150 finally {
1151 unsetProperty("@");
1152 }
1153 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1154 }
1155
1156 //
1157 // Check if exporter has mandatory attributes
1158 //
1159 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1160 if (mandatory != null) {
1161 String[] attrs = mandatory.split("\\s*,\\s*");
1162 for (int i = 0; i < attrs.length; i++) {
1163 if (!importAttributes.containsKey(attrs[i]))
1164 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1165 }
1166 }
1167
1168 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1169 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1170
1171 fixupAttributes(importAttributes);
1172 removeAttributes(importAttributes);
1173
1174 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1175 if (result == null)
1176 noimports.add(packageRef);
1177 }
1178 finally {
1179 unsetProperty(CURRENT_PACKAGE);
1180 }
1181 }
1182
1183 if (isPedantic() && noimports.size() != 0) {
1184 warning("Imports that lack version ranges: %s", noimports);
1185 }
1186 }
1187
1188 /**
1189 * Find the packages we depend on, where we implement an interface that is a
1190 * Provider Type. These packages, when we import them, must use the provider
1191 * policy.
1192 *
1193 * @throws Exception
1194 */
1195 Set<PackageRef> findProvidedPackages() throws Exception {
1196 Set<PackageRef> providers = Create.set();
1197 Set<TypeRef> cached = Create.set();
1198
1199 for (Clazz c : classspace.values()) {
1200 TypeRef[] interfaces = c.getInterfaces();
1201 if (interfaces != null)
1202 for (TypeRef t : interfaces)
1203 if (cached.contains(t) || isProvider(t)) {
1204 cached.add(t);
1205 providers.add(t.getPackageRef());
1206 }
1207 }
1208 return providers;
1209 }
1210
1211 private boolean isProvider(TypeRef t) throws Exception {
1212 Clazz c = findClass(t);
1213 if (c == null)
1214 return false;
1215
1216 if (c.annotations == null)
1217 return false;
1218
1219 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1220 boolean result = c.annotations.contains(pt);
1221 return result;
1222 }
1223
1224 /**
1225 * Provide any macro substitutions and versions for exported packages.
1226 */
1227
1228 void augmentExports(Packages exports) {
1229 for (PackageRef packageRef : exports.keySet()) {
1230 String packageName = packageRef.getFQN();
1231 setProperty(CURRENT_PACKAGE, packageName);
1232 try {
1233 Attrs attributes = exports.get(packageRef);
1234 Attrs exporterAttributes = classpathExports.get(packageRef);
1235 if (exporterAttributes == null)
1236 continue;
1237
Stuart McCulloch4482c702012-06-15 13:27:53 +00001238 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001239 String key = entry.getKey();
1240 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1241 key = VERSION_ATTRIBUTE;
1242
1243 // dont overwrite and no directives
1244 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1245 attributes.put(key, entry.getValue());
1246 }
1247 }
1248
1249 fixupAttributes(attributes);
1250 removeAttributes(attributes);
1251
1252 }
1253 finally {
1254 unsetProperty(CURRENT_PACKAGE);
1255 }
1256 }
1257 }
1258
1259 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001260 * Fixup Attributes Execute any macros on an export and
Stuart McCullochf3173222012-06-07 21:57:32 +00001261 */
1262
1263 void fixupAttributes(Attrs attributes) {
1264 // Convert any attribute values that have macros.
1265 for (String key : attributes.keySet()) {
1266 String value = attributes.get(key);
1267 if (value.indexOf('$') >= 0) {
1268 value = getReplacer().process(value);
1269 attributes.put(key, value);
1270 }
1271 }
1272
1273 }
1274
1275 /**
1276 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1277 * can add a remove-attribute: directive with a regular expression for
1278 * attributes that need to be removed. We also remove all attributes that
1279 * have a value of !. This allows you to use macros with ${if} to remove
1280 * values.
1281 */
1282
1283 void removeAttributes(Attrs attributes) {
1284 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1285
1286 if (remove != null) {
1287 Instructions removeInstr = new Instructions(remove);
1288 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1289 }
1290
1291 // Remove any ! valued attributes
Stuart McCulloch4482c702012-06-15 13:27:53 +00001292 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001293 String v = i.next().getValue();
1294 if (v.equals("!"))
1295 i.remove();
1296 }
1297 }
1298
1299 /**
1300 * Calculate a version from a version policy.
1301 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001302 * @param version
1303 * The actual exported version
1304 * @param impl
1305 * true for implementations and false for clients
Stuart McCullochf3173222012-06-07 21:57:32 +00001306 */
1307
1308 String calculateVersionRange(String version, boolean impl) {
1309 setProperty("@", version);
1310 try {
1311 return getVersionPolicy(impl);
1312 }
1313 finally {
1314 unsetProperty("@");
1315 }
1316 }
1317
1318 /**
1319 * Add the uses clauses. This method iterates over the exports and cal
1320 *
1321 * @param exports
1322 * @param uses
1323 * @throws MojoExecutionException
1324 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001325 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001326 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1327 return;
1328
1329 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1330 PackageRef packageRef = i.next();
1331 String packageName = packageRef.getFQN();
1332 setProperty(CURRENT_PACKAGE, packageName);
1333 try {
1334 doUses(packageRef, exports, uses, imports);
1335 }
1336 finally {
1337 unsetProperty(CURRENT_PACKAGE);
1338 }
1339
1340 }
1341 }
1342
1343 /**
1344 * @param packageName
1345 * @param exports
1346 * @param uses
1347 * @param imports
1348 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001349 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch4482c702012-06-15 13:27:53 +00001350 Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001351 Attrs clause = exports.get(packageRef);
1352
1353 // Check if someone already set the uses: directive
1354 String override = clause.get(USES_DIRECTIVE);
1355 if (override == null)
1356 override = USES_USES;
1357
1358 // Get the used packages
1359 Collection<PackageRef> usedPackages = uses.get(packageRef);
1360
1361 if (usedPackages != null) {
1362
1363 // Only do a uses on exported or imported packages
1364 // and uses should also not contain our own package
1365 // name
1366 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1367 sharedPackages.addAll(imports.keySet());
1368 sharedPackages.addAll(exports.keySet());
1369 sharedPackages.retainAll(usedPackages);
1370 sharedPackages.remove(packageRef);
1371
1372 StringBuilder sb = new StringBuilder();
1373 String del = "";
1374 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1375 PackageRef usedPackage = u.next();
1376 if (!usedPackage.isJava()) {
1377 sb.append(del);
1378 sb.append(usedPackage.getFQN());
1379 del = ",";
1380 }
1381 }
1382 if (override.indexOf('$') >= 0) {
1383 setProperty(CURRENT_USES, sb.toString());
1384 override = getReplacer().process(override);
1385 unsetProperty(CURRENT_USES);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001386 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001387 // This is for backward compatibility 0.0.287
1388 // can be deprecated over time
Stuart McCulloch4482c702012-06-15 13:27:53 +00001389 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochf3173222012-06-07 21:57:32 +00001390
1391 if (override.endsWith(","))
1392 override = override.substring(0, override.length() - 1);
1393 if (override.startsWith(","))
1394 override = override.substring(1);
1395 if (override.length() > 0) {
1396 clause.put(USES_DIRECTIVE, override);
1397 }
1398 }
1399 }
1400
1401 /**
1402 * Transitively remove all elemens from unreachable through the uses link.
1403 *
1404 * @param name
1405 * @param unreachable
1406 */
1407 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1408 if (!unreachable.contains(name))
1409 return;
1410
1411 unreachable.remove(name);
1412
1413 List<PackageRef> ref = uses.get(name);
1414 if (ref != null) {
1415 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1416 PackageRef element = r.next();
1417 removeTransitive(element, unreachable);
1418 }
1419 }
1420 }
1421
1422 /**
1423 * Helper method to set the package info resource
1424 *
1425 * @param dir
1426 * @param key
1427 * @param value
1428 * @throws Exception
1429 */
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001430 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1431 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001432 if (r == null)
1433 return;
1434
1435 Properties p = new Properties();
Stuart McCullochf3173222012-06-07 21:57:32 +00001436 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001437 InputStream in = r.openInputStream();
1438 try {
1439 p.load(in);
Stuart McCullochf3173222012-06-07 21:57:32 +00001440 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001441 finally {
1442 in.close();
1443 }
1444 Attrs map = classpathExports.get(packageRef);
1445 if (map == null) {
1446 classpathExports.put(packageRef, map = new Attrs());
1447 }
1448 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1449 String key = t.nextElement();
1450 String value = map.get(key);
1451 if (value == null) {
1452 value = p.getProperty(key);
1453
1454 // Messy, to allow directives we need to
1455 // allow the value to start with a ':' since we cannot
1456 // encode this in a property name
1457
1458 if (value.startsWith(":")) {
1459 key = key + ":";
1460 value = value.substring(1);
1461 }
1462 map.put(key, value);
1463 }
1464 }
1465 }
1466 catch (Exception e) {
1467 msgs.NoSuchFile_(r);
Stuart McCullochf3173222012-06-07 21:57:32 +00001468 }
1469 }
1470
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001471 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001472 public void close() {
1473 if (diagnostics) {
1474 PrintStream out = System.err;
1475 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1476 out.println("Classpath used");
1477 for (Jar jar : getClasspath()) {
1478 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001479 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochf3173222012-06-07 21:57:32 +00001480 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001481 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1482 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1483 Map<String,Resource> dir = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +00001484 String name = entry.getKey().replace('/', '.');
1485 if (dir != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001486 out.printf(" %-30s %d%n", name, dir.size());
1487 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001488 out.printf(" %-30s <<empty>>%n", name);
1489 }
1490 }
1491 }
1492 }
1493
1494 super.close();
1495 if (dot != null)
1496 dot.close();
1497
1498 if (classpath != null)
1499 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1500 Jar jar = j.next();
1501 jar.close();
1502 }
1503 }
1504
1505 /**
1506 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch4482c702012-06-15 13:27:53 +00001507 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1508 * )? }
Stuart McCullochf3173222012-06-07 21:57:32 +00001509 *
1510 * @param args
1511 * @return
1512 */
1513 public String _findpath(String args[]) {
1514 return findPath("findpath", args, true);
1515 }
1516
1517 public String _findname(String args[]) {
1518 return findPath("findname", args, false);
1519 }
1520
1521 String findPath(String name, String[] args, boolean fullPathName) {
1522 if (args.length > 3) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001523 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1524 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochf3173222012-06-07 21:57:32 +00001525 return null;
1526 }
1527
1528 String regexp = ".*";
1529 String replace = null;
1530
1531 switch (args.length) {
1532 case 3 :
1533 replace = args[2];
1534 //$FALL-THROUGH$
1535 case 2 :
1536 regexp = args[1];
1537 }
1538 StringBuilder sb = new StringBuilder();
1539 String del = "";
1540
1541 Pattern expr = Pattern.compile(regexp);
1542 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1543 String path = e.next();
1544 if (!fullPathName) {
1545 int n = path.lastIndexOf('/');
1546 if (n >= 0) {
1547 path = path.substring(n + 1);
1548 }
1549 }
1550
1551 Matcher m = expr.matcher(path);
1552 if (m.matches()) {
1553 if (replace != null)
1554 path = m.replaceAll(replace);
1555
1556 sb.append(del);
1557 sb.append(path);
1558 del = ", ";
1559 }
1560 }
1561 return sb.toString();
1562 }
1563
Stuart McCulloch4482c702012-06-15 13:27:53 +00001564 public void putAll(Map<String,String> additional, boolean force) {
1565 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1566 Map.Entry<String,String> entry = i.next();
Stuart McCullochf3173222012-06-07 21:57:32 +00001567 if (force || getProperties().get(entry.getKey()) == null)
1568 setProperty(entry.getKey(), entry.getValue());
1569 }
1570 }
1571
1572 boolean firstUse = true;
1573
1574 public List<Jar> getClasspath() {
1575 if (firstUse) {
1576 firstUse = false;
1577 String cp = getProperty(CLASSPATH);
1578 if (cp != null)
1579 for (String s : split(cp)) {
1580 Jar jar = getJarFromName(s, "getting classpath");
1581 if (jar != null)
1582 addClasspath(jar);
1583 else
1584 warning("Cannot find entry on -classpath: %s", s);
1585 }
1586 }
1587 return classpath;
1588 }
1589
1590 public void addClasspath(Jar jar) {
1591 if (isPedantic() && jar.getResources().isEmpty())
1592 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1593
1594 classpath.add(jar);
1595 }
1596
1597 public void addClasspath(Collection< ? > jars) throws IOException {
1598 for (Object jar : jars) {
1599 if (jar instanceof Jar)
1600 addClasspath((Jar) jar);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001601 else if (jar instanceof File)
1602 addClasspath((File) jar);
1603 else if (jar instanceof String)
1604 addClasspath(getFile((String) jar));
Stuart McCullochf3173222012-06-07 21:57:32 +00001605 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001606 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001607 }
1608 }
1609
1610 public void addClasspath(File cp) throws IOException {
1611 if (!cp.exists())
1612 warning("File on classpath that does not exist: " + cp);
1613 Jar jar = new Jar(cp);
1614 addClose(jar);
1615 classpath.add(jar);
1616 }
1617
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001618 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001619 public void clear() {
1620 classpath.clear();
1621 }
1622
1623 public Jar getTarget() {
1624 return dot;
1625 }
1626
1627 private void analyzeBundleClasspath() throws Exception {
1628 Parameters bcp = getBundleClasspath();
1629
1630 if (bcp.isEmpty()) {
1631 analyzeJar(dot, "", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001632 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001633 boolean okToIncludeDirs = true;
1634
1635 for (String path : bcp.keySet()) {
1636 if (dot.getDirectories().containsKey(path)) {
1637 okToIncludeDirs = false;
1638 break;
1639 }
1640 }
1641
1642 for (String path : bcp.keySet()) {
1643 Attrs info = bcp.get(path);
1644
1645 if (path.equals(".")) {
1646 analyzeJar(dot, "", okToIncludeDirs);
1647 continue;
1648 }
1649 //
1650 // There are 3 cases:
1651 // - embedded JAR file
1652 // - directory
1653 // - error
1654 //
1655
1656 Resource resource = dot.getResource(path);
1657 if (resource != null) {
1658 try {
1659 Jar jar = new Jar(path);
1660 addClose(jar);
1661 EmbeddedResource.build(jar, resource);
1662 analyzeJar(jar, "", true);
1663 }
1664 catch (Exception e) {
1665 warning("Invalid bundle classpath entry: " + path + " " + e);
1666 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001667 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001668 if (dot.getDirectories().containsKey(path)) {
1669 // if directories are used, we should not have dot as we
1670 // would have the classes in these directories on the
1671 // class path twice.
1672 if (bcp.containsKey("."))
1673 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1674 path, path);
1675 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001676 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001677 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1678 warning("No sub JAR or directory " + path);
1679 }
1680 }
1681 }
1682
1683 }
1684 }
1685
1686 /**
1687 * We traverse through all the classes that we can find and calculate the
1688 * contained and referred set and uses. This method ignores the Bundle
1689 * classpath.
1690 *
1691 * @param jar
1692 * @param contained
1693 * @param referred
1694 * @param uses
1695 * @throws IOException
1696 */
1697 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001698 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochf3173222012-06-07 21:57:32 +00001699
1700 next: for (String path : jar.getResources().keySet()) {
1701 if (path.startsWith(prefix)) {
1702
1703 String relativePath = path.substring(prefix.length());
1704
1705 if (okToIncludeDirs) {
1706 int n = relativePath.lastIndexOf('/');
1707 if (n < 0)
1708 n = relativePath.length();
1709 String relativeDir = relativePath.substring(0, n);
1710
1711 PackageRef packageRef = getPackageRef(relativeDir);
1712 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1713 contained.put(packageRef);
1714
1715 // For each package we encounter for the first
1716 // time. Unfortunately we can only do this once
1717 // we found a class since the bcp has a tendency
1718 // to overlap
1719 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001720 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001721 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001722 }
1723 }
1724 }
1725
1726 // Check class resources, we need to analyze them
1727 if (path.endsWith(".class")) {
1728 Resource resource = jar.getResource(path);
1729 Clazz clazz;
1730 Attrs info = null;
1731
1732 try {
1733 InputStream in = resource.openInputStream();
1734 clazz = new Clazz(this, path, resource);
1735 try {
1736 // Check if we have a package-info
1737 if (relativePath.endsWith("/package-info.class")) {
1738 // package-info can contain an Export annotation
1739 info = new Attrs();
1740 parsePackageInfoClass(clazz, info);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001741 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001742 // Otherwise we just parse it simply
1743 clazz.parseClassFile();
1744 }
1745 }
1746 finally {
1747 in.close();
1748 }
1749 }
1750 catch (Throwable e) {
1751 error("Invalid class file %s (%s)", e, relativePath, e);
1752 e.printStackTrace();
1753 continue next;
1754 }
1755
1756 String calculatedPath = clazz.getClassName().getPath();
1757 if (!calculatedPath.equals(relativePath)) {
1758 // If there is a mismatch we
1759 // warning
1760 if (okToIncludeDirs) // assume already reported
1761 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001762 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001763 classspace.put(clazz.getClassName(), clazz);
1764 PackageRef packageRef = clazz.getClassName().getPackageRef();
1765
1766 if (!contained.containsKey(packageRef)) {
1767 contained.put(packageRef);
1768 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001769 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001770 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001771 }
1772 }
1773 if (info != null)
1774 contained.merge(packageRef, false, info);
1775
Stuart McCullochf3173222012-06-07 21:57:32 +00001776 // Look at the referred packages
1777 // and copy them to our baseline
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001778 Set<PackageRef> refs = Create.set();
Stuart McCullochf3173222012-06-07 21:57:32 +00001779 for (PackageRef p : clazz.getReferred()) {
1780 referred.put(p);
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001781 refs.add(p);
Stuart McCullochf3173222012-06-07 21:57:32 +00001782 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001783 refs.remove(packageRef);
1784 uses.addAll(packageRef, refs);
Stuart McCullochb32291a2012-07-16 14:10:57 +00001785
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001786 // Collect the API
1787 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochf3173222012-06-07 21:57:32 +00001788 }
1789 }
1790 }
1791 }
1792
1793 if (mismatched.size() > 0) {
1794 error("Classes found in the wrong directory: %s", mismatched);
1795 return false;
1796 }
1797 return true;
1798 }
1799
1800 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1801
1802 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1803 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1804 @Override
1805 public void annotation(Annotation a) {
1806 String name = a.name.getFQN();
1807 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1808
1809 // Check version
1810 String version = a.get("value");
1811 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1812 if (version != null) {
1813 version = getReplacer().process(version);
1814 if (Verifier.VERSION.matcher(version).matches())
1815 info.put(VERSION_ATTRIBUTE, version);
1816 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001817 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochf3173222012-06-07 21:57:32 +00001818 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001819 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001820 // Verify this matches with packageinfo
1821 String presentVersion = info.get(VERSION_ATTRIBUTE);
1822 try {
1823 Version av = new Version(presentVersion);
1824 Version bv = new Version(version);
1825 if (!av.equals(bv)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001826 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1827 .getClassName().getFQN());
Stuart McCullochf3173222012-06-07 21:57:32 +00001828 }
1829 }
1830 catch (Exception e) {
1831 // Ignore
1832 }
1833 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001834 } else if (name.equals(Export.class.getName())) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001835
Stuart McCulloch4482c702012-06-15 13:27:53 +00001836 // Check mandatory attributes
1837 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1838 if (!attrs.isEmpty()) {
1839 info.putAll(attrs);
1840 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1841 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001842
Stuart McCulloch4482c702012-06-15 13:27:53 +00001843 // Check optional attributes
1844 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1845 if (!attrs.isEmpty()) {
1846 info.putAll(attrs);
1847 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001848
Stuart McCulloch4482c702012-06-15 13:27:53 +00001849 // Check Included classes
1850 Object[] included = a.get(Export.INCLUDE);
1851 if (included != null && included.length > 0) {
1852 StringBuilder sb = new StringBuilder();
1853 String del = "";
1854 for (Object i : included) {
1855 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1856 if (m.matches()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001857 sb.append(del);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001858 sb.append(m.group(2));
Stuart McCullochf3173222012-06-07 21:57:32 +00001859 del = ",";
1860 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001861 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001862 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +00001863 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001864
1865 // Check Excluded classes
1866 Object[] excluded = a.get(Export.EXCLUDE);
1867 if (excluded != null && excluded.length > 0) {
1868 StringBuilder sb = new StringBuilder();
1869 String del = "";
1870 for (Object i : excluded) {
1871 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1872 if (m.matches()) {
1873 sb.append(del);
1874 sb.append(m.group(2));
1875 del = ",";
1876 }
1877 }
1878 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1879 }
1880
1881 // Check Uses
1882 Object[] uses = a.get(Export.USES);
1883 if (uses != null && uses.length > 0) {
1884 String old = info.get(USES_DIRECTIVE);
1885 if (old == null)
1886 old = "";
1887 StringBuilder sb = new StringBuilder(old);
1888 String del = sb.length() == 0 ? "" : ",";
1889
1890 for (Object use : uses) {
1891 sb.append(del);
1892 sb.append(use);
1893 del = ",";
1894 }
1895 info.put(USES_DIRECTIVE, sb.toString());
1896 }
1897 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001898 }
1899
1900 });
1901 }
1902
1903 /**
1904 * Clean up version parameters. Other builders use more fuzzy definitions of
1905 * the version syntax. This method cleans up such a version to match an OSGi
1906 * version.
1907 *
1908 * @param VERSION_STRING
1909 * @return
1910 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001911 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1912 Pattern.DOTALL);
1913 static Pattern fuzzyVersionRange = Pattern.compile(
1914 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1915 Pattern.DOTALL);
Stuart McCullochf3173222012-06-07 21:57:32 +00001916 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1917
1918 static Pattern nummeric = Pattern.compile("\\d*");
1919
1920 static public String cleanupVersion(String version) {
1921 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1922
1923 if (m.matches()) {
1924 return version;
1925 }
1926
1927 m = fuzzyVersionRange.matcher(version);
1928 if (m.matches()) {
1929 String prefix = m.group(1);
1930 String first = m.group(2);
1931 String last = m.group(3);
1932 String suffix = m.group(4);
1933 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1934 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001935
Stuart McCulloch4482c702012-06-15 13:27:53 +00001936 m = fuzzyVersion.matcher(version);
1937 if (m.matches()) {
1938 StringBuilder result = new StringBuilder();
1939 String major = removeLeadingZeroes(m.group(1));
1940 String minor = removeLeadingZeroes(m.group(3));
1941 String micro = removeLeadingZeroes(m.group(5));
1942 String qualifier = m.group(7);
1943
1944 if (major != null) {
1945 result.append(major);
1946 if (minor != null) {
1947 result.append(".");
1948 result.append(minor);
1949 if (micro != null) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001950 result.append(".");
Stuart McCulloch4482c702012-06-15 13:27:53 +00001951 result.append(micro);
Stuart McCullochf3173222012-06-07 21:57:32 +00001952 if (qualifier != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001953 result.append(".");
Stuart McCullochf3173222012-06-07 21:57:32 +00001954 cleanupModifier(result, qualifier);
1955 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001956 } else if (qualifier != null) {
1957 result.append(".0.");
1958 cleanupModifier(result, qualifier);
1959 }
1960 } else if (qualifier != null) {
1961 result.append(".0.0.");
1962 cleanupModifier(result, qualifier);
Stuart McCullochf3173222012-06-07 21:57:32 +00001963 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001964 return result.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +00001965 }
1966 }
1967 return version;
1968 }
1969
1970 private static String removeLeadingZeroes(String group) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001971 if (group == null)
1972 return null;
1973
Stuart McCullochf3173222012-06-07 21:57:32 +00001974 int n = 0;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001975 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochf3173222012-06-07 21:57:32 +00001976 n++;
1977 if (n == 0)
1978 return group;
1979
1980 return group.substring(n);
1981 }
1982
1983 static void cleanupModifier(StringBuilder result, String modifier) {
1984 Matcher m = fuzzyModifier.matcher(modifier);
1985 if (m.matches())
1986 modifier = m.group(2);
1987
1988 for (int i = 0; i < modifier.length(); i++) {
1989 char c = modifier.charAt(i);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001990 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochf3173222012-06-07 21:57:32 +00001991 result.append(c);
1992 }
1993 }
1994
1995 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
1996 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
1997
Stuart McCullochf3173222012-06-07 21:57:32 +00001998 public String getVersionPolicy(boolean implemented) {
1999 if (implemented) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00002000 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002001 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002002
Stuart McCulloch669423b2012-06-26 16:34:24 +00002003 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002004 }
2005
2006 /**
2007 * The extends macro traverses all classes and returns a list of class names
2008 * that extend a base class.
2009 */
2010
2011 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";
2012
2013 public String _classes(String... args) throws Exception {
2014 // Macro.verifyCommand(args, _classesHelp, new
2015 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
2016 // null}, 3,3);
2017
2018 Collection<Clazz> matched = getClasses(args);
2019 if (matched.isEmpty())
2020 return "";
2021
2022 return join(matched);
2023 }
2024
2025 public Collection<Clazz> getClasses(String... args) throws Exception {
2026
2027 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2028 for (int i = 1; i < args.length; i++) {
2029 if (args.length < i + 1)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002030 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2031 + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002032
2033 String typeName = args[i];
2034 if (typeName.equalsIgnoreCase("extending"))
2035 typeName = "extends";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002036 else if (typeName.equalsIgnoreCase("importing"))
2037 typeName = "imports";
2038 else if (typeName.equalsIgnoreCase("implementing"))
2039 typeName = "implements";
Stuart McCullochf3173222012-06-07 21:57:32 +00002040
2041 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2042
2043 if (type == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002044 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002045
2046 Instruction instr = null;
2047 if (Clazz.HAS_ARGUMENT.contains(type)) {
2048 String s = args[++i];
2049 instr = new Instruction(s);
2050 }
2051 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2052 Clazz clazz = c.next();
2053 if (!clazz.is(type, instr, this)) {
2054 c.remove();
2055 }
2056 }
2057 }
2058 return matched;
2059 }
2060
2061 /**
2062 * Get the exporter of a package ...
2063 */
2064
2065 public String _exporters(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002066 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochf3173222012-06-07 21:57:32 +00002067 null, 2, 2);
2068 StringBuilder sb = new StringBuilder();
2069 String del = "";
2070 String pack = args[1].replace('.', '/');
2071 for (Jar jar : classpath) {
2072 if (jar.getDirectories().containsKey(pack)) {
2073 sb.append(del);
2074 sb.append(jar.getName());
2075 }
2076 }
2077 return sb.toString();
2078 }
2079
Stuart McCulloch4482c702012-06-15 13:27:53 +00002080 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochf3173222012-06-07 21:57:32 +00002081 return classspace;
2082 }
2083
2084 /**
2085 * Locate a resource on the class path.
2086 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002087 * @param path
2088 * Path of the reosurce
Stuart McCullochf3173222012-06-07 21:57:32 +00002089 * @return A resource or <code>null</code>
2090 */
2091 public Resource findResource(String path) {
2092 for (Jar entry : getClasspath()) {
2093 Resource r = entry.getResource(path);
2094 if (r != null)
2095 return r;
2096 }
2097 return null;
2098 }
2099
2100 /**
2101 * Find a clazz on the class path. This class has been parsed.
2102 *
2103 * @param path
2104 * @return
2105 */
2106 public Clazz findClass(TypeRef typeRef) throws Exception {
2107 Clazz c = classspace.get(typeRef);
2108 if (c != null)
2109 return c;
2110
2111 c = importedClassesCache.get(typeRef);
2112 if (c != null)
2113 return c;
2114
2115 Resource r = findResource(typeRef.getPath());
2116 if (r == null) {
2117 getClass().getClassLoader();
2118 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2119 if (url != null)
2120 r = new URLResource(url);
2121 }
2122 if (r != null) {
2123 c = new Clazz(this, typeRef.getPath(), r);
2124 c.parseClassFile();
2125 importedClassesCache.put(typeRef, c);
2126 }
2127 return c;
2128 }
2129
2130 /**
2131 * Answer the bundle version.
2132 *
2133 * @return
2134 */
2135 public String getVersion() {
2136 String version = getProperty(BUNDLE_VERSION);
2137 if (version == null)
2138 version = "0.0.0";
2139 return version;
2140 }
2141
2142 public boolean isNoBundle() {
2143 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2144 }
2145
2146 public void referTo(TypeRef ref) {
2147 PackageRef pack = ref.getPackageRef();
2148 if (!referred.containsKey(pack))
2149 referred.put(pack, new Attrs());
2150 }
2151
2152 public void referToByBinaryName(String binaryClassName) {
2153 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2154 referTo(ref);
2155 }
2156
2157 /**
2158 * Ensure that we are running on the correct bnd.
2159 */
2160 void doRequireBnd() {
2161 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2162 if (require == null || require.isEmpty())
2163 return;
2164
Stuart McCulloch4482c702012-06-15 13:27:53 +00002165 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +00002166 map.put(Constants.VERSION_FILTER, getBndVersion());
2167
2168 for (String filter : require.keySet()) {
2169 try {
2170 Filter f = new Filter(filter);
2171 if (f.match(map))
2172 continue;
2173 error("%s fails %s", REQUIRE_BND, require.get(filter));
2174 }
2175 catch (Exception t) {
2176 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2177 }
2178 }
2179 }
2180
2181 /**
2182 * md5 macro
2183 */
2184
2185 static String _md5Help = "${md5;path}";
2186
2187 public String _md5(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002188 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2189 null, null, Pattern.compile("base64|hex")
2190 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002191
2192 Digester<MD5> digester = MD5.getDigester();
2193 Resource r = dot.getResource(args[1]);
2194 if (r == null)
2195 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2196
2197 IO.copy(r.openInputStream(), digester);
2198 boolean hex = args.length > 2 && args[2].equals("hex");
2199 if (hex)
2200 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch4482c702012-06-15 13:27:53 +00002201
2202 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochf3173222012-06-07 21:57:32 +00002203 }
2204
2205 /**
2206 * SHA1 macro
2207 */
2208
2209 static String _sha1Help = "${sha1;path}";
2210
2211 public String _sha1(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002212 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2213 null, null, Pattern.compile("base64|hex")
2214 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002215 Digester<SHA1> digester = SHA1.getDigester();
2216 Resource r = dot.getResource(args[1]);
2217 if (r == null)
2218 throw new FileNotFoundException("From sha1, not found " + args[1]);
2219
2220 IO.copy(r.openInputStream(), digester);
2221 return Base64.encodeBase64(digester.digest().digest());
2222 }
2223
2224 public Descriptor getDescriptor(String descriptor) {
2225 return descriptors.getDescriptor(descriptor);
2226 }
2227
2228 public TypeRef getTypeRef(String binaryClassName) {
2229 return descriptors.getTypeRef(binaryClassName);
2230 }
2231
2232 public PackageRef getPackageRef(String binaryName) {
2233 return descriptors.getPackageRef(binaryName);
2234 }
2235
2236 public TypeRef getTypeRefFromFQN(String fqn) {
2237 return descriptors.getTypeRefFromFQN(fqn);
2238 }
2239
2240 public TypeRef getTypeRefFromPath(String path) {
2241 return descriptors.getTypeRefFromPath(path);
2242 }
2243
2244 public boolean isImported(PackageRef packageRef) {
2245 return imports.containsKey(packageRef);
2246 }
2247
2248 /**
2249 * Merge the attributes of two maps, where the first map can contain
2250 * wildcarded names. The idea is that the first map contains instructions
2251 * (for example *) with a set of attributes. These patterns are matched
2252 * against the found packages in actual. If they match, the result is set
2253 * with the merged set of attributes. It is expected that the instructions
2254 * are ordered so that the instructor can define which pattern matches
2255 * first. Attributes in the instructions override any attributes from the
2256 * actual.<br/>
Stuart McCullochf3173222012-06-07 21:57:32 +00002257 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2258 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2259 * if the pattern starts with an exclamation mark, it will remove that
2260 * matches for that pattern (- the !) from the working set. So the following
2261 * patterns should work:
2262 * <ul>
2263 * <li>com.foo.bar</li>
2264 * <li>com.foo.*</li>
2265 * <li>com.foo.???</li>
2266 * <li>com.*.[^b][^a][^r]</li>
2267 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2268 * </ul>
2269 * Enough rope to hang the average developer I would say.
2270 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002271 * @param instructions
2272 * the instructions with patterns.
2273 * @param source
2274 * the actual found packages, contains no duplicates
Stuart McCullochf3173222012-06-07 21:57:32 +00002275 * @return Only the packages that were filtered by the given instructions
2276 */
2277
2278 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2279 Packages result = new Packages();
2280 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2281 Collections.sort(refs);
2282
2283 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2284 if (nomatch == null)
2285 nomatch = Create.set();
2286
2287 for (Instruction instruction : filters) {
2288 boolean match = false;
2289
2290 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2291 PackageRef packageRef = i.next();
2292
2293 if (packageRef.isMetaData()) {
2294 i.remove(); // no use checking it again
2295 continue;
2296 }
2297
2298 String packageName = packageRef.getFQN();
2299
2300 if (instruction.matches(packageName)) {
2301 match = true;
2302 if (!instruction.isNegated()) {
2303 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2304 instructions.get(instruction));
2305 }
2306 i.remove(); // Can never match again for another pattern
2307 }
2308 }
2309 if (!match && !instruction.isAny())
2310 nomatch.add(instruction);
2311 }
2312
2313 /*
2314 * Tricky. If we have umatched instructions they might indicate that we
2315 * want to have multiple decorators for the same package. So we check
2316 * the unmatched against the result list. If then then match and have
2317 * actually interesting properties then we merge them
2318 */
2319
2320 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2321 Instruction instruction = i.next();
2322
2323 // We assume the user knows what he is
2324 // doing and inserted a literal. So
2325 // we ignore any not matched literals
2326 if (instruction.isLiteral()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002327 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochf3173222012-06-07 21:57:32 +00002328 i.remove();
2329 continue;
2330 }
2331
2332 // Not matching a negated instruction looks
2333 // like an error ...
2334 if (instruction.isNegated()) {
2335 continue;
2336 }
2337
2338 // An optional instruction should not generate
2339 // an error
2340 if (instruction.isOptional()) {
2341 i.remove();
2342 continue;
2343 }
2344
2345 // boolean matched = false;
2346 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2347 // for (PackageRef ref : prefs) {
2348 // if (instruction.matches(ref.getFQN())) {
2349 // result.merge(ref, true, source.get(ref),
2350 // instructions.get(instruction));
2351 // matched = true;
2352 // }
2353 // }
2354 // if (matched)
2355 // i.remove();
2356 }
2357 return result;
2358 }
2359
2360 public void setDiagnostics(boolean b) {
2361 diagnostics = b;
2362 }
2363
2364 public Clazz.JAVA getLowestEE() {
2365 if (ees.isEmpty())
2366 return Clazz.JAVA.JDK1_4;
2367
2368 return ees.first();
2369 }
2370
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002371 public String _ee(@SuppressWarnings("unused")
2372 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00002373 return getLowestEE().getEE();
2374 }
2375
2376 /**
2377 * Calculate the output file for the given target. The strategy is:
2378 *
2379 * <pre>
2380 * parameter given if not null and not directory
2381 * if directory, this will be the output directory
2382 * based on bsn-version.jar
2383 * name of the source file if exists
2384 * Untitled-[n]
2385 * </pre>
2386 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002387 * @param output
2388 * may be null, otherwise a file path relative to base
Stuart McCullochf3173222012-06-07 21:57:32 +00002389 */
2390 public File getOutputFile(String output) {
2391
2392 if (output == null)
2393 output = get(Constants.OUTPUT);
2394
2395 File outputDir;
2396
2397 if (output != null) {
2398 File outputFile = getFile(output);
2399 if (outputFile.isDirectory())
2400 outputDir = outputFile;
2401 else
2402 return outputFile;
Stuart McCulloch4482c702012-06-15 13:27:53 +00002403 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002404 outputDir = getBase();
2405
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002406 Entry<String,Attrs> name = getBundleSymbolicName();
2407 if (name != null) {
2408 String bsn = name.getKey();
Stuart McCullochf3173222012-06-07 21:57:32 +00002409 String version = getBundleVersion();
2410 Version v = Version.parseVersion(version);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002411 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochf3173222012-06-07 21:57:32 +00002412 return new File(outputDir, outputName);
2413 }
2414
2415 File source = getJar().getSource();
2416 if (source != null) {
2417 String outputName = source.getName();
2418 return new File(outputDir, outputName);
2419 }
2420
Stuart McCulloch4482c702012-06-15 13:27:53 +00002421 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochf3173222012-06-07 21:57:32 +00002422 int n = 0;
2423 File f = getFile(outputDir, "Untitled");
2424 while (f.isFile()) {
2425 f = getFile(outputDir, "Untitled-" + n++);
2426 }
2427 return f;
2428 }
2429
2430 /**
2431 * Utility function to carefully save the file. Will create a backup if the
2432 * source file has the same path as the output. It will also only save if
2433 * the file was modified or the force flag is true
2434 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002435 * @param output
2436 * the output file, if null {@link #getOutputFile(String)} is
2437 * used.
2438 * @param force
2439 * if it needs to be overwritten
Stuart McCullochf3173222012-06-07 21:57:32 +00002440 * @throws Exception
2441 */
2442
2443 public boolean save(File output, boolean force) throws Exception {
2444 if (output == null)
2445 output = getOutputFile(null);
2446
2447 Jar jar = getJar();
2448 File source = jar.getSource();
2449
Stuart McCulloch4482c702012-06-15 13:27:53 +00002450 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2451 jar.lastModified() - output.lastModified());
Stuart McCullochf3173222012-06-07 21:57:32 +00002452
2453 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002454 File op = output.getParentFile();
2455 if (!op.exists() && !op.mkdirs()) {
2456 throw new IOException("Could not create directory " + op);
2457 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002458 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2459 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2460 if (!source.renameTo(bak)) {
2461 error("Could not create backup file %s", bak);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002462 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002463 source.delete();
2464 }
2465 try {
2466 trace("Saving jar to %s", output);
2467 getJar().write(output);
2468 }
2469 catch (Exception e) {
2470 output.delete();
2471 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2472 }
2473 return true;
2474 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002475 trace("Not modified %s", output);
2476 return false;
2477
Stuart McCullochf3173222012-06-07 21:57:32 +00002478 }
2479
2480 /**
2481 * Set default import and export instructions if none are set
2482 */
2483 public void setDefaults(String bsn, Version version) {
2484 if (getExportPackage() == null)
2485 setExportPackage("*");
2486 if (getImportPackage() == null)
2487 setExportPackage("*");
2488 if (bsn != null && getBundleSymbolicName() == null)
2489 setBundleSymbolicName(bsn);
2490 if (version != null && getBundleVersion() == null)
2491 setBundleVersion(version);
2492 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002493
2494 /**
Stuart McCullochb32291a2012-07-16 14:10:57 +00002495 * Remove the own references and optional java references from the uses lib
2496 *
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002497 * @param apiUses
2498 * @param removeJava
2499 * @return
2500 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00002501 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002502 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002503 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002504 e.getValue().remove(e.getKey());
2505 if (!removeJava)
2506 continue;
Stuart McCullochb32291a2012-07-16 14:10:57 +00002507
2508 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2509 if (i.next().isJava())
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002510 i.remove();
2511 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002512 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002513 return map;
2514 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002515
2516 /**
2517 * Return the classes for a given source package.
2518 *
2519 * @param source
2520 * the source package
2521 * @return a set of classes for the requested package.
2522 */
2523 public Set<Clazz> getClassspace(PackageRef source) {
2524 Set<Clazz> result = new HashSet<Clazz>();
2525 for (Clazz c : getClassspace().values()) {
2526 if (c.getClassName().getPackageRef() == source)
2527 result.add(c);
2528 }
2529 return result;
2530 }
2531
2532 /**
2533 * Create a cross reference from package source, to packages in dest
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002534 *
Stuart McCullochb32291a2012-07-16 14:10:57 +00002535 * @param source
2536 * @param dest
2537 * @param sourceModifiers
2538 * @return
2539 * @throws Exception
2540 */
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002541 public Map<Clazz.Def,List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest,
2542 final int sourceModifiers) throws Exception {
2543 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def,TypeRef>(Clazz.Def.class, TypeRef.class, true);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002544
2545 for (final Clazz clazz : getClassspace().values()) {
2546 if ((clazz.accessx & sourceModifiers) == 0)
2547 continue;
2548
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002549 if (source != null && source != clazz.getClassName().getPackageRef())
Stuart McCullochb32291a2012-07-16 14:10:57 +00002550 continue;
2551
2552 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2553 Clazz.Def member;
2554
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002555 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002556 public void extendsClass(TypeRef zuper) throws Exception {
2557 if (dest.contains(zuper.getPackageRef()))
2558 xref.add(clazz.getExtends(zuper), zuper);
2559 }
2560
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002561 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002562 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2563 for (TypeRef i : interfaces) {
2564 if (dest.contains(i.getPackageRef()))
2565 xref.add(clazz.getImplements(i), i);
2566 }
2567 }
2568
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002569 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002570 public void referTo(TypeRef to, int modifiers) {
2571 if (to.isJava())
2572 return;
2573
2574 if (!dest.contains(to.getPackageRef()))
2575 return;
2576
2577 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2578 xref.add(member, to);
2579 }
2580
2581 }
2582
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002583 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002584 public void method(Clazz.MethodDef defined) {
2585 member = defined;
2586 }
2587
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002588 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002589 public void field(Clazz.FieldDef defined) {
2590 member = defined;
2591 }
2592
2593 });
2594
2595 }
2596 return xref;
2597 }
2598
Stuart McCullochf3173222012-06-07 21:57:32 +00002599}