blob: 860fe080881379bd0504aa4f34de9b3e83889669 [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 McCulloch2a0afd62012-09-06 18:28:06 +0000701 public Packages getClasspathExports() {
702 return classpathExports;
703 }
704
Stuart McCullochf3173222012-06-07 21:57:32 +0000705 /**
706 * Get the version for this bnd
707 *
708 * @return version or unknown.
709 */
710 public String getBndVersion() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000711 return getBndInfo("version", "<unknown>");
Stuart McCullochf3173222012-06-07 21:57:32 +0000712 }
713
714 public long getBndLastModified() {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000715 String time = getBndInfo("lastmodified", "0");
Stuart McCullochf3173222012-06-07 21:57:32 +0000716 try {
717 return Long.parseLong(time);
718 }
719 catch (Exception e) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000720 // Ignore
Stuart McCullochf3173222012-06-07 21:57:32 +0000721 }
722 return 0;
723 }
724
725 public String getBndInfo(String key, String defaultValue) {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000726 if (bndInfo == null) {
727 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000728 Properties bndInfoLocal = new Properties();
729 URL url = Analyzer.class.getResource("bnd.info");
730 if (url != null) {
731 InputStream in = url.openStream();
732 try {
733 bndInfoLocal.load(in);
734 }
735 finally {
736 in.close();
737 }
738 }
739 bndInfo = bndInfoLocal;
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000740 }
741 catch (Exception e) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000742 e.printStackTrace();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000743 return defaultValue;
Stuart McCullochf3173222012-06-07 21:57:32 +0000744 }
745 }
Stuart McCulloch669423b2012-06-26 16:34:24 +0000746 String value = bndInfo.getProperty(key);
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000747 if (value == null)
748 return defaultValue;
749 return value;
Stuart McCullochf3173222012-06-07 21:57:32 +0000750 }
751
752 /**
753 * Merge the existing manifest with the instructions but do not override
754 * existing properties.
755 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000756 * @param manifest
757 * The manifest to merge with
Stuart McCullochf3173222012-06-07 21:57:32 +0000758 * @throws IOException
759 */
760 public void mergeManifest(Manifest manifest) throws IOException {
761 if (manifest != null) {
762 Attributes attributes = manifest.getMainAttributes();
763 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
764 Name name = (Name) i.next();
765 String key = name.toString();
766 // Dont want instructions
767 if (key.startsWith("-"))
768 continue;
769
770 if (getProperty(key) == null)
771 setProperty(key, attributes.getValue(name));
772 }
773 }
774 }
775
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000776 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000777 public void setBase(File file) {
778 super.setBase(file);
779 getProperties().put("project.dir", getBase().getAbsolutePath());
780 }
781
782 /**
783 * Set the classpath for this analyzer by file.
784 *
785 * @param classpath
786 * @throws IOException
787 */
788 public void setClasspath(File[] classpath) throws IOException {
789 List<Jar> list = new ArrayList<Jar>();
790 for (int i = 0; i < classpath.length; i++) {
791 if (classpath[i].exists()) {
792 Jar current = new Jar(classpath[i]);
793 list.add(current);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000794 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000795 error("Missing file on classpath: %s", classpath[i]);
796 }
797 }
798 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
799 addClasspath(i.next());
800 }
801 }
802
803 public void setClasspath(Jar[] classpath) {
804 for (int i = 0; i < classpath.length; i++) {
805 addClasspath(classpath[i]);
806 }
807 }
808
809 public void setClasspath(String[] classpath) {
810 for (int i = 0; i < classpath.length; i++) {
811 Jar jar = getJarFromName(classpath[i], " setting classpath");
812 if (jar != null)
813 addClasspath(jar);
814 }
815 }
816
817 /**
818 * Set the JAR file we are going to work in. This will read the JAR in
819 * memory.
820 *
821 * @param jar
822 * @return
823 * @throws IOException
824 */
825 public Jar setJar(File jar) throws IOException {
826 Jar jarx = new Jar(jar);
827 addClose(jarx);
828 return setJar(jarx);
829 }
830
831 /**
832 * Set the JAR directly we are going to work on.
833 *
834 * @param jar
835 * @return
836 */
837 public Jar setJar(Jar jar) {
838 if (dot != null)
839 removeClose(dot);
840
841 this.dot = jar;
842 if (dot != null)
843 addClose(dot);
844
845 return jar;
846 }
847
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000848 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000849 protected void begin() {
850 if (inited == false) {
851 inited = true;
852 super.begin();
853
854 updateModified(getBndLastModified(), "bnd last modified");
855 verifyManifestHeadersCase(getProperties());
856
857 }
858 }
859
860 /**
861 * Try to get a Jar from a file name/path or a url, or in last resort from
862 * the classpath name part of their files.
863 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000864 * @param name
865 * URL or filename relative to the base
866 * @param from
867 * Message identifying the caller for errors
Stuart McCullochf3173222012-06-07 21:57:32 +0000868 * @return null or a Jar with the contents for the name
869 */
870 Jar getJarFromName(String name, String from) {
871 File file = new File(name);
872 if (!file.isAbsolute())
873 file = new File(getBase(), name);
874
875 if (file.exists())
876 try {
877 Jar jar = new Jar(file);
878 addClose(jar);
879 return jar;
880 }
881 catch (Exception e) {
882 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
883 }
884 // It is not a file ...
885 try {
886 // Lets try a URL
887 URL url = new URL(name);
888 Jar jar = new Jar(fileName(url.getPath()));
889 addClose(jar);
890 URLConnection connection = url.openConnection();
891 InputStream in = connection.getInputStream();
892 long lastModified = connection.getLastModified();
893 if (lastModified == 0)
894 // We assume the worst :-(
895 lastModified = System.currentTimeMillis();
896 EmbeddedResource.build(jar, in, lastModified);
897 in.close();
898 return jar;
899 }
900 catch (IOException ee) {
901 // Check if we have files on the classpath
902 // that have the right name, allows us to specify those
903 // names instead of the full path.
904 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
905 Jar entry = cp.next();
906 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
907 return entry;
908 }
909 }
910 // error("Can not find jar file for " + from + ": " + name);
911 }
912 return null;
913 }
914
915 private String fileName(String path) {
916 int n = path.lastIndexOf('/');
917 if (n > 0)
918 return path.substring(n + 1);
919 return path;
920 }
921
922 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000923 * @param manifests
924 * @throws Exception
925 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000926 private void merge(Manifest result, Manifest old) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000927 if (old != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000928 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
929 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000930 Attributes.Name name = (Attributes.Name) entry.getKey();
931 String value = (String) entry.getValue();
932 if (name.toString().equalsIgnoreCase("Created-By"))
933 name = new Attributes.Name("Originally-Created-By");
934 if (!result.getMainAttributes().containsKey(name))
935 result.getMainAttributes().put(name, value);
936 }
937
938 // do not overwrite existing entries
Stuart McCulloch4482c702012-06-15 13:27:53 +0000939 Map<String,Attributes> oldEntries = old.getEntries();
940 Map<String,Attributes> newEntries = result.getEntries();
941 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
942 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000943 if (!newEntries.containsKey(entry.getKey())) {
944 newEntries.put(entry.getKey(), entry.getValue());
945 }
946 }
947 }
948 }
949
950 /**
951 * Bnd is case sensitive for the instructions so we better check people are
952 * not using an invalid case. We do allow this to set headers that should
953 * not be processed by us but should be used by the framework.
954 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000955 * @param properties
956 * Properties to verify.
Stuart McCullochf3173222012-06-07 21:57:32 +0000957 */
958
959 void verifyManifestHeadersCase(Properties properties) {
960 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
961 String header = (String) i.next();
962 for (int j = 0; j < headers.length; j++) {
963 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
964 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
965 + header + " and expecting: " + headers[j]);
966 break;
967 }
968 }
969 }
970 }
971
972 /**
973 * We will add all exports to the imports unless there is a -noimport
974 * directive specified on an export. This directive is skipped for the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000975 * manifest. We also remove any version parameter so that augmentImports can
976 * do the version policy. The following method is really tricky and evolved
977 * over time. Coming from the original background of OSGi, it was a weird
978 * idea for me to have a public package that should not be substitutable. I
979 * was so much convinced that this was the right rule that I rücksichtlos
980 * imported them all. Alas, the real world was more subtle than that. It
981 * turns out that it is not a good idea to always import. First, there must
982 * be a need to import, i.e. there must be a contained package that refers
983 * to the exported package for it to make use importing that package.
984 * Second, if an exported package refers to an internal package than it
985 * should not be imported. Additionally, it is necessary to treat the
986 * exports in groups. If an exported package refers to another exported
987 * packages than it must be in the same group. A framework can only
988 * substitute exports for imports for the whole of such a group. WHY?????
989 * Not clear anymore ...
Stuart McCullochf3173222012-06-07 21:57:32 +0000990 */
991 Packages doExportsToImports(Packages exports) {
992
993 // private packages = contained - exported.
994 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
995 privatePackages.removeAll(exports.keySet());
996
997 // private references = ∀ p : private packages | uses(p)
998 Set<PackageRef> privateReferences = newSet();
999 for (PackageRef p : privatePackages) {
1000 Collection<PackageRef> uses = this.uses.get(p);
1001 if (uses != null)
1002 privateReferences.addAll(uses);
1003 }
1004
1005 // Assume we are going to export all exported packages
1006 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
1007
1008 // Remove packages that are not referenced privately
1009 toBeImported.retainAll(privateReferences);
1010
1011 // Not necessary to import anything that is already
1012 // imported in the Import-Package statement.
1013 // TODO toBeImported.removeAll(imports.keySet());
1014
1015 // Remove exported packages that are referring to
1016 // private packages.
1017 // Each exported package has a uses clause. We just use
1018 // the used packages for each exported package to find out
1019 // if it refers to an internal package.
1020 //
1021
1022 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1023 PackageRef next = i.next();
1024 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1025
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00001026 // We had an NPE on usedByExportedPackage in GF.
1027 // I guess this can happen with hard coded
1028 // imports that do not match reality ...
1029 if (usedByExportedPackage == null || usedByExportedPackage.isEmpty()) {
1030 continue;
1031 }
1032
Stuart McCullochf3173222012-06-07 21:57:32 +00001033 for (PackageRef privatePackage : privatePackages) {
1034 if (usedByExportedPackage.contains(privatePackage)) {
1035 i.remove();
1036 break;
1037 }
1038 }
1039 }
1040
1041 // Clean up attributes and generate result map
1042 Packages result = new Packages();
1043 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1044 PackageRef ep = i.next();
1045 Attrs parameters = exports.get(ep);
1046
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001047 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochf3173222012-06-07 21:57:32 +00001048 if (noimport != null && noimport.equalsIgnoreCase("true"))
1049 continue;
1050
1051 // // we can't substitute when there is no version
1052 // String version = parameters.get(VERSION_ATTRIBUTE);
1053 // if (version == null) {
1054 // if (isPedantic())
1055 // warning(
1056 // "Cannot automatically import exported package %s because it has no version defined",
1057 // ep);
1058 // continue;
1059 // }
1060
1061 parameters = new Attrs();
1062 parameters.remove(VERSION_ATTRIBUTE);
1063 result.put(ep, parameters);
1064 }
1065 return result;
1066 }
1067
1068 public boolean referred(PackageRef packageName) {
1069 // return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001070 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001071 if (!contained.getKey().equals(packageName)) {
1072 if (contained.getValue().contains(packageName))
1073 return true;
1074 }
1075 }
1076 return false;
1077 }
1078
1079 /**
Stuart McCullochf3173222012-06-07 21:57:32 +00001080 * @param jar
1081 */
1082 private void getExternalExports(Jar jar, Packages classpathExports) {
1083 try {
1084 Manifest m = jar.getManifest();
1085 if (m != null) {
1086 Domain domain = Domain.domain(m);
1087 Parameters exported = domain.getExportPackage();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001088 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001089 PackageRef ref = getPackageRef(e.getKey());
1090 if (!classpathExports.containsKey(ref)) {
1091 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1092 // jar.getBsn()+"-"+jar.getVersion());
1093
1094 classpathExports.put(ref, e.getValue());
1095 }
1096 }
1097 }
1098 }
1099 catch (Exception e) {
1100 warning("Erroneous Manifest for " + jar + " " + e);
1101 }
1102 }
1103
1104 /**
1105 * Find some more information about imports in manifest and other places. It
1106 * is assumed that the augmentsExports has already copied external attrs
1107 * from the classpathExports.
1108 *
1109 * @throws Exception
1110 */
1111 void augmentImports(Packages imports, Packages exports) throws Exception {
1112 List<PackageRef> noimports = Create.list();
1113 Set<PackageRef> provided = findProvidedPackages();
1114
1115 for (PackageRef packageRef : imports.keySet()) {
1116 String packageName = packageRef.getFQN();
1117
1118 setProperty(CURRENT_PACKAGE, packageName);
1119 try {
1120 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001121 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochf3173222012-06-07 21:57:32 +00001122
1123 String exportVersion = exportAttributes.getVersion();
1124 String importRange = importAttributes.getVersion();
1125
1126 if (exportVersion == null) {
1127 // TODO Should check if the source is from a bundle.
1128
Stuart McCulloch4482c702012-06-15 13:27:53 +00001129 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001130
1131 //
1132 // Version Policy - Import version substitution. We
1133 // calculate the export version and then allow the
1134 // import version attribute to use it in a substitution
1135 // by using a ${@} macro. The export version can
1136 // be defined externally or locally
1137 //
1138
1139 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch4482c702012-06-15 13:27:53 +00001140 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochf3173222012-06-07 21:57:32 +00001141
1142 exportVersion = cleanupVersion(exportVersion);
1143
1144 try {
1145 setProperty("@", exportVersion);
1146
1147 if (importRange != null) {
1148 importRange = cleanupVersion(importRange);
1149 importRange = getReplacer().process(importRange);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001150 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001151 importRange = getVersionPolicy(provider);
1152
1153 }
1154 finally {
1155 unsetProperty("@");
1156 }
1157 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1158 }
1159
1160 //
1161 // Check if exporter has mandatory attributes
1162 //
1163 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1164 if (mandatory != null) {
1165 String[] attrs = mandatory.split("\\s*,\\s*");
1166 for (int i = 0; i < attrs.length; i++) {
1167 if (!importAttributes.containsKey(attrs[i]))
1168 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1169 }
1170 }
1171
1172 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1173 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1174
1175 fixupAttributes(importAttributes);
1176 removeAttributes(importAttributes);
1177
1178 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1179 if (result == null)
1180 noimports.add(packageRef);
1181 }
1182 finally {
1183 unsetProperty(CURRENT_PACKAGE);
1184 }
1185 }
1186
1187 if (isPedantic() && noimports.size() != 0) {
1188 warning("Imports that lack version ranges: %s", noimports);
1189 }
1190 }
1191
1192 /**
1193 * Find the packages we depend on, where we implement an interface that is a
1194 * Provider Type. These packages, when we import them, must use the provider
1195 * policy.
1196 *
1197 * @throws Exception
1198 */
1199 Set<PackageRef> findProvidedPackages() throws Exception {
1200 Set<PackageRef> providers = Create.set();
1201 Set<TypeRef> cached = Create.set();
1202
1203 for (Clazz c : classspace.values()) {
1204 TypeRef[] interfaces = c.getInterfaces();
1205 if (interfaces != null)
1206 for (TypeRef t : interfaces)
1207 if (cached.contains(t) || isProvider(t)) {
1208 cached.add(t);
1209 providers.add(t.getPackageRef());
1210 }
1211 }
1212 return providers;
1213 }
1214
1215 private boolean isProvider(TypeRef t) throws Exception {
1216 Clazz c = findClass(t);
1217 if (c == null)
1218 return false;
1219
1220 if (c.annotations == null)
1221 return false;
1222
1223 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1224 boolean result = c.annotations.contains(pt);
1225 return result;
1226 }
1227
1228 /**
1229 * Provide any macro substitutions and versions for exported packages.
1230 */
1231
1232 void augmentExports(Packages exports) {
1233 for (PackageRef packageRef : exports.keySet()) {
1234 String packageName = packageRef.getFQN();
1235 setProperty(CURRENT_PACKAGE, packageName);
1236 try {
1237 Attrs attributes = exports.get(packageRef);
1238 Attrs exporterAttributes = classpathExports.get(packageRef);
1239 if (exporterAttributes == null)
1240 continue;
1241
Stuart McCulloch4482c702012-06-15 13:27:53 +00001242 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001243 String key = entry.getKey();
1244 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1245 key = VERSION_ATTRIBUTE;
1246
1247 // dont overwrite and no directives
1248 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1249 attributes.put(key, entry.getValue());
1250 }
1251 }
1252
1253 fixupAttributes(attributes);
1254 removeAttributes(attributes);
1255
1256 }
1257 finally {
1258 unsetProperty(CURRENT_PACKAGE);
1259 }
1260 }
1261 }
1262
1263 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001264 * Fixup Attributes Execute any macros on an export and
Stuart McCullochf3173222012-06-07 21:57:32 +00001265 */
1266
1267 void fixupAttributes(Attrs attributes) {
1268 // Convert any attribute values that have macros.
1269 for (String key : attributes.keySet()) {
1270 String value = attributes.get(key);
1271 if (value.indexOf('$') >= 0) {
1272 value = getReplacer().process(value);
1273 attributes.put(key, value);
1274 }
1275 }
1276
1277 }
1278
1279 /**
1280 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1281 * can add a remove-attribute: directive with a regular expression for
1282 * attributes that need to be removed. We also remove all attributes that
1283 * have a value of !. This allows you to use macros with ${if} to remove
1284 * values.
1285 */
1286
1287 void removeAttributes(Attrs attributes) {
1288 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1289
1290 if (remove != null) {
1291 Instructions removeInstr = new Instructions(remove);
1292 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1293 }
1294
1295 // Remove any ! valued attributes
Stuart McCulloch4482c702012-06-15 13:27:53 +00001296 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001297 String v = i.next().getValue();
1298 if (v.equals("!"))
1299 i.remove();
1300 }
1301 }
1302
1303 /**
1304 * Calculate a version from a version policy.
1305 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001306 * @param version
1307 * The actual exported version
1308 * @param impl
1309 * true for implementations and false for clients
Stuart McCullochf3173222012-06-07 21:57:32 +00001310 */
1311
1312 String calculateVersionRange(String version, boolean impl) {
1313 setProperty("@", version);
1314 try {
1315 return getVersionPolicy(impl);
1316 }
1317 finally {
1318 unsetProperty("@");
1319 }
1320 }
1321
1322 /**
1323 * Add the uses clauses. This method iterates over the exports and cal
1324 *
1325 * @param exports
1326 * @param uses
1327 * @throws MojoExecutionException
1328 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001329 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001330 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1331 return;
1332
1333 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1334 PackageRef packageRef = i.next();
1335 String packageName = packageRef.getFQN();
1336 setProperty(CURRENT_PACKAGE, packageName);
1337 try {
1338 doUses(packageRef, exports, uses, imports);
1339 }
1340 finally {
1341 unsetProperty(CURRENT_PACKAGE);
1342 }
1343
1344 }
1345 }
1346
1347 /**
1348 * @param packageName
1349 * @param exports
1350 * @param uses
1351 * @param imports
1352 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00001353 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch4482c702012-06-15 13:27:53 +00001354 Packages imports) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001355 Attrs clause = exports.get(packageRef);
1356
1357 // Check if someone already set the uses: directive
1358 String override = clause.get(USES_DIRECTIVE);
1359 if (override == null)
1360 override = USES_USES;
1361
1362 // Get the used packages
1363 Collection<PackageRef> usedPackages = uses.get(packageRef);
1364
1365 if (usedPackages != null) {
1366
1367 // Only do a uses on exported or imported packages
1368 // and uses should also not contain our own package
1369 // name
1370 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1371 sharedPackages.addAll(imports.keySet());
1372 sharedPackages.addAll(exports.keySet());
1373 sharedPackages.retainAll(usedPackages);
1374 sharedPackages.remove(packageRef);
1375
1376 StringBuilder sb = new StringBuilder();
1377 String del = "";
1378 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1379 PackageRef usedPackage = u.next();
1380 if (!usedPackage.isJava()) {
1381 sb.append(del);
1382 sb.append(usedPackage.getFQN());
1383 del = ",";
1384 }
1385 }
1386 if (override.indexOf('$') >= 0) {
1387 setProperty(CURRENT_USES, sb.toString());
1388 override = getReplacer().process(override);
1389 unsetProperty(CURRENT_USES);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001390 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00001391 // This is for backward compatibility 0.0.287
1392 // can be deprecated over time
Stuart McCulloch4482c702012-06-15 13:27:53 +00001393 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochf3173222012-06-07 21:57:32 +00001394
1395 if (override.endsWith(","))
1396 override = override.substring(0, override.length() - 1);
1397 if (override.startsWith(","))
1398 override = override.substring(1);
1399 if (override.length() > 0) {
1400 clause.put(USES_DIRECTIVE, override);
1401 }
1402 }
1403 }
1404
1405 /**
1406 * Transitively remove all elemens from unreachable through the uses link.
1407 *
1408 * @param name
1409 * @param unreachable
1410 */
1411 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1412 if (!unreachable.contains(name))
1413 return;
1414
1415 unreachable.remove(name);
1416
1417 List<PackageRef> ref = uses.get(name);
1418 if (ref != null) {
1419 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1420 PackageRef element = r.next();
1421 removeTransitive(element, unreachable);
1422 }
1423 }
1424 }
1425
1426 /**
1427 * Helper method to set the package info resource
1428 *
1429 * @param dir
1430 * @param key
1431 * @param value
1432 * @throws Exception
1433 */
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001434 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1435 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001436 if (r == null)
1437 return;
1438
1439 Properties p = new Properties();
Stuart McCullochf3173222012-06-07 21:57:32 +00001440 try {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001441 InputStream in = r.openInputStream();
1442 try {
1443 p.load(in);
Stuart McCullochf3173222012-06-07 21:57:32 +00001444 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001445 finally {
1446 in.close();
1447 }
1448 Attrs map = classpathExports.get(packageRef);
1449 if (map == null) {
1450 classpathExports.put(packageRef, map = new Attrs());
1451 }
1452 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1453 String key = t.nextElement();
1454 String value = map.get(key);
1455 if (value == null) {
1456 value = p.getProperty(key);
1457
1458 // Messy, to allow directives we need to
1459 // allow the value to start with a ':' since we cannot
1460 // encode this in a property name
1461
1462 if (value.startsWith(":")) {
1463 key = key + ":";
1464 value = value.substring(1);
1465 }
1466 map.put(key, value);
1467 }
1468 }
1469 }
1470 catch (Exception e) {
1471 msgs.NoSuchFile_(r);
Stuart McCullochf3173222012-06-07 21:57:32 +00001472 }
1473 }
1474
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001475 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001476 public void close() {
1477 if (diagnostics) {
1478 PrintStream out = System.err;
1479 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1480 out.println("Classpath used");
1481 for (Jar jar : getClasspath()) {
1482 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001483 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochf3173222012-06-07 21:57:32 +00001484 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001485 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1486 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1487 Map<String,Resource> dir = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +00001488 String name = entry.getKey().replace('/', '.');
1489 if (dir != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001490 out.printf(" %-30s %d%n", name, dir.size());
1491 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001492 out.printf(" %-30s <<empty>>%n", name);
1493 }
1494 }
1495 }
1496 }
1497
1498 super.close();
1499 if (dot != null)
1500 dot.close();
1501
1502 if (classpath != null)
1503 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1504 Jar jar = j.next();
1505 jar.close();
1506 }
1507 }
1508
1509 /**
1510 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch4482c702012-06-15 13:27:53 +00001511 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1512 * )? }
Stuart McCullochf3173222012-06-07 21:57:32 +00001513 *
1514 * @param args
1515 * @return
1516 */
1517 public String _findpath(String args[]) {
1518 return findPath("findpath", args, true);
1519 }
1520
1521 public String _findname(String args[]) {
1522 return findPath("findname", args, false);
1523 }
1524
1525 String findPath(String name, String[] args, boolean fullPathName) {
1526 if (args.length > 3) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001527 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1528 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochf3173222012-06-07 21:57:32 +00001529 return null;
1530 }
1531
1532 String regexp = ".*";
1533 String replace = null;
1534
1535 switch (args.length) {
1536 case 3 :
1537 replace = args[2];
1538 //$FALL-THROUGH$
1539 case 2 :
1540 regexp = args[1];
1541 }
1542 StringBuilder sb = new StringBuilder();
1543 String del = "";
1544
1545 Pattern expr = Pattern.compile(regexp);
1546 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1547 String path = e.next();
1548 if (!fullPathName) {
1549 int n = path.lastIndexOf('/');
1550 if (n >= 0) {
1551 path = path.substring(n + 1);
1552 }
1553 }
1554
1555 Matcher m = expr.matcher(path);
1556 if (m.matches()) {
1557 if (replace != null)
1558 path = m.replaceAll(replace);
1559
1560 sb.append(del);
1561 sb.append(path);
1562 del = ", ";
1563 }
1564 }
1565 return sb.toString();
1566 }
1567
Stuart McCulloch4482c702012-06-15 13:27:53 +00001568 public void putAll(Map<String,String> additional, boolean force) {
1569 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1570 Map.Entry<String,String> entry = i.next();
Stuart McCullochf3173222012-06-07 21:57:32 +00001571 if (force || getProperties().get(entry.getKey()) == null)
1572 setProperty(entry.getKey(), entry.getValue());
1573 }
1574 }
1575
1576 boolean firstUse = true;
1577
1578 public List<Jar> getClasspath() {
1579 if (firstUse) {
1580 firstUse = false;
1581 String cp = getProperty(CLASSPATH);
1582 if (cp != null)
1583 for (String s : split(cp)) {
1584 Jar jar = getJarFromName(s, "getting classpath");
1585 if (jar != null)
1586 addClasspath(jar);
1587 else
1588 warning("Cannot find entry on -classpath: %s", s);
1589 }
1590 }
1591 return classpath;
1592 }
1593
1594 public void addClasspath(Jar jar) {
1595 if (isPedantic() && jar.getResources().isEmpty())
1596 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1597
1598 classpath.add(jar);
1599 }
1600
1601 public void addClasspath(Collection< ? > jars) throws IOException {
1602 for (Object jar : jars) {
1603 if (jar instanceof Jar)
1604 addClasspath((Jar) jar);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001605 else if (jar instanceof File)
1606 addClasspath((File) jar);
1607 else if (jar instanceof String)
1608 addClasspath(getFile((String) jar));
Stuart McCullochf3173222012-06-07 21:57:32 +00001609 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001610 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001611 }
1612 }
1613
1614 public void addClasspath(File cp) throws IOException {
1615 if (!cp.exists())
1616 warning("File on classpath that does not exist: " + cp);
1617 Jar jar = new Jar(cp);
1618 addClose(jar);
1619 classpath.add(jar);
1620 }
1621
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001622 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001623 public void clear() {
1624 classpath.clear();
1625 }
1626
1627 public Jar getTarget() {
1628 return dot;
1629 }
1630
1631 private void analyzeBundleClasspath() throws Exception {
1632 Parameters bcp = getBundleClasspath();
1633
1634 if (bcp.isEmpty()) {
1635 analyzeJar(dot, "", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001636 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001637 boolean okToIncludeDirs = true;
1638
1639 for (String path : bcp.keySet()) {
1640 if (dot.getDirectories().containsKey(path)) {
1641 okToIncludeDirs = false;
1642 break;
1643 }
1644 }
1645
1646 for (String path : bcp.keySet()) {
1647 Attrs info = bcp.get(path);
1648
1649 if (path.equals(".")) {
1650 analyzeJar(dot, "", okToIncludeDirs);
1651 continue;
1652 }
1653 //
1654 // There are 3 cases:
1655 // - embedded JAR file
1656 // - directory
1657 // - error
1658 //
1659
1660 Resource resource = dot.getResource(path);
1661 if (resource != null) {
1662 try {
1663 Jar jar = new Jar(path);
1664 addClose(jar);
1665 EmbeddedResource.build(jar, resource);
1666 analyzeJar(jar, "", true);
1667 }
1668 catch (Exception e) {
1669 warning("Invalid bundle classpath entry: " + path + " " + e);
1670 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001671 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001672 if (dot.getDirectories().containsKey(path)) {
1673 // if directories are used, we should not have dot as we
1674 // would have the classes in these directories on the
1675 // class path twice.
1676 if (bcp.containsKey("."))
1677 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1678 path, path);
1679 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001680 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001681 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1682 warning("No sub JAR or directory " + path);
1683 }
1684 }
1685 }
1686
1687 }
1688 }
1689
1690 /**
1691 * We traverse through all the classes that we can find and calculate the
1692 * contained and referred set and uses. This method ignores the Bundle
1693 * classpath.
1694 *
1695 * @param jar
1696 * @param contained
1697 * @param referred
1698 * @param uses
1699 * @throws IOException
1700 */
1701 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001702 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochf3173222012-06-07 21:57:32 +00001703
1704 next: for (String path : jar.getResources().keySet()) {
1705 if (path.startsWith(prefix)) {
1706
1707 String relativePath = path.substring(prefix.length());
1708
1709 if (okToIncludeDirs) {
1710 int n = relativePath.lastIndexOf('/');
1711 if (n < 0)
1712 n = relativePath.length();
1713 String relativeDir = relativePath.substring(0, n);
1714
1715 PackageRef packageRef = getPackageRef(relativeDir);
1716 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1717 contained.put(packageRef);
1718
1719 // For each package we encounter for the first
1720 // time. Unfortunately we can only do this once
1721 // we found a class since the bcp has a tendency
1722 // to overlap
1723 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001724 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001725 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001726 }
1727 }
1728 }
1729
1730 // Check class resources, we need to analyze them
1731 if (path.endsWith(".class")) {
1732 Resource resource = jar.getResource(path);
1733 Clazz clazz;
1734 Attrs info = null;
1735
1736 try {
1737 InputStream in = resource.openInputStream();
1738 clazz = new Clazz(this, path, resource);
1739 try {
1740 // Check if we have a package-info
1741 if (relativePath.endsWith("/package-info.class")) {
1742 // package-info can contain an Export annotation
1743 info = new Attrs();
1744 parsePackageInfoClass(clazz, info);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001745 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001746 // Otherwise we just parse it simply
1747 clazz.parseClassFile();
1748 }
1749 }
1750 finally {
1751 in.close();
1752 }
1753 }
1754 catch (Throwable e) {
1755 error("Invalid class file %s (%s)", e, relativePath, e);
1756 e.printStackTrace();
1757 continue next;
1758 }
1759
1760 String calculatedPath = clazz.getClassName().getPath();
1761 if (!calculatedPath.equals(relativePath)) {
1762 // If there is a mismatch we
1763 // warning
1764 if (okToIncludeDirs) // assume already reported
1765 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001766 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001767 classspace.put(clazz.getClassName(), clazz);
1768 PackageRef packageRef = clazz.getClassName().getPackageRef();
1769
1770 if (!contained.containsKey(packageRef)) {
1771 contained.put(packageRef);
1772 if (!packageRef.isMetaData()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001773 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCulloch669423b2012-06-26 16:34:24 +00001774 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochf3173222012-06-07 21:57:32 +00001775 }
1776 }
1777 if (info != null)
1778 contained.merge(packageRef, false, info);
1779
Stuart McCullochf3173222012-06-07 21:57:32 +00001780 // Look at the referred packages
1781 // and copy them to our baseline
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001782 Set<PackageRef> refs = Create.set();
Stuart McCullochf3173222012-06-07 21:57:32 +00001783 for (PackageRef p : clazz.getReferred()) {
1784 referred.put(p);
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001785 refs.add(p);
Stuart McCullochf3173222012-06-07 21:57:32 +00001786 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001787 refs.remove(packageRef);
1788 uses.addAll(packageRef, refs);
Stuart McCullochb32291a2012-07-16 14:10:57 +00001789
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001790 // Collect the API
1791 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochf3173222012-06-07 21:57:32 +00001792 }
1793 }
1794 }
1795 }
1796
1797 if (mismatched.size() > 0) {
1798 error("Classes found in the wrong directory: %s", mismatched);
1799 return false;
1800 }
1801 return true;
1802 }
1803
1804 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1805
1806 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1807 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1808 @Override
1809 public void annotation(Annotation a) {
1810 String name = a.name.getFQN();
1811 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1812
1813 // Check version
1814 String version = a.get("value");
1815 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1816 if (version != null) {
1817 version = getReplacer().process(version);
1818 if (Verifier.VERSION.matcher(version).matches())
1819 info.put(VERSION_ATTRIBUTE, version);
1820 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001821 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochf3173222012-06-07 21:57:32 +00001822 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001823 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001824 // Verify this matches with packageinfo
1825 String presentVersion = info.get(VERSION_ATTRIBUTE);
1826 try {
1827 Version av = new Version(presentVersion);
1828 Version bv = new Version(version);
1829 if (!av.equals(bv)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001830 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1831 .getClassName().getFQN());
Stuart McCullochf3173222012-06-07 21:57:32 +00001832 }
1833 }
1834 catch (Exception e) {
1835 // Ignore
1836 }
1837 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001838 } else if (name.equals(Export.class.getName())) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001839
Stuart McCulloch4482c702012-06-15 13:27:53 +00001840 // Check mandatory attributes
1841 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1842 if (!attrs.isEmpty()) {
1843 info.putAll(attrs);
1844 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1845 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001846
Stuart McCulloch4482c702012-06-15 13:27:53 +00001847 // Check optional attributes
1848 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1849 if (!attrs.isEmpty()) {
1850 info.putAll(attrs);
1851 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001852
Stuart McCulloch4482c702012-06-15 13:27:53 +00001853 // Check Included classes
1854 Object[] included = a.get(Export.INCLUDE);
1855 if (included != null && included.length > 0) {
1856 StringBuilder sb = new StringBuilder();
1857 String del = "";
1858 for (Object i : included) {
1859 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1860 if (m.matches()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001861 sb.append(del);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001862 sb.append(m.group(2));
Stuart McCullochf3173222012-06-07 21:57:32 +00001863 del = ",";
1864 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001865 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001866 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +00001867 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001868
1869 // Check Excluded classes
1870 Object[] excluded = a.get(Export.EXCLUDE);
1871 if (excluded != null && excluded.length > 0) {
1872 StringBuilder sb = new StringBuilder();
1873 String del = "";
1874 for (Object i : excluded) {
1875 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1876 if (m.matches()) {
1877 sb.append(del);
1878 sb.append(m.group(2));
1879 del = ",";
1880 }
1881 }
1882 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1883 }
1884
1885 // Check Uses
1886 Object[] uses = a.get(Export.USES);
1887 if (uses != null && uses.length > 0) {
1888 String old = info.get(USES_DIRECTIVE);
1889 if (old == null)
1890 old = "";
1891 StringBuilder sb = new StringBuilder(old);
1892 String del = sb.length() == 0 ? "" : ",";
1893
1894 for (Object use : uses) {
1895 sb.append(del);
1896 sb.append(use);
1897 del = ",";
1898 }
1899 info.put(USES_DIRECTIVE, sb.toString());
1900 }
1901 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001902 }
1903
1904 });
1905 }
1906
1907 /**
1908 * Clean up version parameters. Other builders use more fuzzy definitions of
1909 * the version syntax. This method cleans up such a version to match an OSGi
1910 * version.
1911 *
1912 * @param VERSION_STRING
1913 * @return
1914 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001915 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1916 Pattern.DOTALL);
1917 static Pattern fuzzyVersionRange = Pattern.compile(
1918 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1919 Pattern.DOTALL);
Stuart McCullochf3173222012-06-07 21:57:32 +00001920 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1921
1922 static Pattern nummeric = Pattern.compile("\\d*");
1923
1924 static public String cleanupVersion(String version) {
1925 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1926
1927 if (m.matches()) {
1928 return version;
1929 }
1930
1931 m = fuzzyVersionRange.matcher(version);
1932 if (m.matches()) {
1933 String prefix = m.group(1);
1934 String first = m.group(2);
1935 String last = m.group(3);
1936 String suffix = m.group(4);
1937 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1938 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001939
Stuart McCulloch4482c702012-06-15 13:27:53 +00001940 m = fuzzyVersion.matcher(version);
1941 if (m.matches()) {
1942 StringBuilder result = new StringBuilder();
1943 String major = removeLeadingZeroes(m.group(1));
1944 String minor = removeLeadingZeroes(m.group(3));
1945 String micro = removeLeadingZeroes(m.group(5));
1946 String qualifier = m.group(7);
1947
1948 if (major != null) {
1949 result.append(major);
1950 if (minor != null) {
1951 result.append(".");
1952 result.append(minor);
1953 if (micro != null) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001954 result.append(".");
Stuart McCulloch4482c702012-06-15 13:27:53 +00001955 result.append(micro);
Stuart McCullochf3173222012-06-07 21:57:32 +00001956 if (qualifier != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001957 result.append(".");
Stuart McCullochf3173222012-06-07 21:57:32 +00001958 cleanupModifier(result, qualifier);
1959 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001960 } else if (qualifier != null) {
1961 result.append(".0.");
1962 cleanupModifier(result, qualifier);
1963 }
1964 } else if (qualifier != null) {
1965 result.append(".0.0.");
1966 cleanupModifier(result, qualifier);
Stuart McCullochf3173222012-06-07 21:57:32 +00001967 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001968 return result.toString();
Stuart McCullochf3173222012-06-07 21:57:32 +00001969 }
1970 }
1971 return version;
1972 }
1973
1974 private static String removeLeadingZeroes(String group) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001975 if (group == null)
1976 return null;
1977
Stuart McCullochf3173222012-06-07 21:57:32 +00001978 int n = 0;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001979 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochf3173222012-06-07 21:57:32 +00001980 n++;
1981 if (n == 0)
1982 return group;
1983
1984 return group.substring(n);
1985 }
1986
1987 static void cleanupModifier(StringBuilder result, String modifier) {
1988 Matcher m = fuzzyModifier.matcher(modifier);
1989 if (m.matches())
1990 modifier = m.group(2);
1991
1992 for (int i = 0; i < modifier.length(); i++) {
1993 char c = modifier.charAt(i);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001994 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochf3173222012-06-07 21:57:32 +00001995 result.append(c);
1996 }
1997 }
1998
1999 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
2000 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
2001
Stuart McCullochf3173222012-06-07 21:57:32 +00002002 public String getVersionPolicy(boolean implemented) {
2003 if (implemented) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00002004 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002005 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002006
Stuart McCulloch669423b2012-06-26 16:34:24 +00002007 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochf3173222012-06-07 21:57:32 +00002008 }
2009
2010 /**
2011 * The extends macro traverses all classes and returns a list of class names
2012 * that extend a base class.
2013 */
2014
2015 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";
2016
2017 public String _classes(String... args) throws Exception {
2018 // Macro.verifyCommand(args, _classesHelp, new
2019 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
2020 // null}, 3,3);
2021
2022 Collection<Clazz> matched = getClasses(args);
2023 if (matched.isEmpty())
2024 return "";
2025
2026 return join(matched);
2027 }
2028
2029 public Collection<Clazz> getClasses(String... args) throws Exception {
2030
2031 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2032 for (int i = 1; i < args.length; i++) {
2033 if (args.length < i + 1)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002034 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2035 + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002036
2037 String typeName = args[i];
2038 if (typeName.equalsIgnoreCase("extending"))
2039 typeName = "extends";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002040 else if (typeName.equalsIgnoreCase("importing"))
2041 typeName = "imports";
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002042 else if (typeName.equalsIgnoreCase("annotation"))
2043 typeName = "annotated";
Stuart McCulloch4482c702012-06-15 13:27:53 +00002044 else if (typeName.equalsIgnoreCase("implementing"))
2045 typeName = "implements";
Stuart McCullochf3173222012-06-07 21:57:32 +00002046
2047 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2048
2049 if (type == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +00002050 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochf3173222012-06-07 21:57:32 +00002051
2052 Instruction instr = null;
2053 if (Clazz.HAS_ARGUMENT.contains(type)) {
2054 String s = args[++i];
2055 instr = new Instruction(s);
2056 }
2057 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2058 Clazz clazz = c.next();
2059 if (!clazz.is(type, instr, this)) {
2060 c.remove();
2061 }
2062 }
2063 }
2064 return matched;
2065 }
2066
2067 /**
2068 * Get the exporter of a package ...
2069 */
2070
2071 public String _exporters(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002072 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochf3173222012-06-07 21:57:32 +00002073 null, 2, 2);
2074 StringBuilder sb = new StringBuilder();
2075 String del = "";
2076 String pack = args[1].replace('.', '/');
2077 for (Jar jar : classpath) {
2078 if (jar.getDirectories().containsKey(pack)) {
2079 sb.append(del);
2080 sb.append(jar.getName());
2081 }
2082 }
2083 return sb.toString();
2084 }
2085
Stuart McCulloch4482c702012-06-15 13:27:53 +00002086 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochf3173222012-06-07 21:57:32 +00002087 return classspace;
2088 }
2089
2090 /**
2091 * Locate a resource on the class path.
2092 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002093 * @param path
2094 * Path of the reosurce
Stuart McCullochf3173222012-06-07 21:57:32 +00002095 * @return A resource or <code>null</code>
2096 */
2097 public Resource findResource(String path) {
2098 for (Jar entry : getClasspath()) {
2099 Resource r = entry.getResource(path);
2100 if (r != null)
2101 return r;
2102 }
2103 return null;
2104 }
2105
2106 /**
2107 * Find a clazz on the class path. This class has been parsed.
2108 *
2109 * @param path
2110 * @return
2111 */
2112 public Clazz findClass(TypeRef typeRef) throws Exception {
2113 Clazz c = classspace.get(typeRef);
2114 if (c != null)
2115 return c;
2116
2117 c = importedClassesCache.get(typeRef);
2118 if (c != null)
2119 return c;
2120
2121 Resource r = findResource(typeRef.getPath());
2122 if (r == null) {
2123 getClass().getClassLoader();
2124 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2125 if (url != null)
2126 r = new URLResource(url);
2127 }
2128 if (r != null) {
2129 c = new Clazz(this, typeRef.getPath(), r);
2130 c.parseClassFile();
2131 importedClassesCache.put(typeRef, c);
2132 }
2133 return c;
2134 }
2135
2136 /**
2137 * Answer the bundle version.
2138 *
2139 * @return
2140 */
2141 public String getVersion() {
2142 String version = getProperty(BUNDLE_VERSION);
2143 if (version == null)
2144 version = "0.0.0";
2145 return version;
2146 }
2147
2148 public boolean isNoBundle() {
2149 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2150 }
2151
2152 public void referTo(TypeRef ref) {
2153 PackageRef pack = ref.getPackageRef();
2154 if (!referred.containsKey(pack))
2155 referred.put(pack, new Attrs());
2156 }
2157
2158 public void referToByBinaryName(String binaryClassName) {
2159 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2160 referTo(ref);
2161 }
2162
2163 /**
2164 * Ensure that we are running on the correct bnd.
2165 */
2166 void doRequireBnd() {
2167 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2168 if (require == null || require.isEmpty())
2169 return;
2170
Stuart McCulloch4482c702012-06-15 13:27:53 +00002171 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +00002172 map.put(Constants.VERSION_FILTER, getBndVersion());
2173
2174 for (String filter : require.keySet()) {
2175 try {
2176 Filter f = new Filter(filter);
2177 if (f.match(map))
2178 continue;
2179 error("%s fails %s", REQUIRE_BND, require.get(filter));
2180 }
2181 catch (Exception t) {
2182 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2183 }
2184 }
2185 }
2186
2187 /**
2188 * md5 macro
2189 */
2190
2191 static String _md5Help = "${md5;path}";
2192
2193 public String _md5(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002194 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2195 null, null, Pattern.compile("base64|hex")
2196 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002197
2198 Digester<MD5> digester = MD5.getDigester();
2199 Resource r = dot.getResource(args[1]);
2200 if (r == null)
2201 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2202
2203 IO.copy(r.openInputStream(), digester);
2204 boolean hex = args.length > 2 && args[2].equals("hex");
2205 if (hex)
2206 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch4482c702012-06-15 13:27:53 +00002207
2208 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochf3173222012-06-07 21:57:32 +00002209 }
2210
2211 /**
2212 * SHA1 macro
2213 */
2214
2215 static String _sha1Help = "${sha1;path}";
2216
2217 public String _sha1(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002218 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2219 null, null, Pattern.compile("base64|hex")
2220 }, 2, 3);
Stuart McCullochf3173222012-06-07 21:57:32 +00002221 Digester<SHA1> digester = SHA1.getDigester();
2222 Resource r = dot.getResource(args[1]);
2223 if (r == null)
2224 throw new FileNotFoundException("From sha1, not found " + args[1]);
2225
2226 IO.copy(r.openInputStream(), digester);
2227 return Base64.encodeBase64(digester.digest().digest());
2228 }
2229
2230 public Descriptor getDescriptor(String descriptor) {
2231 return descriptors.getDescriptor(descriptor);
2232 }
2233
2234 public TypeRef getTypeRef(String binaryClassName) {
2235 return descriptors.getTypeRef(binaryClassName);
2236 }
2237
2238 public PackageRef getPackageRef(String binaryName) {
2239 return descriptors.getPackageRef(binaryName);
2240 }
2241
2242 public TypeRef getTypeRefFromFQN(String fqn) {
2243 return descriptors.getTypeRefFromFQN(fqn);
2244 }
2245
2246 public TypeRef getTypeRefFromPath(String path) {
2247 return descriptors.getTypeRefFromPath(path);
2248 }
2249
2250 public boolean isImported(PackageRef packageRef) {
2251 return imports.containsKey(packageRef);
2252 }
2253
2254 /**
2255 * Merge the attributes of two maps, where the first map can contain
2256 * wildcarded names. The idea is that the first map contains instructions
2257 * (for example *) with a set of attributes. These patterns are matched
2258 * against the found packages in actual. If they match, the result is set
2259 * with the merged set of attributes. It is expected that the instructions
2260 * are ordered so that the instructor can define which pattern matches
2261 * first. Attributes in the instructions override any attributes from the
2262 * actual.<br/>
Stuart McCullochf3173222012-06-07 21:57:32 +00002263 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2264 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2265 * if the pattern starts with an exclamation mark, it will remove that
2266 * matches for that pattern (- the !) from the working set. So the following
2267 * patterns should work:
2268 * <ul>
2269 * <li>com.foo.bar</li>
2270 * <li>com.foo.*</li>
2271 * <li>com.foo.???</li>
2272 * <li>com.*.[^b][^a][^r]</li>
2273 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2274 * </ul>
2275 * Enough rope to hang the average developer I would say.
2276 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002277 * @param instructions
2278 * the instructions with patterns.
2279 * @param source
2280 * the actual found packages, contains no duplicates
Stuart McCullochf3173222012-06-07 21:57:32 +00002281 * @return Only the packages that were filtered by the given instructions
2282 */
2283
2284 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2285 Packages result = new Packages();
2286 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2287 Collections.sort(refs);
2288
2289 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2290 if (nomatch == null)
2291 nomatch = Create.set();
2292
2293 for (Instruction instruction : filters) {
2294 boolean match = false;
2295
2296 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2297 PackageRef packageRef = i.next();
2298
2299 if (packageRef.isMetaData()) {
2300 i.remove(); // no use checking it again
2301 continue;
2302 }
2303
2304 String packageName = packageRef.getFQN();
2305
2306 if (instruction.matches(packageName)) {
2307 match = true;
2308 if (!instruction.isNegated()) {
2309 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2310 instructions.get(instruction));
2311 }
2312 i.remove(); // Can never match again for another pattern
2313 }
2314 }
2315 if (!match && !instruction.isAny())
2316 nomatch.add(instruction);
2317 }
2318
2319 /*
2320 * Tricky. If we have umatched instructions they might indicate that we
2321 * want to have multiple decorators for the same package. So we check
2322 * the unmatched against the result list. If then then match and have
2323 * actually interesting properties then we merge them
2324 */
2325
2326 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2327 Instruction instruction = i.next();
2328
2329 // We assume the user knows what he is
2330 // doing and inserted a literal. So
2331 // we ignore any not matched literals
2332 if (instruction.isLiteral()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00002333 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochf3173222012-06-07 21:57:32 +00002334 i.remove();
2335 continue;
2336 }
2337
2338 // Not matching a negated instruction looks
2339 // like an error ...
2340 if (instruction.isNegated()) {
2341 continue;
2342 }
2343
2344 // An optional instruction should not generate
2345 // an error
2346 if (instruction.isOptional()) {
2347 i.remove();
2348 continue;
2349 }
2350
2351 // boolean matched = false;
2352 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2353 // for (PackageRef ref : prefs) {
2354 // if (instruction.matches(ref.getFQN())) {
2355 // result.merge(ref, true, source.get(ref),
2356 // instructions.get(instruction));
2357 // matched = true;
2358 // }
2359 // }
2360 // if (matched)
2361 // i.remove();
2362 }
2363 return result;
2364 }
2365
2366 public void setDiagnostics(boolean b) {
2367 diagnostics = b;
2368 }
2369
2370 public Clazz.JAVA getLowestEE() {
2371 if (ees.isEmpty())
2372 return Clazz.JAVA.JDK1_4;
2373
2374 return ees.first();
2375 }
2376
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002377 public String _ee(@SuppressWarnings("unused")
2378 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00002379 return getLowestEE().getEE();
2380 }
2381
2382 /**
2383 * Calculate the output file for the given target. The strategy is:
2384 *
2385 * <pre>
2386 * parameter given if not null and not directory
2387 * if directory, this will be the output directory
2388 * based on bsn-version.jar
2389 * name of the source file if exists
2390 * Untitled-[n]
2391 * </pre>
2392 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002393 * @param output
2394 * may be null, otherwise a file path relative to base
Stuart McCullochf3173222012-06-07 21:57:32 +00002395 */
2396 public File getOutputFile(String output) {
2397
2398 if (output == null)
2399 output = get(Constants.OUTPUT);
2400
2401 File outputDir;
2402
2403 if (output != null) {
2404 File outputFile = getFile(output);
2405 if (outputFile.isDirectory())
2406 outputDir = outputFile;
2407 else
2408 return outputFile;
Stuart McCulloch4482c702012-06-15 13:27:53 +00002409 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002410 outputDir = getBase();
2411
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002412 Entry<String,Attrs> name = getBundleSymbolicName();
2413 if (name != null) {
2414 String bsn = name.getKey();
Stuart McCullochf3173222012-06-07 21:57:32 +00002415 String version = getBundleVersion();
2416 Version v = Version.parseVersion(version);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002417 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochf3173222012-06-07 21:57:32 +00002418 return new File(outputDir, outputName);
2419 }
2420
2421 File source = getJar().getSource();
2422 if (source != null) {
2423 String outputName = source.getName();
2424 return new File(outputDir, outputName);
2425 }
2426
Stuart McCulloch4482c702012-06-15 13:27:53 +00002427 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochf3173222012-06-07 21:57:32 +00002428 int n = 0;
2429 File f = getFile(outputDir, "Untitled");
2430 while (f.isFile()) {
2431 f = getFile(outputDir, "Untitled-" + n++);
2432 }
2433 return f;
2434 }
2435
2436 /**
2437 * Utility function to carefully save the file. Will create a backup if the
2438 * source file has the same path as the output. It will also only save if
2439 * the file was modified or the force flag is true
2440 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00002441 * @param output
2442 * the output file, if null {@link #getOutputFile(String)} is
2443 * used.
2444 * @param force
2445 * if it needs to be overwritten
Stuart McCullochf3173222012-06-07 21:57:32 +00002446 * @throws Exception
2447 */
2448
2449 public boolean save(File output, boolean force) throws Exception {
2450 if (output == null)
2451 output = getOutputFile(null);
2452
2453 Jar jar = getJar();
2454 File source = jar.getSource();
2455
Stuart McCulloch4482c702012-06-15 13:27:53 +00002456 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2457 jar.lastModified() - output.lastModified());
Stuart McCullochf3173222012-06-07 21:57:32 +00002458
2459 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002460 File op = output.getParentFile();
2461 if (!op.exists() && !op.mkdirs()) {
2462 throw new IOException("Could not create directory " + op);
2463 }
Stuart McCullochf3173222012-06-07 21:57:32 +00002464 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2465 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2466 if (!source.renameTo(bak)) {
2467 error("Could not create backup file %s", bak);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002468 } else
Stuart McCullochf3173222012-06-07 21:57:32 +00002469 source.delete();
2470 }
2471 try {
2472 trace("Saving jar to %s", output);
2473 getJar().write(output);
2474 }
2475 catch (Exception e) {
2476 output.delete();
2477 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2478 }
2479 return true;
2480 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002481 trace("Not modified %s", output);
2482 return false;
2483
Stuart McCullochf3173222012-06-07 21:57:32 +00002484 }
2485
2486 /**
2487 * Set default import and export instructions if none are set
2488 */
2489 public void setDefaults(String bsn, Version version) {
2490 if (getExportPackage() == null)
2491 setExportPackage("*");
2492 if (getImportPackage() == null)
2493 setExportPackage("*");
2494 if (bsn != null && getBundleSymbolicName() == null)
2495 setBundleSymbolicName(bsn);
2496 if (version != null && getBundleVersion() == null)
2497 setBundleVersion(version);
2498 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002499
2500 /**
Stuart McCullochb32291a2012-07-16 14:10:57 +00002501 * Remove the own references and optional java references from the uses lib
2502 *
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002503 * @param apiUses
2504 * @param removeJava
2505 * @return
2506 */
Stuart McCullochb32291a2012-07-16 14:10:57 +00002507 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002508 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002509 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002510 e.getValue().remove(e.getKey());
2511 if (!removeJava)
2512 continue;
Stuart McCullochb32291a2012-07-16 14:10:57 +00002513
2514 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2515 if (i.next().isJava())
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002516 i.remove();
2517 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002518 }
Stuart McCulloch42151ee2012-07-16 13:43:38 +00002519 return map;
2520 }
Stuart McCullochb32291a2012-07-16 14:10:57 +00002521
2522 /**
2523 * Return the classes for a given source package.
2524 *
2525 * @param source
2526 * the source package
2527 * @return a set of classes for the requested package.
2528 */
2529 public Set<Clazz> getClassspace(PackageRef source) {
2530 Set<Clazz> result = new HashSet<Clazz>();
2531 for (Clazz c : getClassspace().values()) {
2532 if (c.getClassName().getPackageRef() == source)
2533 result.add(c);
2534 }
2535 return result;
2536 }
2537
2538 /**
2539 * Create a cross reference from package source, to packages in dest
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002540 *
Stuart McCullochb32291a2012-07-16 14:10:57 +00002541 * @param source
2542 * @param dest
2543 * @param sourceModifiers
2544 * @return
2545 * @throws Exception
2546 */
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002547 public Map<Clazz.Def,List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest,
2548 final int sourceModifiers) throws Exception {
2549 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def,TypeRef>(Clazz.Def.class, TypeRef.class, true);
Stuart McCullochb32291a2012-07-16 14:10:57 +00002550
2551 for (final Clazz clazz : getClassspace().values()) {
2552 if ((clazz.accessx & sourceModifiers) == 0)
2553 continue;
2554
Stuart McCulloch99fd9a72012-07-24 21:37:47 +00002555 if (source != null && source != clazz.getClassName().getPackageRef())
Stuart McCullochb32291a2012-07-16 14:10:57 +00002556 continue;
2557
2558 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2559 Clazz.Def member;
2560
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002561 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002562 public void extendsClass(TypeRef zuper) throws Exception {
2563 if (dest.contains(zuper.getPackageRef()))
2564 xref.add(clazz.getExtends(zuper), zuper);
2565 }
2566
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002567 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002568 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2569 for (TypeRef i : interfaces) {
2570 if (dest.contains(i.getPackageRef()))
2571 xref.add(clazz.getImplements(i), i);
2572 }
2573 }
2574
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002575 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002576 public void referTo(TypeRef to, int modifiers) {
2577 if (to.isJava())
2578 return;
2579
2580 if (!dest.contains(to.getPackageRef()))
2581 return;
2582
2583 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2584 xref.add(member, to);
2585 }
2586
2587 }
2588
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002589 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002590 public void method(Clazz.MethodDef defined) {
2591 member = defined;
2592 }
2593
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00002594 @Override
Stuart McCullochb32291a2012-07-16 14:10:57 +00002595 public void field(Clazz.FieldDef defined) {
2596 member = defined;
2597 }
2598
2599 });
2600
2601 }
2602 return xref;
2603 }
2604
Stuart McCullochf3173222012-06-07 21:57:32 +00002605}