blob: c490e660e11612f6570ac071b8cf11dd12d76d68 [file] [log] [blame]
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochbb014372012-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 McCulloch39cc9ac2012-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 McCullochbb014372012-06-07 21:57:32 +000037import aQute.bnd.service.*;
Stuart McCulloch6a046662012-07-19 13:11:20 +000038import aQute.bnd.version.Version;
Stuart McCullochbb014372012-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 McCullochbb014372012-06-07 21:57:32 +000044import aQute.libg.cryptography.*;
45import aQute.libg.generics.*;
Stuart McCulloch2286f232012-06-15 13:27:53 +000046import aQute.libg.reporter.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000047
48public class Analyzer extends Processor {
49 private final SortedSet<Clazz.JAVA> ees = new TreeSet<Clazz.JAVA>();
Stuart McCullochd4826102012-06-26 16:34:24 +000050 static Properties bndInfo;
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +000061 private final MultiMap<PackageRef,PackageRef> uses = new MultiMap<PackageRef,PackageRef>(
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +000062 PackageRef.class, PackageRef.class,
63 true);
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000064 private final MultiMap<PackageRef,PackageRef> apiUses = new MultiMap<PackageRef,PackageRef>(
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +000065 PackageRef.class, PackageRef.class,
66 true);
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +000070 private final Map<TypeRef,Clazz> classspace = map();
71 private final Map<TypeRef,Clazz> importedClassesCache = map();
Stuart McCullochbb014372012-06-07 21:57:32 +000072 private boolean analyzed = false;
73 private boolean diagnostics = false;
74 private boolean inited = false;
Stuart McCulloch2286f232012-06-15 13:27:53 +000075 final protected AnalyzerMessages msgs = ReporterMessages.base(this,
76 AnalyzerMessages.class);
Stuart McCullochbb014372012-06-07 21:57:32 +000077
78 public Analyzer(Processor parent) {
79 super(parent);
80 }
81
Stuart McCulloch2286f232012-06-15 13:27:53 +000082 public Analyzer() {}
Stuart McCullochbb014372012-06-07 21:57:32 +000083
84 /**
85 * Specifically for Maven
86 *
Stuart McCulloch2286f232012-06-15 13:27:53 +000087 * @param properties
88 * the properties
Stuart McCullochbb014372012-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 McCulloch39cc9ac2012-07-16 13:43:38 +0000121 apiUses.clear();
Stuart McCullochbb014372012-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 McCullochd4826102012-06-26 16:34:24 +0000146 getExportVersionsFromPackageInfo(packageRef, resource, classpathExports);
Stuart McCullochbb014372012-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 McCullochd4826102012-06-26 16:34:24 +0000156 trace("activator %s %s", s, activator);
Stuart McCullochbb014372012-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 McCulloch3b7e6af2012-07-16 14:10:57 +0000236
237 boolean api = isTrue(getProperty(EXPERIMENTS)) || true; // brave,
238 // lets see
239
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000240 doUses(exports, api ? apiUses : uses, imports);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000241
Stuart McCulloch39cc9ac2012-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 McCulloch3b7e6af2012-07-16 14:10:57 +0000250 Set<PackageRef> privatePackages = getPrivates();
Stuart McCulloch39cc9ac2012-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 McCullochbb014372012-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 McCulloch4b9de8e2012-07-22 00:19:13 +0000309 Processor previous = beginHandleErrors(plugin.toString());
Stuart McCullochbb014372012-06-07 21:57:32 +0000310 boolean reanalyze = plugin.analyzeJar(this);
Stuart McCulloch4b9de8e2012-07-22 00:19:13 +0000311 endHandleErrors(previous);
Stuart McCullochbb014372012-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 McCullochbb014372012-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 McCulloch61c61eb2012-07-24 21:37:47 +0000339 try {
340 analyze();
341 Manifest manifest = new Manifest();
342 Attributes main = manifest.getMainAttributes();
Stuart McCullochbb014372012-06-07 21:57:32 +0000343
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000344 main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
345 main.putValue(BUNDLE_MANIFESTVERSION, "2");
Stuart McCullochbb014372012-06-07 21:57:32 +0000346
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000347 boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
Stuart McCullochbb014372012-06-07 21:57:32 +0000348
Stuart McCulloch61c61eb2012-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 McCullochbb014372012-06-07 21:57:32 +0000354 }
355
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000356 String exportHeader = printClauses(exports, true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000357
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000358 if (exportHeader.length() > 0)
359 main.putValue(EXPORT_PACKAGE, exportHeader);
360 else
361 main.remove(EXPORT_PACKAGE);
Stuart McCullochbb014372012-06-07 21:57:32 +0000362
Stuart McCulloch61c61eb2012-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 McCulloch2286f232012-06-15 13:27:53 +0000366 } else {
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000367 main.remove(IMPORT_PACKAGE);
Stuart McCullochbb014372012-06-07 21:57:32 +0000368 }
Stuart McCulloch61c61eb2012-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 McCullochbb014372012-06-07 21:57:32 +0000461 }
Stuart McCulloch61c61eb2012-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 McCullochbb014372012-06-07 21:57:32 +0000467 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000468 }
469
470 /**
471 * Parse the namesection as instructions and then match them against the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000472 * current set of resources For example:
Stuart McCullochbb014372012-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 McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +0000496 for (Map.Entry<Instruction,Attrs> instr : instructions.entrySet()) {
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +0000525 for (Map.Entry<String,String> property : instr.getValue().entrySet()) {
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +0000541 warning("The instruction %s in %s did not match any resources", instr.getKey(), NAMESECTION);
Stuart McCullochbb014372012-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 McCulloch2286f232012-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 McCullochbb014372012-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 McCulloch2286f232012-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 McCullochbb014372012-06-07 21:57:32 +0000600 }
Stuart McCullochbb014372012-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 McCulloch39cc9ac2012-07-16 13:43:38 +0000612 public String _bsn(@SuppressWarnings("unused")
613 String args[]) {
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +0000620 * @param bundle
621 * The jar file to analyze
Stuart McCullochbb014372012-06-07 21:57:32 +0000622 * @return
623 */
624 public String calculateExportsFromContents(Jar bundle) {
625 String ddel = "";
626 StringBuilder sb = new StringBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000627 Map<String,Map<String,Resource>> map = bundle.getDirectories();
Stuart McCullochbb014372012-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 McCulloch3b7e6af2012-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 McCullochbb014372012-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 McCulloch3b7e6af2012-07-16 14:10:57 +0000693 public Map<PackageRef,List<PackageRef>> getUses() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000694 return uses;
695 }
696
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +0000697 public Map<PackageRef,List<PackageRef>> getAPIUses() {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000698 return apiUses;
699 }
700
Stuart McCullochbb014372012-06-07 21:57:32 +0000701 /**
702 * Get the version for this bnd
703 *
704 * @return version or unknown.
705 */
706 public String getBndVersion() {
Stuart McCullochd4826102012-06-26 16:34:24 +0000707 return getBndInfo("version", "<unknown>");
Stuart McCullochbb014372012-06-07 21:57:32 +0000708 }
709
710 public long getBndLastModified() {
Stuart McCullochd4826102012-06-26 16:34:24 +0000711 String time = getBndInfo("lastmodified", "0");
Stuart McCullochbb014372012-06-07 21:57:32 +0000712 try {
713 return Long.parseLong(time);
714 }
715 catch (Exception e) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000716 // Ignore
Stuart McCullochbb014372012-06-07 21:57:32 +0000717 }
718 return 0;
719 }
720
721 public String getBndInfo(String key, String defaultValue) {
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000722 if (bndInfo == null) {
723 try {
Stuart McCullochd4826102012-06-26 16:34:24 +0000724 Properties bndInfoLocal = new Properties();
725 URL url = Analyzer.class.getResource("bnd.info");
726 if (url != null) {
727 InputStream in = url.openStream();
728 try {
729 bndInfoLocal.load(in);
730 }
731 finally {
732 in.close();
733 }
734 }
735 bndInfo = bndInfoLocal;
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000736 }
737 catch (Exception e) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000738 e.printStackTrace();
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000739 return defaultValue;
Stuart McCullochbb014372012-06-07 21:57:32 +0000740 }
741 }
Stuart McCullochd4826102012-06-26 16:34:24 +0000742 String value = bndInfo.getProperty(key);
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000743 if (value == null)
744 return defaultValue;
745 return value;
Stuart McCullochbb014372012-06-07 21:57:32 +0000746 }
747
748 /**
749 * Merge the existing manifest with the instructions but do not override
750 * existing properties.
751 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000752 * @param manifest
753 * The manifest to merge with
Stuart McCullochbb014372012-06-07 21:57:32 +0000754 * @throws IOException
755 */
756 public void mergeManifest(Manifest manifest) throws IOException {
757 if (manifest != null) {
758 Attributes attributes = manifest.getMainAttributes();
759 for (Iterator<Object> i = attributes.keySet().iterator(); i.hasNext();) {
760 Name name = (Name) i.next();
761 String key = name.toString();
762 // Dont want instructions
763 if (key.startsWith("-"))
764 continue;
765
766 if (getProperty(key) == null)
767 setProperty(key, attributes.getValue(name));
768 }
769 }
770 }
771
772 public void setBase(File file) {
773 super.setBase(file);
774 getProperties().put("project.dir", getBase().getAbsolutePath());
775 }
776
777 /**
778 * Set the classpath for this analyzer by file.
779 *
780 * @param classpath
781 * @throws IOException
782 */
783 public void setClasspath(File[] classpath) throws IOException {
784 List<Jar> list = new ArrayList<Jar>();
785 for (int i = 0; i < classpath.length; i++) {
786 if (classpath[i].exists()) {
787 Jar current = new Jar(classpath[i]);
788 list.add(current);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000789 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000790 error("Missing file on classpath: %s", classpath[i]);
791 }
792 }
793 for (Iterator<Jar> i = list.iterator(); i.hasNext();) {
794 addClasspath(i.next());
795 }
796 }
797
798 public void setClasspath(Jar[] classpath) {
799 for (int i = 0; i < classpath.length; i++) {
800 addClasspath(classpath[i]);
801 }
802 }
803
804 public void setClasspath(String[] classpath) {
805 for (int i = 0; i < classpath.length; i++) {
806 Jar jar = getJarFromName(classpath[i], " setting classpath");
807 if (jar != null)
808 addClasspath(jar);
809 }
810 }
811
812 /**
813 * Set the JAR file we are going to work in. This will read the JAR in
814 * memory.
815 *
816 * @param jar
817 * @return
818 * @throws IOException
819 */
820 public Jar setJar(File jar) throws IOException {
821 Jar jarx = new Jar(jar);
822 addClose(jarx);
823 return setJar(jarx);
824 }
825
826 /**
827 * Set the JAR directly we are going to work on.
828 *
829 * @param jar
830 * @return
831 */
832 public Jar setJar(Jar jar) {
833 if (dot != null)
834 removeClose(dot);
835
836 this.dot = jar;
837 if (dot != null)
838 addClose(dot);
839
840 return jar;
841 }
842
843 protected void begin() {
844 if (inited == false) {
845 inited = true;
846 super.begin();
847
848 updateModified(getBndLastModified(), "bnd last modified");
849 verifyManifestHeadersCase(getProperties());
850
851 }
852 }
853
854 /**
855 * Try to get a Jar from a file name/path or a url, or in last resort from
856 * the classpath name part of their files.
857 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000858 * @param name
859 * URL or filename relative to the base
860 * @param from
861 * Message identifying the caller for errors
Stuart McCullochbb014372012-06-07 21:57:32 +0000862 * @return null or a Jar with the contents for the name
863 */
864 Jar getJarFromName(String name, String from) {
865 File file = new File(name);
866 if (!file.isAbsolute())
867 file = new File(getBase(), name);
868
869 if (file.exists())
870 try {
871 Jar jar = new Jar(file);
872 addClose(jar);
873 return jar;
874 }
875 catch (Exception e) {
876 error("Exception in parsing jar file for " + from + ": " + name + " " + e);
877 }
878 // It is not a file ...
879 try {
880 // Lets try a URL
881 URL url = new URL(name);
882 Jar jar = new Jar(fileName(url.getPath()));
883 addClose(jar);
884 URLConnection connection = url.openConnection();
885 InputStream in = connection.getInputStream();
886 long lastModified = connection.getLastModified();
887 if (lastModified == 0)
888 // We assume the worst :-(
889 lastModified = System.currentTimeMillis();
890 EmbeddedResource.build(jar, in, lastModified);
891 in.close();
892 return jar;
893 }
894 catch (IOException ee) {
895 // Check if we have files on the classpath
896 // that have the right name, allows us to specify those
897 // names instead of the full path.
898 for (Iterator<Jar> cp = getClasspath().iterator(); cp.hasNext();) {
899 Jar entry = cp.next();
900 if (entry.getSource() != null && entry.getSource().getName().equals(name)) {
901 return entry;
902 }
903 }
904 // error("Can not find jar file for " + from + ": " + name);
905 }
906 return null;
907 }
908
909 private String fileName(String path) {
910 int n = path.lastIndexOf('/');
911 if (n > 0)
912 return path.substring(n + 1);
913 return path;
914 }
915
916 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000917 * @param manifests
918 * @throws Exception
919 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000920 private void merge(Manifest result, Manifest old) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000921 if (old != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000922 for (Iterator<Map.Entry<Object,Object>> e = old.getMainAttributes().entrySet().iterator(); e.hasNext();) {
923 Map.Entry<Object,Object> entry = e.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000924 Attributes.Name name = (Attributes.Name) entry.getKey();
925 String value = (String) entry.getValue();
926 if (name.toString().equalsIgnoreCase("Created-By"))
927 name = new Attributes.Name("Originally-Created-By");
928 if (!result.getMainAttributes().containsKey(name))
929 result.getMainAttributes().put(name, value);
930 }
931
932 // do not overwrite existing entries
Stuart McCulloch2286f232012-06-15 13:27:53 +0000933 Map<String,Attributes> oldEntries = old.getEntries();
934 Map<String,Attributes> newEntries = result.getEntries();
935 for (Iterator<Map.Entry<String,Attributes>> e = oldEntries.entrySet().iterator(); e.hasNext();) {
936 Map.Entry<String,Attributes> entry = e.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000937 if (!newEntries.containsKey(entry.getKey())) {
938 newEntries.put(entry.getKey(), entry.getValue());
939 }
940 }
941 }
942 }
943
944 /**
945 * Bnd is case sensitive for the instructions so we better check people are
946 * not using an invalid case. We do allow this to set headers that should
947 * not be processed by us but should be used by the framework.
948 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000949 * @param properties
950 * Properties to verify.
Stuart McCullochbb014372012-06-07 21:57:32 +0000951 */
952
953 void verifyManifestHeadersCase(Properties properties) {
954 for (Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
955 String header = (String) i.next();
956 for (int j = 0; j < headers.length; j++) {
957 if (!headers[j].equals(header) && headers[j].equalsIgnoreCase(header)) {
958 warning("Using a standard OSGi header with the wrong case (bnd is case sensitive!), using: "
959 + header + " and expecting: " + headers[j]);
960 break;
961 }
962 }
963 }
964 }
965
966 /**
967 * We will add all exports to the imports unless there is a -noimport
968 * directive specified on an export. This directive is skipped for the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000969 * manifest. We also remove any version parameter so that augmentImports can
970 * do the version policy. The following method is really tricky and evolved
971 * over time. Coming from the original background of OSGi, it was a weird
972 * idea for me to have a public package that should not be substitutable. I
973 * was so much convinced that this was the right rule that I rücksichtlos
974 * imported them all. Alas, the real world was more subtle than that. It
975 * turns out that it is not a good idea to always import. First, there must
976 * be a need to import, i.e. there must be a contained package that refers
977 * to the exported package for it to make use importing that package.
978 * Second, if an exported package refers to an internal package than it
979 * should not be imported. Additionally, it is necessary to treat the
980 * exports in groups. If an exported package refers to another exported
981 * packages than it must be in the same group. A framework can only
982 * substitute exports for imports for the whole of such a group. WHY?????
983 * Not clear anymore ...
Stuart McCullochbb014372012-06-07 21:57:32 +0000984 */
985 Packages doExportsToImports(Packages exports) {
986
987 // private packages = contained - exported.
988 Set<PackageRef> privatePackages = new HashSet<PackageRef>(contained.keySet());
989 privatePackages.removeAll(exports.keySet());
990
991 // private references = ∀ p : private packages | uses(p)
992 Set<PackageRef> privateReferences = newSet();
993 for (PackageRef p : privatePackages) {
994 Collection<PackageRef> uses = this.uses.get(p);
995 if (uses != null)
996 privateReferences.addAll(uses);
997 }
998
999 // Assume we are going to export all exported packages
1000 Set<PackageRef> toBeImported = new HashSet<PackageRef>(exports.keySet());
1001
1002 // Remove packages that are not referenced privately
1003 toBeImported.retainAll(privateReferences);
1004
1005 // Not necessary to import anything that is already
1006 // imported in the Import-Package statement.
1007 // TODO toBeImported.removeAll(imports.keySet());
1008
1009 // Remove exported packages that are referring to
1010 // private packages.
1011 // Each exported package has a uses clause. We just use
1012 // the used packages for each exported package to find out
1013 // if it refers to an internal package.
1014 //
1015
1016 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1017 PackageRef next = i.next();
1018 Collection<PackageRef> usedByExportedPackage = this.uses.get(next);
1019
Stuart McCulloch61c61eb2012-07-24 21:37:47 +00001020 // We had an NPE on usedByExportedPackage in GF.
1021 // I guess this can happen with hard coded
1022 // imports that do not match reality ...
1023 if (usedByExportedPackage == null || usedByExportedPackage.isEmpty()) {
1024 continue;
1025 }
1026
Stuart McCullochbb014372012-06-07 21:57:32 +00001027 for (PackageRef privatePackage : privatePackages) {
1028 if (usedByExportedPackage.contains(privatePackage)) {
1029 i.remove();
1030 break;
1031 }
1032 }
1033 }
1034
1035 // Clean up attributes and generate result map
1036 Packages result = new Packages();
1037 for (Iterator<PackageRef> i = toBeImported.iterator(); i.hasNext();) {
1038 PackageRef ep = i.next();
1039 Attrs parameters = exports.get(ep);
1040
Stuart McCullochffa8aaf2012-06-17 20:38:35 +00001041 String noimport = parameters == null ? null : parameters.get(NO_IMPORT_DIRECTIVE);
Stuart McCullochbb014372012-06-07 21:57:32 +00001042 if (noimport != null && noimport.equalsIgnoreCase("true"))
1043 continue;
1044
1045 // // we can't substitute when there is no version
1046 // String version = parameters.get(VERSION_ATTRIBUTE);
1047 // if (version == null) {
1048 // if (isPedantic())
1049 // warning(
1050 // "Cannot automatically import exported package %s because it has no version defined",
1051 // ep);
1052 // continue;
1053 // }
1054
1055 parameters = new Attrs();
1056 parameters.remove(VERSION_ATTRIBUTE);
1057 result.put(ep, parameters);
1058 }
1059 return result;
1060 }
1061
1062 public boolean referred(PackageRef packageName) {
1063 // return true;
Stuart McCulloch2286f232012-06-15 13:27:53 +00001064 for (Map.Entry<PackageRef,List<PackageRef>> contained : uses.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001065 if (!contained.getKey().equals(packageName)) {
1066 if (contained.getValue().contains(packageName))
1067 return true;
1068 }
1069 }
1070 return false;
1071 }
1072
1073 /**
Stuart McCullochbb014372012-06-07 21:57:32 +00001074 * @param jar
1075 */
1076 private void getExternalExports(Jar jar, Packages classpathExports) {
1077 try {
1078 Manifest m = jar.getManifest();
1079 if (m != null) {
1080 Domain domain = Domain.domain(m);
1081 Parameters exported = domain.getExportPackage();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001082 for (Entry<String,Attrs> e : exported.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001083 PackageRef ref = getPackageRef(e.getKey());
1084 if (!classpathExports.containsKey(ref)) {
1085 // TODO e.getValue().put(SOURCE_DIRECTIVE,
1086 // jar.getBsn()+"-"+jar.getVersion());
1087
1088 classpathExports.put(ref, e.getValue());
1089 }
1090 }
1091 }
1092 }
1093 catch (Exception e) {
1094 warning("Erroneous Manifest for " + jar + " " + e);
1095 }
1096 }
1097
1098 /**
1099 * Find some more information about imports in manifest and other places. It
1100 * is assumed that the augmentsExports has already copied external attrs
1101 * from the classpathExports.
1102 *
1103 * @throws Exception
1104 */
1105 void augmentImports(Packages imports, Packages exports) throws Exception {
1106 List<PackageRef> noimports = Create.list();
1107 Set<PackageRef> provided = findProvidedPackages();
1108
1109 for (PackageRef packageRef : imports.keySet()) {
1110 String packageName = packageRef.getFQN();
1111
1112 setProperty(CURRENT_PACKAGE, packageName);
1113 try {
1114 Attrs importAttributes = imports.get(packageRef);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001115 Attrs exportAttributes = exports.get(packageRef, classpathExports.get(packageRef, new Attrs()));
Stuart McCullochbb014372012-06-07 21:57:32 +00001116
1117 String exportVersion = exportAttributes.getVersion();
1118 String importRange = importAttributes.getVersion();
1119
1120 if (exportVersion == null) {
1121 // TODO Should check if the source is from a bundle.
1122
Stuart McCulloch2286f232012-06-15 13:27:53 +00001123 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001124
1125 //
1126 // Version Policy - Import version substitution. We
1127 // calculate the export version and then allow the
1128 // import version attribute to use it in a substitution
1129 // by using a ${@} macro. The export version can
1130 // be defined externally or locally
1131 //
1132
1133 boolean provider = isTrue(importAttributes.get(PROVIDE_DIRECTIVE))
Stuart McCulloch2286f232012-06-15 13:27:53 +00001134 || isTrue(exportAttributes.get(PROVIDE_DIRECTIVE)) || provided.contains(packageRef);
Stuart McCullochbb014372012-06-07 21:57:32 +00001135
1136 exportVersion = cleanupVersion(exportVersion);
1137
1138 try {
1139 setProperty("@", exportVersion);
1140
1141 if (importRange != null) {
1142 importRange = cleanupVersion(importRange);
1143 importRange = getReplacer().process(importRange);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001144 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00001145 importRange = getVersionPolicy(provider);
1146
1147 }
1148 finally {
1149 unsetProperty("@");
1150 }
1151 importAttributes.put(VERSION_ATTRIBUTE, importRange);
1152 }
1153
1154 //
1155 // Check if exporter has mandatory attributes
1156 //
1157 String mandatory = exportAttributes.get(MANDATORY_DIRECTIVE);
1158 if (mandatory != null) {
1159 String[] attrs = mandatory.split("\\s*,\\s*");
1160 for (int i = 0; i < attrs.length; i++) {
1161 if (!importAttributes.containsKey(attrs[i]))
1162 importAttributes.put(attrs[i], exportAttributes.get(attrs[i]));
1163 }
1164 }
1165
1166 if (exportAttributes.containsKey(IMPORT_DIRECTIVE))
1167 importAttributes.put(IMPORT_DIRECTIVE, exportAttributes.get(IMPORT_DIRECTIVE));
1168
1169 fixupAttributes(importAttributes);
1170 removeAttributes(importAttributes);
1171
1172 String result = importAttributes.get(Constants.VERSION_ATTRIBUTE);
1173 if (result == null)
1174 noimports.add(packageRef);
1175 }
1176 finally {
1177 unsetProperty(CURRENT_PACKAGE);
1178 }
1179 }
1180
1181 if (isPedantic() && noimports.size() != 0) {
1182 warning("Imports that lack version ranges: %s", noimports);
1183 }
1184 }
1185
1186 /**
1187 * Find the packages we depend on, where we implement an interface that is a
1188 * Provider Type. These packages, when we import them, must use the provider
1189 * policy.
1190 *
1191 * @throws Exception
1192 */
1193 Set<PackageRef> findProvidedPackages() throws Exception {
1194 Set<PackageRef> providers = Create.set();
1195 Set<TypeRef> cached = Create.set();
1196
1197 for (Clazz c : classspace.values()) {
1198 TypeRef[] interfaces = c.getInterfaces();
1199 if (interfaces != null)
1200 for (TypeRef t : interfaces)
1201 if (cached.contains(t) || isProvider(t)) {
1202 cached.add(t);
1203 providers.add(t.getPackageRef());
1204 }
1205 }
1206 return providers;
1207 }
1208
1209 private boolean isProvider(TypeRef t) throws Exception {
1210 Clazz c = findClass(t);
1211 if (c == null)
1212 return false;
1213
1214 if (c.annotations == null)
1215 return false;
1216
1217 TypeRef pt = getTypeRefFromFQN(ProviderType.class.getName());
1218 boolean result = c.annotations.contains(pt);
1219 return result;
1220 }
1221
1222 /**
1223 * Provide any macro substitutions and versions for exported packages.
1224 */
1225
1226 void augmentExports(Packages exports) {
1227 for (PackageRef packageRef : exports.keySet()) {
1228 String packageName = packageRef.getFQN();
1229 setProperty(CURRENT_PACKAGE, packageName);
1230 try {
1231 Attrs attributes = exports.get(packageRef);
1232 Attrs exporterAttributes = classpathExports.get(packageRef);
1233 if (exporterAttributes == null)
1234 continue;
1235
Stuart McCulloch2286f232012-06-15 13:27:53 +00001236 for (Map.Entry<String,String> entry : exporterAttributes.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001237 String key = entry.getKey();
1238 if (key.equalsIgnoreCase(SPECIFICATION_VERSION))
1239 key = VERSION_ATTRIBUTE;
1240
1241 // dont overwrite and no directives
1242 if (!key.endsWith(":") && !attributes.containsKey(key)) {
1243 attributes.put(key, entry.getValue());
1244 }
1245 }
1246
1247 fixupAttributes(attributes);
1248 removeAttributes(attributes);
1249
1250 }
1251 finally {
1252 unsetProperty(CURRENT_PACKAGE);
1253 }
1254 }
1255 }
1256
1257 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001258 * Fixup Attributes Execute any macros on an export and
Stuart McCullochbb014372012-06-07 21:57:32 +00001259 */
1260
1261 void fixupAttributes(Attrs attributes) {
1262 // Convert any attribute values that have macros.
1263 for (String key : attributes.keySet()) {
1264 String value = attributes.get(key);
1265 if (value.indexOf('$') >= 0) {
1266 value = getReplacer().process(value);
1267 attributes.put(key, value);
1268 }
1269 }
1270
1271 }
1272
1273 /**
1274 * Remove the attributes mentioned in the REMOVE_ATTRIBUTE_DIRECTIVE. You
1275 * can add a remove-attribute: directive with a regular expression for
1276 * attributes that need to be removed. We also remove all attributes that
1277 * have a value of !. This allows you to use macros with ${if} to remove
1278 * values.
1279 */
1280
1281 void removeAttributes(Attrs attributes) {
1282 String remove = attributes.remove(REMOVE_ATTRIBUTE_DIRECTIVE);
1283
1284 if (remove != null) {
1285 Instructions removeInstr = new Instructions(remove);
1286 attributes.keySet().removeAll(removeInstr.select(attributes.keySet(), false));
1287 }
1288
1289 // Remove any ! valued attributes
Stuart McCulloch2286f232012-06-15 13:27:53 +00001290 for (Iterator<Entry<String,String>> i = attributes.entrySet().iterator(); i.hasNext();) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001291 String v = i.next().getValue();
1292 if (v.equals("!"))
1293 i.remove();
1294 }
1295 }
1296
1297 /**
1298 * Calculate a version from a version policy.
1299 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001300 * @param version
1301 * The actual exported version
1302 * @param impl
1303 * true for implementations and false for clients
Stuart McCullochbb014372012-06-07 21:57:32 +00001304 */
1305
1306 String calculateVersionRange(String version, boolean impl) {
1307 setProperty("@", version);
1308 try {
1309 return getVersionPolicy(impl);
1310 }
1311 finally {
1312 unsetProperty("@");
1313 }
1314 }
1315
1316 /**
1317 * Add the uses clauses. This method iterates over the exports and cal
1318 *
1319 * @param exports
1320 * @param uses
1321 * @throws MojoExecutionException
1322 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001323 void doUses(Packages exports, Map<PackageRef,List<PackageRef>> uses, Packages imports) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001324 if ("true".equalsIgnoreCase(getProperty(NOUSES)))
1325 return;
1326
1327 for (Iterator<PackageRef> i = exports.keySet().iterator(); i.hasNext();) {
1328 PackageRef packageRef = i.next();
1329 String packageName = packageRef.getFQN();
1330 setProperty(CURRENT_PACKAGE, packageName);
1331 try {
1332 doUses(packageRef, exports, uses, imports);
1333 }
1334 finally {
1335 unsetProperty(CURRENT_PACKAGE);
1336 }
1337
1338 }
1339 }
1340
1341 /**
1342 * @param packageName
1343 * @param exports
1344 * @param uses
1345 * @param imports
1346 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001347 protected void doUses(PackageRef packageRef, Packages exports, Map<PackageRef,List<PackageRef>> uses,
Stuart McCulloch2286f232012-06-15 13:27:53 +00001348 Packages imports) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001349 Attrs clause = exports.get(packageRef);
1350
1351 // Check if someone already set the uses: directive
1352 String override = clause.get(USES_DIRECTIVE);
1353 if (override == null)
1354 override = USES_USES;
1355
1356 // Get the used packages
1357 Collection<PackageRef> usedPackages = uses.get(packageRef);
1358
1359 if (usedPackages != null) {
1360
1361 // Only do a uses on exported or imported packages
1362 // and uses should also not contain our own package
1363 // name
1364 Set<PackageRef> sharedPackages = new HashSet<PackageRef>();
1365 sharedPackages.addAll(imports.keySet());
1366 sharedPackages.addAll(exports.keySet());
1367 sharedPackages.retainAll(usedPackages);
1368 sharedPackages.remove(packageRef);
1369
1370 StringBuilder sb = new StringBuilder();
1371 String del = "";
1372 for (Iterator<PackageRef> u = sharedPackages.iterator(); u.hasNext();) {
1373 PackageRef usedPackage = u.next();
1374 if (!usedPackage.isJava()) {
1375 sb.append(del);
1376 sb.append(usedPackage.getFQN());
1377 del = ",";
1378 }
1379 }
1380 if (override.indexOf('$') >= 0) {
1381 setProperty(CURRENT_USES, sb.toString());
1382 override = getReplacer().process(override);
1383 unsetProperty(CURRENT_USES);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001384 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00001385 // This is for backward compatibility 0.0.287
1386 // can be deprecated over time
Stuart McCulloch2286f232012-06-15 13:27:53 +00001387 override = override.replaceAll(USES_USES, Matcher.quoteReplacement(sb.toString())).trim();
Stuart McCullochbb014372012-06-07 21:57:32 +00001388
1389 if (override.endsWith(","))
1390 override = override.substring(0, override.length() - 1);
1391 if (override.startsWith(","))
1392 override = override.substring(1);
1393 if (override.length() > 0) {
1394 clause.put(USES_DIRECTIVE, override);
1395 }
1396 }
1397 }
1398
1399 /**
1400 * Transitively remove all elemens from unreachable through the uses link.
1401 *
1402 * @param name
1403 * @param unreachable
1404 */
1405 void removeTransitive(PackageRef name, Set<PackageRef> unreachable) {
1406 if (!unreachable.contains(name))
1407 return;
1408
1409 unreachable.remove(name);
1410
1411 List<PackageRef> ref = uses.get(name);
1412 if (ref != null) {
1413 for (Iterator<PackageRef> r = ref.iterator(); r.hasNext();) {
1414 PackageRef element = r.next();
1415 removeTransitive(element, unreachable);
1416 }
1417 }
1418 }
1419
1420 /**
1421 * Helper method to set the package info resource
1422 *
1423 * @param dir
1424 * @param key
1425 * @param value
1426 * @throws Exception
1427 */
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001428 void getExportVersionsFromPackageInfo(PackageRef packageRef, Resource r, Packages classpathExports)
1429 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001430 if (r == null)
1431 return;
1432
1433 Properties p = new Properties();
Stuart McCullochbb014372012-06-07 21:57:32 +00001434 try {
Stuart McCullochd4826102012-06-26 16:34:24 +00001435 InputStream in = r.openInputStream();
1436 try {
1437 p.load(in);
Stuart McCullochbb014372012-06-07 21:57:32 +00001438 }
Stuart McCullochd4826102012-06-26 16:34:24 +00001439 finally {
1440 in.close();
1441 }
1442 Attrs map = classpathExports.get(packageRef);
1443 if (map == null) {
1444 classpathExports.put(packageRef, map = new Attrs());
1445 }
1446 for (Enumeration<String> t = (Enumeration<String>) p.propertyNames(); t.hasMoreElements();) {
1447 String key = t.nextElement();
1448 String value = map.get(key);
1449 if (value == null) {
1450 value = p.getProperty(key);
1451
1452 // Messy, to allow directives we need to
1453 // allow the value to start with a ':' since we cannot
1454 // encode this in a property name
1455
1456 if (value.startsWith(":")) {
1457 key = key + ":";
1458 value = value.substring(1);
1459 }
1460 map.put(key, value);
1461 }
1462 }
1463 }
1464 catch (Exception e) {
1465 msgs.NoSuchFile_(r);
Stuart McCullochbb014372012-06-07 21:57:32 +00001466 }
1467 }
1468
1469 public void close() {
1470 if (diagnostics) {
1471 PrintStream out = System.err;
1472 out.printf("Current directory : %s%n", new File("").getAbsolutePath());
1473 out.println("Classpath used");
1474 for (Jar jar : getClasspath()) {
1475 out.printf("File : %s%n", jar.getSource());
Stuart McCulloch2286f232012-06-15 13:27:53 +00001476 out.printf("File abs path : %s%n", jar.getSource().getAbsolutePath());
Stuart McCullochbb014372012-06-07 21:57:32 +00001477 out.printf("Name : %s%n", jar.getName());
Stuart McCulloch2286f232012-06-15 13:27:53 +00001478 Map<String,Map<String,Resource>> dirs = jar.getDirectories();
1479 for (Map.Entry<String,Map<String,Resource>> entry : dirs.entrySet()) {
1480 Map<String,Resource> dir = entry.getValue();
Stuart McCullochbb014372012-06-07 21:57:32 +00001481 String name = entry.getKey().replace('/', '.');
1482 if (dir != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001483 out.printf(" %-30s %d%n", name, dir.size());
1484 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001485 out.printf(" %-30s <<empty>>%n", name);
1486 }
1487 }
1488 }
1489 }
1490
1491 super.close();
1492 if (dot != null)
1493 dot.close();
1494
1495 if (classpath != null)
1496 for (Iterator<Jar> j = classpath.iterator(); j.hasNext();) {
1497 Jar jar = j.next();
1498 jar.close();
1499 }
1500 }
1501
1502 /**
1503 * Findpath looks through the contents of the JAR and finds paths that end
Stuart McCulloch2286f232012-06-15 13:27:53 +00001504 * with the given regular expression ${findpath (; reg-expr (; replacement)?
1505 * )? }
Stuart McCullochbb014372012-06-07 21:57:32 +00001506 *
1507 * @param args
1508 * @return
1509 */
1510 public String _findpath(String args[]) {
1511 return findPath("findpath", args, true);
1512 }
1513
1514 public String _findname(String args[]) {
1515 return findPath("findname", args, false);
1516 }
1517
1518 String findPath(String name, String[] args, boolean fullPathName) {
1519 if (args.length > 3) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001520 warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
1521 + " (; reg-expr (; replacement)? )? }");
Stuart McCullochbb014372012-06-07 21:57:32 +00001522 return null;
1523 }
1524
1525 String regexp = ".*";
1526 String replace = null;
1527
1528 switch (args.length) {
1529 case 3 :
1530 replace = args[2];
1531 //$FALL-THROUGH$
1532 case 2 :
1533 regexp = args[1];
1534 }
1535 StringBuilder sb = new StringBuilder();
1536 String del = "";
1537
1538 Pattern expr = Pattern.compile(regexp);
1539 for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
1540 String path = e.next();
1541 if (!fullPathName) {
1542 int n = path.lastIndexOf('/');
1543 if (n >= 0) {
1544 path = path.substring(n + 1);
1545 }
1546 }
1547
1548 Matcher m = expr.matcher(path);
1549 if (m.matches()) {
1550 if (replace != null)
1551 path = m.replaceAll(replace);
1552
1553 sb.append(del);
1554 sb.append(path);
1555 del = ", ";
1556 }
1557 }
1558 return sb.toString();
1559 }
1560
Stuart McCulloch2286f232012-06-15 13:27:53 +00001561 public void putAll(Map<String,String> additional, boolean force) {
1562 for (Iterator<Map.Entry<String,String>> i = additional.entrySet().iterator(); i.hasNext();) {
1563 Map.Entry<String,String> entry = i.next();
Stuart McCullochbb014372012-06-07 21:57:32 +00001564 if (force || getProperties().get(entry.getKey()) == null)
1565 setProperty(entry.getKey(), entry.getValue());
1566 }
1567 }
1568
1569 boolean firstUse = true;
1570
1571 public List<Jar> getClasspath() {
1572 if (firstUse) {
1573 firstUse = false;
1574 String cp = getProperty(CLASSPATH);
1575 if (cp != null)
1576 for (String s : split(cp)) {
1577 Jar jar = getJarFromName(s, "getting classpath");
1578 if (jar != null)
1579 addClasspath(jar);
1580 else
1581 warning("Cannot find entry on -classpath: %s", s);
1582 }
1583 }
1584 return classpath;
1585 }
1586
1587 public void addClasspath(Jar jar) {
1588 if (isPedantic() && jar.getResources().isEmpty())
1589 warning("There is an empty jar or directory on the classpath: " + jar.getName());
1590
1591 classpath.add(jar);
1592 }
1593
1594 public void addClasspath(Collection< ? > jars) throws IOException {
1595 for (Object jar : jars) {
1596 if (jar instanceof Jar)
1597 addClasspath((Jar) jar);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001598 else if (jar instanceof File)
1599 addClasspath((File) jar);
1600 else if (jar instanceof String)
1601 addClasspath(getFile((String) jar));
Stuart McCullochbb014372012-06-07 21:57:32 +00001602 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001603 error("Cannot convert to JAR to add to classpath %s. Not a File, Jar, or String", jar);
Stuart McCullochbb014372012-06-07 21:57:32 +00001604 }
1605 }
1606
1607 public void addClasspath(File cp) throws IOException {
1608 if (!cp.exists())
1609 warning("File on classpath that does not exist: " + cp);
1610 Jar jar = new Jar(cp);
1611 addClose(jar);
1612 classpath.add(jar);
1613 }
1614
1615 public void clear() {
1616 classpath.clear();
1617 }
1618
1619 public Jar getTarget() {
1620 return dot;
1621 }
1622
1623 private void analyzeBundleClasspath() throws Exception {
1624 Parameters bcp = getBundleClasspath();
1625
1626 if (bcp.isEmpty()) {
1627 analyzeJar(dot, "", true);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001628 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001629 boolean okToIncludeDirs = true;
1630
1631 for (String path : bcp.keySet()) {
1632 if (dot.getDirectories().containsKey(path)) {
1633 okToIncludeDirs = false;
1634 break;
1635 }
1636 }
1637
1638 for (String path : bcp.keySet()) {
1639 Attrs info = bcp.get(path);
1640
1641 if (path.equals(".")) {
1642 analyzeJar(dot, "", okToIncludeDirs);
1643 continue;
1644 }
1645 //
1646 // There are 3 cases:
1647 // - embedded JAR file
1648 // - directory
1649 // - error
1650 //
1651
1652 Resource resource = dot.getResource(path);
1653 if (resource != null) {
1654 try {
1655 Jar jar = new Jar(path);
1656 addClose(jar);
1657 EmbeddedResource.build(jar, resource);
1658 analyzeJar(jar, "", true);
1659 }
1660 catch (Exception e) {
1661 warning("Invalid bundle classpath entry: " + path + " " + e);
1662 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001663 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001664 if (dot.getDirectories().containsKey(path)) {
1665 // if directories are used, we should not have dot as we
1666 // would have the classes in these directories on the
1667 // class path twice.
1668 if (bcp.containsKey("."))
1669 warning("Bundle-ClassPath uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
1670 path, path);
1671 analyzeJar(dot, Processor.appendPath(path) + "/", true);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001672 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001673 if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
1674 warning("No sub JAR or directory " + path);
1675 }
1676 }
1677 }
1678
1679 }
1680 }
1681
1682 /**
1683 * We traverse through all the classes that we can find and calculate the
1684 * contained and referred set and uses. This method ignores the Bundle
1685 * classpath.
1686 *
1687 * @param jar
1688 * @param contained
1689 * @param referred
1690 * @param uses
1691 * @throws IOException
1692 */
1693 private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001694 Map<String,Clazz> mismatched = new HashMap<String,Clazz>();
Stuart McCullochbb014372012-06-07 21:57:32 +00001695
1696 next: for (String path : jar.getResources().keySet()) {
1697 if (path.startsWith(prefix)) {
1698
1699 String relativePath = path.substring(prefix.length());
1700
1701 if (okToIncludeDirs) {
1702 int n = relativePath.lastIndexOf('/');
1703 if (n < 0)
1704 n = relativePath.length();
1705 String relativeDir = relativePath.substring(0, n);
1706
1707 PackageRef packageRef = getPackageRef(relativeDir);
1708 if (!packageRef.isMetaData() && !contained.containsKey(packageRef)) {
1709 contained.put(packageRef);
1710
1711 // For each package we encounter for the first
1712 // time. Unfortunately we can only do this once
1713 // we found a class since the bcp has a tendency
1714 // to overlap
1715 if (!packageRef.isMetaData()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001716 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCullochd4826102012-06-26 16:34:24 +00001717 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochbb014372012-06-07 21:57:32 +00001718 }
1719 }
1720 }
1721
1722 // Check class resources, we need to analyze them
1723 if (path.endsWith(".class")) {
1724 Resource resource = jar.getResource(path);
1725 Clazz clazz;
1726 Attrs info = null;
1727
1728 try {
1729 InputStream in = resource.openInputStream();
1730 clazz = new Clazz(this, path, resource);
1731 try {
1732 // Check if we have a package-info
1733 if (relativePath.endsWith("/package-info.class")) {
1734 // package-info can contain an Export annotation
1735 info = new Attrs();
1736 parsePackageInfoClass(clazz, info);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001737 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001738 // Otherwise we just parse it simply
1739 clazz.parseClassFile();
1740 }
1741 }
1742 finally {
1743 in.close();
1744 }
1745 }
1746 catch (Throwable e) {
1747 error("Invalid class file %s (%s)", e, relativePath, e);
1748 e.printStackTrace();
1749 continue next;
1750 }
1751
1752 String calculatedPath = clazz.getClassName().getPath();
1753 if (!calculatedPath.equals(relativePath)) {
1754 // If there is a mismatch we
1755 // warning
1756 if (okToIncludeDirs) // assume already reported
1757 mismatched.put(clazz.getAbsolutePath(), clazz);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001758 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001759 classspace.put(clazz.getClassName(), clazz);
1760 PackageRef packageRef = clazz.getClassName().getPackageRef();
1761
1762 if (!contained.containsKey(packageRef)) {
1763 contained.put(packageRef);
1764 if (!packageRef.isMetaData()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001765 Resource pinfo = jar.getResource(prefix + packageRef.getPath() + "/packageinfo");
Stuart McCullochd4826102012-06-26 16:34:24 +00001766 getExportVersionsFromPackageInfo(packageRef, pinfo, classpathExports);
Stuart McCullochbb014372012-06-07 21:57:32 +00001767 }
1768 }
1769 if (info != null)
1770 contained.merge(packageRef, false, info);
1771
Stuart McCullochbb014372012-06-07 21:57:32 +00001772 // Look at the referred packages
1773 // and copy them to our baseline
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001774 Set<PackageRef> refs = Create.set();
Stuart McCullochbb014372012-06-07 21:57:32 +00001775 for (PackageRef p : clazz.getReferred()) {
1776 referred.put(p);
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001777 refs.add(p);
Stuart McCullochbb014372012-06-07 21:57:32 +00001778 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001779 refs.remove(packageRef);
1780 uses.addAll(packageRef, refs);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00001781
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001782 // Collect the API
1783 apiUses.addAll(packageRef, clazz.getAPIUses());
Stuart McCullochbb014372012-06-07 21:57:32 +00001784 }
1785 }
1786 }
1787 }
1788
1789 if (mismatched.size() > 0) {
1790 error("Classes found in the wrong directory: %s", mismatched);
1791 return false;
1792 }
1793 return true;
1794 }
1795
1796 static Pattern OBJECT_REFERENCE = Pattern.compile("L([^/]+/)*([^;]+);");
1797
1798 private void parsePackageInfoClass(final Clazz clazz, final Attrs info) throws Exception {
1799 clazz.parseClassFileWithCollector(new ClassDataCollector() {
1800 @Override
1801 public void annotation(Annotation a) {
1802 String name = a.name.getFQN();
1803 if (aQute.bnd.annotation.Version.class.getName().equals(name)) {
1804
1805 // Check version
1806 String version = a.get("value");
1807 if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
1808 if (version != null) {
1809 version = getReplacer().process(version);
1810 if (Verifier.VERSION.matcher(version).matches())
1811 info.put(VERSION_ATTRIBUTE, version);
1812 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001813 error("Export annotation in %s has invalid version info: %s", clazz, version);
Stuart McCullochbb014372012-06-07 21:57:32 +00001814 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001815 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001816 // Verify this matches with packageinfo
1817 String presentVersion = info.get(VERSION_ATTRIBUTE);
1818 try {
1819 Version av = new Version(presentVersion);
1820 Version bv = new Version(version);
1821 if (!av.equals(bv)) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001822 error("Version from annotation for %s differs with packageinfo or Manifest", clazz
1823 .getClassName().getFQN());
Stuart McCullochbb014372012-06-07 21:57:32 +00001824 }
1825 }
1826 catch (Exception e) {
1827 // Ignore
1828 }
1829 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001830 } else if (name.equals(Export.class.getName())) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001831
Stuart McCulloch2286f232012-06-15 13:27:53 +00001832 // Check mandatory attributes
1833 Attrs attrs = doAttrbutes((Object[]) a.get(Export.MANDATORY), clazz, getReplacer());
1834 if (!attrs.isEmpty()) {
1835 info.putAll(attrs);
1836 info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
1837 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001838
Stuart McCulloch2286f232012-06-15 13:27:53 +00001839 // Check optional attributes
1840 attrs = doAttrbutes((Object[]) a.get(Export.OPTIONAL), clazz, getReplacer());
1841 if (!attrs.isEmpty()) {
1842 info.putAll(attrs);
1843 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001844
Stuart McCulloch2286f232012-06-15 13:27:53 +00001845 // Check Included classes
1846 Object[] included = a.get(Export.INCLUDE);
1847 if (included != null && included.length > 0) {
1848 StringBuilder sb = new StringBuilder();
1849 String del = "";
1850 for (Object i : included) {
1851 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1852 if (m.matches()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001853 sb.append(del);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001854 sb.append(m.group(2));
Stuart McCullochbb014372012-06-07 21:57:32 +00001855 del = ",";
1856 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001857 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001858 info.put(INCLUDE_DIRECTIVE, sb.toString());
Stuart McCullochbb014372012-06-07 21:57:32 +00001859 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001860
1861 // Check Excluded classes
1862 Object[] excluded = a.get(Export.EXCLUDE);
1863 if (excluded != null && excluded.length > 0) {
1864 StringBuilder sb = new StringBuilder();
1865 String del = "";
1866 for (Object i : excluded) {
1867 Matcher m = OBJECT_REFERENCE.matcher((String) i);
1868 if (m.matches()) {
1869 sb.append(del);
1870 sb.append(m.group(2));
1871 del = ",";
1872 }
1873 }
1874 info.put(EXCLUDE_DIRECTIVE, sb.toString());
1875 }
1876
1877 // Check Uses
1878 Object[] uses = a.get(Export.USES);
1879 if (uses != null && uses.length > 0) {
1880 String old = info.get(USES_DIRECTIVE);
1881 if (old == null)
1882 old = "";
1883 StringBuilder sb = new StringBuilder(old);
1884 String del = sb.length() == 0 ? "" : ",";
1885
1886 for (Object use : uses) {
1887 sb.append(del);
1888 sb.append(use);
1889 del = ",";
1890 }
1891 info.put(USES_DIRECTIVE, sb.toString());
1892 }
1893 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001894 }
1895
1896 });
1897 }
1898
1899 /**
1900 * Clean up version parameters. Other builders use more fuzzy definitions of
1901 * the version syntax. This method cleans up such a version to match an OSGi
1902 * version.
1903 *
1904 * @param VERSION_STRING
1905 * @return
1906 */
Stuart McCulloch2286f232012-06-15 13:27:53 +00001907 static Pattern fuzzyVersion = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
1908 Pattern.DOTALL);
1909 static Pattern fuzzyVersionRange = Pattern.compile(
1910 "(\\(|\\[)\\s*([-\\da-zA-Z.]+)\\s*,\\s*([-\\da-zA-Z.]+)\\s*(\\]|\\))",
1911 Pattern.DOTALL);
Stuart McCullochbb014372012-06-07 21:57:32 +00001912 static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)", Pattern.DOTALL);
1913
1914 static Pattern nummeric = Pattern.compile("\\d*");
1915
1916 static public String cleanupVersion(String version) {
1917 Matcher m = Verifier.VERSIONRANGE.matcher(version);
1918
1919 if (m.matches()) {
1920 return version;
1921 }
1922
1923 m = fuzzyVersionRange.matcher(version);
1924 if (m.matches()) {
1925 String prefix = m.group(1);
1926 String first = m.group(2);
1927 String last = m.group(3);
1928 String suffix = m.group(4);
1929 return prefix + cleanupVersion(first) + "," + cleanupVersion(last) + suffix;
1930 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001931
Stuart McCulloch2286f232012-06-15 13:27:53 +00001932 m = fuzzyVersion.matcher(version);
1933 if (m.matches()) {
1934 StringBuilder result = new StringBuilder();
1935 String major = removeLeadingZeroes(m.group(1));
1936 String minor = removeLeadingZeroes(m.group(3));
1937 String micro = removeLeadingZeroes(m.group(5));
1938 String qualifier = m.group(7);
1939
1940 if (major != null) {
1941 result.append(major);
1942 if (minor != null) {
1943 result.append(".");
1944 result.append(minor);
1945 if (micro != null) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001946 result.append(".");
Stuart McCulloch2286f232012-06-15 13:27:53 +00001947 result.append(micro);
Stuart McCullochbb014372012-06-07 21:57:32 +00001948 if (qualifier != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001949 result.append(".");
Stuart McCullochbb014372012-06-07 21:57:32 +00001950 cleanupModifier(result, qualifier);
1951 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001952 } else if (qualifier != null) {
1953 result.append(".0.");
1954 cleanupModifier(result, qualifier);
1955 }
1956 } else if (qualifier != null) {
1957 result.append(".0.0.");
1958 cleanupModifier(result, qualifier);
Stuart McCullochbb014372012-06-07 21:57:32 +00001959 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001960 return result.toString();
Stuart McCullochbb014372012-06-07 21:57:32 +00001961 }
1962 }
1963 return version;
1964 }
1965
1966 private static String removeLeadingZeroes(String group) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001967 if (group == null)
1968 return null;
1969
Stuart McCullochbb014372012-06-07 21:57:32 +00001970 int n = 0;
Stuart McCulloch2286f232012-06-15 13:27:53 +00001971 while (n < group.length() - 1 && group.charAt(n) == '0')
Stuart McCullochbb014372012-06-07 21:57:32 +00001972 n++;
1973 if (n == 0)
1974 return group;
1975
1976 return group.substring(n);
1977 }
1978
1979 static void cleanupModifier(StringBuilder result, String modifier) {
1980 Matcher m = fuzzyModifier.matcher(modifier);
1981 if (m.matches())
1982 modifier = m.group(2);
1983
1984 for (int i = 0; i < modifier.length(); i++) {
1985 char c = modifier.charAt(i);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001986 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
Stuart McCullochbb014372012-06-07 21:57:32 +00001987 result.append(c);
1988 }
1989 }
1990
1991 final static String DEFAULT_PROVIDER_POLICY = "${range;[==,=+)}";
1992 final static String DEFAULT_CONSUMER_POLICY = "${range;[==,+)}";
1993
Stuart McCullochbb014372012-06-07 21:57:32 +00001994 public String getVersionPolicy(boolean implemented) {
1995 if (implemented) {
Stuart McCullochd4826102012-06-26 16:34:24 +00001996 return getProperty(PROVIDER_POLICY, DEFAULT_PROVIDER_POLICY);
Stuart McCullochbb014372012-06-07 21:57:32 +00001997 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001998
Stuart McCullochd4826102012-06-26 16:34:24 +00001999 return getProperty(CONSUMER_POLICY, DEFAULT_CONSUMER_POLICY);
Stuart McCullochbb014372012-06-07 21:57:32 +00002000 }
2001
2002 /**
2003 * The extends macro traverses all classes and returns a list of class names
2004 * that extend a base class.
2005 */
2006
2007 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";
2008
2009 public String _classes(String... args) throws Exception {
2010 // Macro.verifyCommand(args, _classesHelp, new
2011 // Pattern[]{null,Pattern.compile("(implementing|implements|extending|extends|importing|imports|any)"),
2012 // null}, 3,3);
2013
2014 Collection<Clazz> matched = getClasses(args);
2015 if (matched.isEmpty())
2016 return "";
2017
2018 return join(matched);
2019 }
2020
2021 public Collection<Clazz> getClasses(String... args) throws Exception {
2022
2023 Set<Clazz> matched = new HashSet<Clazz>(classspace.values());
2024 for (int i = 1; i < args.length; i++) {
2025 if (args.length < i + 1)
Stuart McCulloch2286f232012-06-15 13:27:53 +00002026 throw new IllegalArgumentException("${classes} macro must have odd number of arguments. "
2027 + _classesHelp);
Stuart McCullochbb014372012-06-07 21:57:32 +00002028
2029 String typeName = args[i];
2030 if (typeName.equalsIgnoreCase("extending"))
2031 typeName = "extends";
Stuart McCulloch2286f232012-06-15 13:27:53 +00002032 else if (typeName.equalsIgnoreCase("importing"))
2033 typeName = "imports";
2034 else if (typeName.equalsIgnoreCase("implementing"))
2035 typeName = "implements";
Stuart McCullochbb014372012-06-07 21:57:32 +00002036
2037 Clazz.QUERY type = Clazz.QUERY.valueOf(typeName.toUpperCase());
2038
2039 if (type == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +00002040 throw new IllegalArgumentException("${classes} has invalid type: " + typeName + ". " + _classesHelp);
Stuart McCullochbb014372012-06-07 21:57:32 +00002041
2042 Instruction instr = null;
2043 if (Clazz.HAS_ARGUMENT.contains(type)) {
2044 String s = args[++i];
2045 instr = new Instruction(s);
2046 }
2047 for (Iterator<Clazz> c = matched.iterator(); c.hasNext();) {
2048 Clazz clazz = c.next();
2049 if (!clazz.is(type, instr, this)) {
2050 c.remove();
2051 }
2052 }
2053 }
2054 return matched;
2055 }
2056
2057 /**
2058 * Get the exporter of a package ...
2059 */
2060
2061 public String _exporters(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002062 Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
Stuart McCullochbb014372012-06-07 21:57:32 +00002063 null, 2, 2);
2064 StringBuilder sb = new StringBuilder();
2065 String del = "";
2066 String pack = args[1].replace('.', '/');
2067 for (Jar jar : classpath) {
2068 if (jar.getDirectories().containsKey(pack)) {
2069 sb.append(del);
2070 sb.append(jar.getName());
2071 }
2072 }
2073 return sb.toString();
2074 }
2075
Stuart McCulloch2286f232012-06-15 13:27:53 +00002076 public Map<TypeRef,Clazz> getClassspace() {
Stuart McCullochbb014372012-06-07 21:57:32 +00002077 return classspace;
2078 }
2079
2080 /**
2081 * Locate a resource on the class path.
2082 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002083 * @param path
2084 * Path of the reosurce
Stuart McCullochbb014372012-06-07 21:57:32 +00002085 * @return A resource or <code>null</code>
2086 */
2087 public Resource findResource(String path) {
2088 for (Jar entry : getClasspath()) {
2089 Resource r = entry.getResource(path);
2090 if (r != null)
2091 return r;
2092 }
2093 return null;
2094 }
2095
2096 /**
2097 * Find a clazz on the class path. This class has been parsed.
2098 *
2099 * @param path
2100 * @return
2101 */
2102 public Clazz findClass(TypeRef typeRef) throws Exception {
2103 Clazz c = classspace.get(typeRef);
2104 if (c != null)
2105 return c;
2106
2107 c = importedClassesCache.get(typeRef);
2108 if (c != null)
2109 return c;
2110
2111 Resource r = findResource(typeRef.getPath());
2112 if (r == null) {
2113 getClass().getClassLoader();
2114 URL url = ClassLoader.getSystemResource(typeRef.getPath());
2115 if (url != null)
2116 r = new URLResource(url);
2117 }
2118 if (r != null) {
2119 c = new Clazz(this, typeRef.getPath(), r);
2120 c.parseClassFile();
2121 importedClassesCache.put(typeRef, c);
2122 }
2123 return c;
2124 }
2125
2126 /**
2127 * Answer the bundle version.
2128 *
2129 * @return
2130 */
2131 public String getVersion() {
2132 String version = getProperty(BUNDLE_VERSION);
2133 if (version == null)
2134 version = "0.0.0";
2135 return version;
2136 }
2137
2138 public boolean isNoBundle() {
2139 return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
2140 }
2141
2142 public void referTo(TypeRef ref) {
2143 PackageRef pack = ref.getPackageRef();
2144 if (!referred.containsKey(pack))
2145 referred.put(pack, new Attrs());
2146 }
2147
2148 public void referToByBinaryName(String binaryClassName) {
2149 TypeRef ref = descriptors.getTypeRef(binaryClassName);
2150 referTo(ref);
2151 }
2152
2153 /**
2154 * Ensure that we are running on the correct bnd.
2155 */
2156 void doRequireBnd() {
2157 Attrs require = OSGiHeader.parseProperties(getProperty(REQUIRE_BND));
2158 if (require == null || require.isEmpty())
2159 return;
2160
Stuart McCulloch2286f232012-06-15 13:27:53 +00002161 Hashtable<String,String> map = new Hashtable<String,String>();
Stuart McCullochbb014372012-06-07 21:57:32 +00002162 map.put(Constants.VERSION_FILTER, getBndVersion());
2163
2164 for (String filter : require.keySet()) {
2165 try {
2166 Filter f = new Filter(filter);
2167 if (f.match(map))
2168 continue;
2169 error("%s fails %s", REQUIRE_BND, require.get(filter));
2170 }
2171 catch (Exception t) {
2172 error("%s with value %s throws exception", t, REQUIRE_BND, require);
2173 }
2174 }
2175 }
2176
2177 /**
2178 * md5 macro
2179 */
2180
2181 static String _md5Help = "${md5;path}";
2182
2183 public String _md5(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002184 Macro.verifyCommand(args, _md5Help, new Pattern[] {
2185 null, null, Pattern.compile("base64|hex")
2186 }, 2, 3);
Stuart McCullochbb014372012-06-07 21:57:32 +00002187
2188 Digester<MD5> digester = MD5.getDigester();
2189 Resource r = dot.getResource(args[1]);
2190 if (r == null)
2191 throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
2192
2193 IO.copy(r.openInputStream(), digester);
2194 boolean hex = args.length > 2 && args[2].equals("hex");
2195 if (hex)
2196 return Hex.toHexString(digester.digest().digest());
Stuart McCulloch2286f232012-06-15 13:27:53 +00002197
2198 return Base64.encodeBase64(digester.digest().digest());
Stuart McCullochbb014372012-06-07 21:57:32 +00002199 }
2200
2201 /**
2202 * SHA1 macro
2203 */
2204
2205 static String _sha1Help = "${sha1;path}";
2206
2207 public String _sha1(String args[]) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002208 Macro.verifyCommand(args, _sha1Help, new Pattern[] {
2209 null, null, Pattern.compile("base64|hex")
2210 }, 2, 3);
Stuart McCullochbb014372012-06-07 21:57:32 +00002211 Digester<SHA1> digester = SHA1.getDigester();
2212 Resource r = dot.getResource(args[1]);
2213 if (r == null)
2214 throw new FileNotFoundException("From sha1, not found " + args[1]);
2215
2216 IO.copy(r.openInputStream(), digester);
2217 return Base64.encodeBase64(digester.digest().digest());
2218 }
2219
2220 public Descriptor getDescriptor(String descriptor) {
2221 return descriptors.getDescriptor(descriptor);
2222 }
2223
2224 public TypeRef getTypeRef(String binaryClassName) {
2225 return descriptors.getTypeRef(binaryClassName);
2226 }
2227
2228 public PackageRef getPackageRef(String binaryName) {
2229 return descriptors.getPackageRef(binaryName);
2230 }
2231
2232 public TypeRef getTypeRefFromFQN(String fqn) {
2233 return descriptors.getTypeRefFromFQN(fqn);
2234 }
2235
2236 public TypeRef getTypeRefFromPath(String path) {
2237 return descriptors.getTypeRefFromPath(path);
2238 }
2239
2240 public boolean isImported(PackageRef packageRef) {
2241 return imports.containsKey(packageRef);
2242 }
2243
2244 /**
2245 * Merge the attributes of two maps, where the first map can contain
2246 * wildcarded names. The idea is that the first map contains instructions
2247 * (for example *) with a set of attributes. These patterns are matched
2248 * against the found packages in actual. If they match, the result is set
2249 * with the merged set of attributes. It is expected that the instructions
2250 * are ordered so that the instructor can define which pattern matches
2251 * first. Attributes in the instructions override any attributes from the
2252 * actual.<br/>
Stuart McCullochbb014372012-06-07 21:57:32 +00002253 * A pattern is a modified regexp so it looks like globbing. The * becomes a
2254 * .* just like the ? becomes a .?. '.' are replaced with \\. Additionally,
2255 * if the pattern starts with an exclamation mark, it will remove that
2256 * matches for that pattern (- the !) from the working set. So the following
2257 * patterns should work:
2258 * <ul>
2259 * <li>com.foo.bar</li>
2260 * <li>com.foo.*</li>
2261 * <li>com.foo.???</li>
2262 * <li>com.*.[^b][^a][^r]</li>
2263 * <li>!com.foo.* (throws away any match for com.foo.*)</li>
2264 * </ul>
2265 * Enough rope to hang the average developer I would say.
2266 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002267 * @param instructions
2268 * the instructions with patterns.
2269 * @param source
2270 * the actual found packages, contains no duplicates
Stuart McCullochbb014372012-06-07 21:57:32 +00002271 * @return Only the packages that were filtered by the given instructions
2272 */
2273
2274 Packages filter(Instructions instructions, Packages source, Set<Instruction> nomatch) {
2275 Packages result = new Packages();
2276 List<PackageRef> refs = new ArrayList<PackageRef>(source.keySet());
2277 Collections.sort(refs);
2278
2279 List<Instruction> filters = new ArrayList<Instruction>(instructions.keySet());
2280 if (nomatch == null)
2281 nomatch = Create.set();
2282
2283 for (Instruction instruction : filters) {
2284 boolean match = false;
2285
2286 for (Iterator<PackageRef> i = refs.iterator(); i.hasNext();) {
2287 PackageRef packageRef = i.next();
2288
2289 if (packageRef.isMetaData()) {
2290 i.remove(); // no use checking it again
2291 continue;
2292 }
2293
2294 String packageName = packageRef.getFQN();
2295
2296 if (instruction.matches(packageName)) {
2297 match = true;
2298 if (!instruction.isNegated()) {
2299 result.merge(packageRef, instruction.isDuplicate(), source.get(packageRef),
2300 instructions.get(instruction));
2301 }
2302 i.remove(); // Can never match again for another pattern
2303 }
2304 }
2305 if (!match && !instruction.isAny())
2306 nomatch.add(instruction);
2307 }
2308
2309 /*
2310 * Tricky. If we have umatched instructions they might indicate that we
2311 * want to have multiple decorators for the same package. So we check
2312 * the unmatched against the result list. If then then match and have
2313 * actually interesting properties then we merge them
2314 */
2315
2316 for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
2317 Instruction instruction = i.next();
2318
2319 // We assume the user knows what he is
2320 // doing and inserted a literal. So
2321 // we ignore any not matched literals
2322 if (instruction.isLiteral()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00002323 result.merge(getPackageRef(instruction.getLiteral()), true, instructions.get(instruction));
Stuart McCullochbb014372012-06-07 21:57:32 +00002324 i.remove();
2325 continue;
2326 }
2327
2328 // Not matching a negated instruction looks
2329 // like an error ...
2330 if (instruction.isNegated()) {
2331 continue;
2332 }
2333
2334 // An optional instruction should not generate
2335 // an error
2336 if (instruction.isOptional()) {
2337 i.remove();
2338 continue;
2339 }
2340
2341 // boolean matched = false;
2342 // Set<PackageRef> prefs = new HashSet<PackageRef>(result.keySet());
2343 // for (PackageRef ref : prefs) {
2344 // if (instruction.matches(ref.getFQN())) {
2345 // result.merge(ref, true, source.get(ref),
2346 // instructions.get(instruction));
2347 // matched = true;
2348 // }
2349 // }
2350 // if (matched)
2351 // i.remove();
2352 }
2353 return result;
2354 }
2355
2356 public void setDiagnostics(boolean b) {
2357 diagnostics = b;
2358 }
2359
2360 public Clazz.JAVA getLowestEE() {
2361 if (ees.isEmpty())
2362 return Clazz.JAVA.JDK1_4;
2363
2364 return ees.first();
2365 }
2366
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002367 public String _ee(@SuppressWarnings("unused")
2368 String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +00002369 return getLowestEE().getEE();
2370 }
2371
2372 /**
2373 * Calculate the output file for the given target. The strategy is:
2374 *
2375 * <pre>
2376 * parameter given if not null and not directory
2377 * if directory, this will be the output directory
2378 * based on bsn-version.jar
2379 * name of the source file if exists
2380 * Untitled-[n]
2381 * </pre>
2382 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002383 * @param output
2384 * may be null, otherwise a file path relative to base
Stuart McCullochbb014372012-06-07 21:57:32 +00002385 */
2386 public File getOutputFile(String output) {
2387
2388 if (output == null)
2389 output = get(Constants.OUTPUT);
2390
2391 File outputDir;
2392
2393 if (output != null) {
2394 File outputFile = getFile(output);
2395 if (outputFile.isDirectory())
2396 outputDir = outputFile;
2397 else
2398 return outputFile;
Stuart McCulloch2286f232012-06-15 13:27:53 +00002399 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00002400 outputDir = getBase();
2401
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002402 Entry<String,Attrs> name = getBundleSymbolicName();
2403 if (name != null) {
2404 String bsn = name.getKey();
Stuart McCullochbb014372012-06-07 21:57:32 +00002405 String version = getBundleVersion();
2406 Version v = Version.parseVersion(version);
Stuart McCulloch2286f232012-06-15 13:27:53 +00002407 String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
Stuart McCullochbb014372012-06-07 21:57:32 +00002408 return new File(outputDir, outputName);
2409 }
2410
2411 File source = getJar().getSource();
2412 if (source != null) {
2413 String outputName = source.getName();
2414 return new File(outputDir, outputName);
2415 }
2416
Stuart McCulloch2286f232012-06-15 13:27:53 +00002417 error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
Stuart McCullochbb014372012-06-07 21:57:32 +00002418 int n = 0;
2419 File f = getFile(outputDir, "Untitled");
2420 while (f.isFile()) {
2421 f = getFile(outputDir, "Untitled-" + n++);
2422 }
2423 return f;
2424 }
2425
2426 /**
2427 * Utility function to carefully save the file. Will create a backup if the
2428 * source file has the same path as the output. It will also only save if
2429 * the file was modified or the force flag is true
2430 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00002431 * @param output
2432 * the output file, if null {@link #getOutputFile(String)} is
2433 * used.
2434 * @param force
2435 * if it needs to be overwritten
Stuart McCullochbb014372012-06-07 21:57:32 +00002436 * @throws Exception
2437 */
2438
2439 public boolean save(File output, boolean force) throws Exception {
2440 if (output == null)
2441 output = getOutputFile(null);
2442
2443 Jar jar = getJar();
2444 File source = jar.getSource();
2445
Stuart McCulloch2286f232012-06-15 13:27:53 +00002446 trace("check for modified build=%s file=%s, diff=%s", jar.lastModified(), output.lastModified(),
2447 jar.lastModified() - output.lastModified());
Stuart McCullochbb014372012-06-07 21:57:32 +00002448
2449 if (!output.exists() || output.lastModified() <= jar.lastModified() || force) {
2450 output.getParentFile().mkdirs();
2451 if (source != null && output.getCanonicalPath().equals(source.getCanonicalPath())) {
2452 File bak = new File(source.getParentFile(), source.getName() + ".bak");
2453 if (!source.renameTo(bak)) {
2454 error("Could not create backup file %s", bak);
Stuart McCulloch2286f232012-06-15 13:27:53 +00002455 } else
Stuart McCullochbb014372012-06-07 21:57:32 +00002456 source.delete();
2457 }
2458 try {
2459 trace("Saving jar to %s", output);
2460 getJar().write(output);
2461 }
2462 catch (Exception e) {
2463 output.delete();
2464 error("Cannot write JAR file to %s due to %s", e, output, e.getMessage());
2465 }
2466 return true;
2467 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00002468 trace("Not modified %s", output);
2469 return false;
2470
Stuart McCullochbb014372012-06-07 21:57:32 +00002471 }
2472
2473 /**
2474 * Set default import and export instructions if none are set
2475 */
2476 public void setDefaults(String bsn, Version version) {
2477 if (getExportPackage() == null)
2478 setExportPackage("*");
2479 if (getImportPackage() == null)
2480 setExportPackage("*");
2481 if (bsn != null && getBundleSymbolicName() == null)
2482 setBundleSymbolicName(bsn);
2483 if (version != null && getBundleVersion() == null)
2484 setBundleVersion(version);
2485 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002486
2487 /**
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002488 * Remove the own references and optional java references from the uses lib
2489 *
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002490 * @param apiUses
2491 * @param removeJava
2492 * @return
2493 */
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002494 public Map<PackageRef,List<PackageRef>> cleanupUses(Map<PackageRef,List<PackageRef>> apiUses, boolean removeJava) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002495 MultiMap<PackageRef,PackageRef> map = new MultiMap<PackageRef,PackageRef>(apiUses);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002496 for (Entry<PackageRef,List<PackageRef>> e : map.entrySet()) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002497 e.getValue().remove(e.getKey());
2498 if (!removeJava)
2499 continue;
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002500
2501 for (Iterator<PackageRef> i = e.getValue().iterator(); i.hasNext();) {
2502 if (i.next().isJava())
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002503 i.remove();
2504 }
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002505 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00002506 return map;
2507 }
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002508
2509 /**
2510 * Return the classes for a given source package.
2511 *
2512 * @param source
2513 * the source package
2514 * @return a set of classes for the requested package.
2515 */
2516 public Set<Clazz> getClassspace(PackageRef source) {
2517 Set<Clazz> result = new HashSet<Clazz>();
2518 for (Clazz c : getClassspace().values()) {
2519 if (c.getClassName().getPackageRef() == source)
2520 result.add(c);
2521 }
2522 return result;
2523 }
2524
2525 /**
2526 * Create a cross reference from package source, to packages in dest
Stuart McCulloch61c61eb2012-07-24 21:37:47 +00002527 *
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002528 * @param source
2529 * @param dest
2530 * @param sourceModifiers
2531 * @return
2532 * @throws Exception
2533 */
Stuart McCulloch61c61eb2012-07-24 21:37:47 +00002534 public Map<Clazz.Def,List<TypeRef>> getXRef(final PackageRef source, final Collection<PackageRef> dest,
2535 final int sourceModifiers) throws Exception {
2536 final MultiMap<Clazz.Def,TypeRef> xref = new MultiMap<Clazz.Def,TypeRef>(Clazz.Def.class, TypeRef.class, true);
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002537
2538 for (final Clazz clazz : getClassspace().values()) {
2539 if ((clazz.accessx & sourceModifiers) == 0)
2540 continue;
2541
Stuart McCulloch61c61eb2012-07-24 21:37:47 +00002542 if (source != null && source != clazz.getClassName().getPackageRef())
Stuart McCulloch3b7e6af2012-07-16 14:10:57 +00002543 continue;
2544
2545 clazz.parseClassFileWithCollector(new ClassDataCollector() {
2546 Clazz.Def member;
2547
2548 public void extendsClass(TypeRef zuper) throws Exception {
2549 if (dest.contains(zuper.getPackageRef()))
2550 xref.add(clazz.getExtends(zuper), zuper);
2551 }
2552
2553 public void implementsInterfaces(TypeRef[] interfaces) throws Exception {
2554 for (TypeRef i : interfaces) {
2555 if (dest.contains(i.getPackageRef()))
2556 xref.add(clazz.getImplements(i), i);
2557 }
2558 }
2559
2560 public void referTo(TypeRef to, int modifiers) {
2561 if (to.isJava())
2562 return;
2563
2564 if (!dest.contains(to.getPackageRef()))
2565 return;
2566
2567 if (member != null && ((modifiers & sourceModifiers) != 0)) {
2568 xref.add(member, to);
2569 }
2570
2571 }
2572
2573 public void method(Clazz.MethodDef defined) {
2574 member = defined;
2575 }
2576
2577 public void field(Clazz.FieldDef defined) {
2578 member = defined;
2579 }
2580
2581 });
2582
2583 }
2584 return xref;
2585 }
2586
Stuart McCullochbb014372012-06-07 21:57:32 +00002587}