blob: b31be5c6ac778d1691b02219cb0ab7a99bd3ea3e [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.jar.*;
7import java.util.regex.*;
8import java.util.zip.*;
9
10import aQute.bnd.component.*;
11import aQute.bnd.differ.*;
12import aQute.bnd.differ.Baseline.Info;
13import aQute.bnd.make.*;
14import aQute.bnd.make.component.*;
15import aQute.bnd.make.metatype.*;
16import aQute.bnd.maven.*;
17import aQute.bnd.service.*;
18import aQute.bnd.service.diff.*;
19import aQute.lib.collections.*;
20import aQute.lib.osgi.Descriptors.PackageRef;
21import aQute.lib.osgi.Descriptors.TypeRef;
22import aQute.libg.generics.*;
23import aQute.libg.header.*;
24
25/**
Stuart McCulloch4482c702012-06-15 13:27:53 +000026 * Include-Resource: ( [name '=' ] file )+ Private-Package: package-decl ( ','
27 * package-decl )* Export-Package: package-decl ( ',' package-decl )*
Stuart McCullochf3173222012-06-07 21:57:32 +000028 * Import-Package: package-decl ( ',' package-decl )*
29 *
30 * @version $Revision$
31 */
32public class Builder extends Analyzer {
Stuart McCulloch4482c702012-06-15 13:27:53 +000033 static Pattern IR_PATTERN = Pattern.compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
Stuart McCullochf3173222012-06-07 21:57:32 +000034 private final DiffPluginImpl differ = new DiffPluginImpl();
35 private Pattern xdoNotCopy = null;
36 private static final int SPLIT_MERGE_LAST = 1;
37 private static final int SPLIT_MERGE_FIRST = 2;
38 private static final int SPLIT_ERROR = 3;
39 private static final int SPLIT_FIRST = 4;
40 private static final int SPLIT_DEFAULT = 0;
41 private final List<File> sourcePath = new ArrayList<File>();
42 private final Make make = new Make(this);
43
44 public Builder(Processor parent) {
45 super(parent);
46 }
47
Stuart McCulloch4482c702012-06-15 13:27:53 +000048 public Builder() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000049
50 public Jar build() throws Exception {
51 trace("build");
52 init();
53 if (isTrue(getProperty(NOBUNDLES)))
54 return null;
55
56 if (getProperty(CONDUIT) != null)
Stuart McCulloch4482c702012-06-15 13:27:53 +000057 error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
Stuart McCullochf3173222012-06-07 21:57:32 +000058
59 Jar dot = new Jar("dot");
60 try {
61 long modified = Long.parseLong(getProperty("base.modified"));
62 dot.updateModified(modified, "Base modified");
63 }
64 catch (Exception e) {
65 // Ignore
66 }
67 setJar(dot);
68
69 doExpand(dot);
70 doIncludeResources(dot);
71 doWab(dot);
72
Stuart McCulloch4482c702012-06-15 13:27:53 +000073
Stuart McCullochf3173222012-06-07 21:57:32 +000074 // Check if we override the calculation of the
75 // manifest. We still need to calculated it because
76 // we need to have analyzed the classpath.
77
78 Manifest manifest = calcManifest();
79
80 String mf = getProperty(MANIFEST);
81 if (mf != null) {
82 File mff = getFile(mf);
83 if (mff.isFile()) {
84 try {
85 InputStream in = new FileInputStream(mff);
86 manifest = new Manifest(in);
87 in.close();
88 }
89 catch (Exception e) {
90 error(MANIFEST + " while reading manifest file", e);
91 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000092 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +000093 error(MANIFEST + ", no such file " + mf);
94 }
95 }
96
97 if (getProperty(NOMANIFEST) == null)
98 dot.setManifest(manifest);
99 else
100 dot.setDoNotTouchManifest();
101
102 // This must happen after we analyzed so
103 // we know what it is on the classpath
104 addSources(dot);
105
106 if (getProperty(POM) != null)
107 dot.putResource("pom.xml", new PomResource(dot.getManifest()));
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000108
Stuart McCullochf3173222012-06-07 21:57:32 +0000109 if (!isNoBundle())
110 doVerify(dot);
111
112 if (dot.getResources().isEmpty())
113 warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
114 getBsn());
115
116 dot.updateModified(lastModified(), "Last Modified Processor");
117 dot.setName(getBsn());
118
119 sign(dot);
Stuart McCullochf3173222012-06-07 21:57:32 +0000120 doSaveManifest(dot);
121
122 doDiff(dot); // check if need to diff this bundle
123 doBaseline(dot); // check for a baseline
124 return dot;
125 }
126
Stuart McCulloch4482c702012-06-15 13:27:53 +0000127
128 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000129 * Check if we need to calculate any checksums.
130 *
131 * @param dot
132 * @throws Exception
133 */
134 private void doDigests(Jar dot) throws Exception {
135 Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
136 if (ps.isEmpty())
137 return;
138 trace("digests %s", ps);
139 String[] digests = ps.keySet().toArray(new String[ps.size()]);
140 dot.calcChecksums(digests);
141 }
142
143 /**
144 * Allow any local initialization by subclasses before we build.
145 */
146 public void init() throws Exception {
147 begin();
148 doRequireBnd();
149
150 // Check if we have sensible setup
151
152 if (getClasspath().size() == 0
153 && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
154 warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
155
156 }
157
158 /**
159 * Turn this normal bundle in a web and add any resources.
160 *
161 * @throws Exception
162 */
163 private Jar doWab(Jar dot) throws Exception {
164 String wab = getProperty(WAB);
165 String wablib = getProperty(WABLIB);
166 if (wab == null && wablib == null)
167 return dot;
168
169 trace("wab %s %s", wab, wablib);
170 setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
171
172 Set<String> paths = new HashSet<String>(dot.getResources().keySet());
173
174 for (String path : paths) {
175 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
176 trace("wab: moving: %s", path);
177 dot.rename(path, "WEB-INF/classes/" + path);
178 }
179 }
180
181 Parameters clauses = parseHeader(getProperty(WABLIB));
182 for (String key : clauses.keySet()) {
183 File f = getFile(key);
184 addWabLib(dot, f);
185 }
186 doIncludeResource(dot, wab);
187 return dot;
188 }
189
190 /**
191 * Add a wab lib to the jar.
192 *
193 * @param f
194 */
195 private void addWabLib(Jar dot, File f) throws Exception {
196 if (f.exists()) {
197 Jar jar = new Jar(f);
198 jar.setDoNotTouchManifest();
199 addClose(jar);
200 String path = "WEB-INF/lib/" + f.getName();
201 dot.putResource(path, new JarResource(jar));
202 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
203
204 Manifest m = jar.getManifest();
205 String cp = m.getMainAttributes().getValue("Class-Path");
206 if (cp != null) {
207 Collection<String> parts = split(cp, ",");
208 for (String part : parts) {
209 File sub = getFile(f.getParentFile(), part);
210 if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000211 warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
212 f);
213 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000214 addWabLib(dot, sub);
215 }
216 }
217 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000218 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000219 error("WAB lib does not exist %s", f);
220 }
221 }
222
223 /**
224 * Get the manifest and write it out separately if -savemanifest is set
225 *
226 * @param dot
227 */
228 private void doSaveManifest(Jar dot) throws Exception {
229 String output = getProperty(SAVEMANIFEST);
230 if (output == null)
231 return;
232
233 File f = getFile(output);
234 if (f.isDirectory()) {
235 f = new File(f, "MANIFEST.MF");
236 }
237 f.delete();
238 f.getParentFile().mkdirs();
239 OutputStream out = new FileOutputStream(f);
240 try {
241 Jar.writeManifest(dot.getManifest(), out);
242 }
243 finally {
244 out.close();
245 }
246 changedFile(f);
247 }
248
Stuart McCulloch669423b2012-06-26 16:34:24 +0000249 protected void changedFile(@SuppressWarnings("unused") File f) {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000250
251 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000252 * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
253 * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
Stuart McCullochf3173222012-06-07 21:57:32 +0000254 *
255 * @return
256 */
257
Stuart McCulloch669423b2012-06-26 16:34:24 +0000258 void sign(@SuppressWarnings("unused") Jar jar) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000259 String signing = getProperty("-sign");
260 if (signing == null)
261 return;
262
263 trace("Signing %s, with %s", getBsn(), signing);
264 List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
265
266 Parameters infos = parseHeader(signing);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000267 for (Entry<String,Attrs> entry : infos.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000268 for (SignerPlugin signer : signers) {
269 signer.sign(this, entry.getKey());
270 }
271 }
272 }
273
274 public boolean hasSources() {
275 return isTrue(getProperty(SOURCES));
276 }
277
278 /**
279 * Answer extra packages. In this case we implement conditional package. Any
280 */
281 protected Jar getExtra() throws Exception {
282 Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
283 if (conditionals.isEmpty())
284 return null;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000285 trace("do Conditional Package %s", conditionals);
Stuart McCullochf3173222012-06-07 21:57:32 +0000286 Instructions instructions = new Instructions(conditionals);
287
288 Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
289 referred.removeAll(getContained().keySet());
290
291 Jar jar = new Jar("conditional-import");
292 addClose(jar);
293 for (PackageRef pref : referred) {
294 for (Jar cpe : getClasspath()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000295 Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
Stuart McCullochf3173222012-06-07 21:57:32 +0000296 if (map != null) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000297 copy(jar, cpe, pref.getPath(), false);
298// Now use copy so that bnd.info is processed, next line should be
299// removed in the future TODO
300// jar.addDirectory(map, false);
Stuart McCullochf3173222012-06-07 21:57:32 +0000301 break;
302 }
303 }
304 }
305 if (jar.getDirectories().size() == 0)
306 return null;
307 return jar;
308 }
309
310 /**
311 * Intercept the call to analyze and cleanup versions after we have analyzed
312 * the setup. We do not want to cleanup if we are going to verify.
313 */
314
315 public void analyze() throws Exception {
316 super.analyze();
317 cleanupVersion(getImports(), null);
318 cleanupVersion(getExports(), getVersion());
319 String version = getProperty(BUNDLE_VERSION);
320 if (version != null) {
321 version = cleanupVersion(version);
322 if (version.endsWith(".SNAPSHOT")) {
323 version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
324 }
325 setProperty(BUNDLE_VERSION, version);
326 }
327 }
328
329 public void cleanupVersion(Packages packages, String defaultVersion) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000330 for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000331 Attrs attributes = entry.getValue();
332 String v = attributes.get(Constants.VERSION_ATTRIBUTE);
333 if (v == null && defaultVersion != null) {
334 if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
335 v = defaultVersion;
336 if (isPedantic())
337 warning("Used bundle version %s for exported package %s", v, entry.getKey());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000338 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000339 if (isPedantic())
340 warning("No export version for exported package %s", entry.getKey());
341 }
342 }
343 if (v != null)
344 attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
345 }
346 }
347
348 /**
349 *
350 */
351 private void addSources(Jar dot) {
352 if (!hasSources())
353 return;
354
355 Set<PackageRef> packages = Create.set();
356
357 for (TypeRef typeRef : getClassspace().keySet()) {
358 PackageRef packageRef = typeRef.getPackageRef();
359 String sourcePath = typeRef.getSourcePath();
360 String packagePath = packageRef.getPath();
361
362 boolean found = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000363 String[] fixed = {
364 "packageinfo", "package.html", "module-info.java", "package-info.java"
365 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000366
367 for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
368 File root = i.next();
369
370 // TODO should use bcp?
371
372 File f = getFile(root, sourcePath);
373 if (f.exists()) {
374 found = true;
375 if (!packages.contains(packageRef)) {
376 packages.add(packageRef);
377 File bdir = getFile(root, packagePath);
378 for (int j = 0; j < fixed.length; j++) {
379 File ff = getFile(bdir, fixed[j]);
380 if (ff.isFile()) {
381 String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
382 dot.putResource(name, new FileResource(ff));
383 }
384 }
385 }
386 if (packageRef.isDefaultPackage())
387 System.err.println("Duh?");
388 dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
389 }
390 }
391 if (!found) {
392 for (Jar jar : getClasspath()) {
393 Resource resource = jar.getResource(sourcePath);
394 if (resource != null) {
395 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000396 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000397 resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
398 if (resource != null) {
399 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
400 }
401 }
402 }
403 }
404 if (getSourcePath().isEmpty())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000405 warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
Stuart McCullochf3173222012-06-07 21:57:32 +0000406 // TODO copy from the jars where they came from
407 }
408 }
409
410 boolean firstUse = true;
411 private Tree tree;
412
413 public Collection<File> getSourcePath() {
414 if (firstUse) {
415 firstUse = false;
416 String sp = getProperty(SOURCEPATH);
417 if (sp != null) {
418 Parameters map = parseHeader(sp);
419 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
420 String file = i.next();
421 if (!isDuplicate(file)) {
422 File f = getFile(file);
423 if (!f.isDirectory()) {
424 error("Adding a sourcepath that is not a directory: " + f);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000425 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000426 sourcePath.add(f);
427 }
428 }
429 }
430 }
431 }
432 return sourcePath;
433 }
434
Stuart McCulloch669423b2012-06-26 16:34:24 +0000435 private void doVerify(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000436 Verifier verifier = new Verifier(this);
437 // Give the verifier the benefit of our analysis
438 // prevents parsing the files twice
439 verifier.verify();
440 getInfo(verifier);
441 }
442
Stuart McCulloch4482c702012-06-15 13:27:53 +0000443 private void doExpand(Jar dot) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000444
445 // Build an index of the class path that we can then
446 // use destructively
Stuart McCulloch4482c702012-06-15 13:27:53 +0000447 MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000448 for (Jar srce : getClasspath()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000449 for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000450 if (e.getValue() != null)
451 packages.add(e.getKey(), srce);
452 }
453 }
454
455 Parameters privatePackages = getPrivatePackage();
456 if (isTrue(getProperty(Constants.UNDERTEST))) {
457 String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
458 privatePackages.putAll(parseHeader(h));
459 }
460
461 if (!privatePackages.isEmpty()) {
462 Instructions privateFilter = new Instructions(privatePackages);
463 Set<Instruction> unused = doExpand(dot, packages, privateFilter);
464
465 if (!unused.isEmpty()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000466 warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
Stuart McCullochf3173222012-06-07 21:57:32 +0000467 }
468 }
469
470 Parameters exportedPackage = getExportPackage();
471 if (!exportedPackage.isEmpty()) {
472 Instructions exportedFilter = new Instructions(exportedPackage);
473
474 // We ignore unused instructions for exports, they should show
475 // up as errors during analysis. Otherwise any overlapping
476 // packages with the private packages should show up as
477 // unused
478
479 doExpand(dot, packages, exportedFilter);
480 }
481 }
482
483 /**
484 * Destructively filter the packages from the build up index. This index is
485 * used by the Export Package as well as the Private Package
486 *
487 * @param jar
488 * @param name
489 * @param instructions
490 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000491 private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000492 Set<Instruction> unused = Create.set();
493
Stuart McCulloch4482c702012-06-15 13:27:53 +0000494 for (Entry<Instruction,Attrs> e : filter.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000495 Instruction instruction = e.getKey();
496 if (instruction.isDuplicate())
497 continue;
498
499 Attrs directives = e.getValue();
500
501 // We can optionally filter on the
502 // source of the package. We assume
503 // they all match but this can be overridden
504 // on the instruction
505 Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
506
507 boolean used = false;
508
Stuart McCulloch4482c702012-06-15 13:27:53 +0000509 for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
510 Entry<String,List<Jar>> p = entry.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000511
512 String directory = p.getKey();
513 PackageRef packageRef = getPackageRef(directory);
514
515 // Skip * and meta data, we're talking packages!
516 if (packageRef.isMetaData() && instruction.isAny())
517 continue;
518
519 if (!instruction.matches(packageRef.getFQN()))
520 continue;
521
522 // Ensure it is never matched again
523 entry.remove();
524
525 // ! effectively removes it from consideration by others (this
526 // includes exports)
527 if (instruction.isNegated())
528 continue;
529
530 // Do the from: directive, filters on the JAR type
531 List<Jar> providers = filterFrom(from, p.getValue());
532 if (providers.isEmpty())
533 continue;
534
535 int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
536 copyPackage(jar, providers, directory, splitStrategy);
537
538 used = true;
539 }
540
541 if (!used && !isTrue(directives.get("optional:")))
542 unused.add(instruction);
543 }
544 return unused;
545 }
546
547 /**
548 * @param from
549 * @return
550 */
551 private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
552 if (from.isAny())
553 return providers;
554
555 List<Jar> np = new ArrayList<Jar>();
556 for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
557 Jar j = i.next();
558 if (from.matches(j.getName())) {
559 np.add(j);
560 }
561 }
562 return np;
563 }
564
565 /**
566 * Copy the package from the providers based on the split package strategy.
567 *
568 * @param dest
569 * @param providers
570 * @param directory
571 * @param splitStrategy
572 */
573 private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
574 switch (splitStrategy) {
575 case SPLIT_MERGE_LAST :
576 for (Jar srce : providers) {
577 copy(dest, srce, path, true);
578 }
579 break;
580
581 case SPLIT_MERGE_FIRST :
582 for (Jar srce : providers) {
583 copy(dest, srce, path, false);
584 }
585 break;
586
587 case SPLIT_ERROR :
588 error(diagnostic(path, providers));
589 break;
590
591 case SPLIT_FIRST :
592 copy(dest, providers.get(0), path, false);
593 break;
594
595 default :
596 if (providers.size() > 1)
597 warning("%s", diagnostic(path, providers));
598 for (Jar srce : providers) {
599 copy(dest, srce, path, false);
600 }
601 break;
602 }
603 }
604
605 /**
606 * Cop
607 *
608 * @param dest
609 * @param srce
610 * @param path
611 * @param overwriteResource
612 */
613 private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000614 trace("copy d=" + dest + " s=" + srce +" p="+ path);
Stuart McCullochf3173222012-06-07 21:57:32 +0000615 dest.copy(srce, path, overwrite);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000616
617 // bnd.info sources must be preprocessed
618 String bndInfoPath = path + "/bnd.info";
619 Resource r = dest.getResource(bndInfoPath);
620 if ( r != null && !(r instanceof PreprocessResource)) {
621 trace("preprocessing bnd.info");
622 PreprocessResource pp = new PreprocessResource(this, r);
623 dest.putResource(bndInfoPath, pp);
624 }
625
Stuart McCullochf3173222012-06-07 21:57:32 +0000626 if (hasSources()) {
627 String srcPath = "OSGI-OPT/src/" + path;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000628 Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
Stuart McCullochf3173222012-06-07 21:57:32 +0000629 if (srcContents != null) {
630 dest.addDirectory(srcContents, overwrite);
631 }
632 }
633 }
634
635 /**
636 * Analyze the classpath for a split package
637 *
638 * @param pack
639 * @param classpath
640 * @param source
641 * @return
642 */
643 private String diagnostic(String pack, List<Jar> culprits) {
644 // Default is like merge-first, but with a warning
645 return "Split package, multiple jars provide the same package:"
646 + pack
647 + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
648 + "Package found in " + culprits + "\n" //
649 + "Class path " + getClasspath();
650 }
651
652 private int getSplitStrategy(String type) {
653 if (type == null)
654 return SPLIT_DEFAULT;
655
656 if (type.equals("merge-last"))
657 return SPLIT_MERGE_LAST;
658
659 if (type.equals("merge-first"))
660 return SPLIT_MERGE_FIRST;
661
662 if (type.equals("error"))
663 return SPLIT_ERROR;
664
665 if (type.equals("first"))
666 return SPLIT_FIRST;
667
668 error("Invalid strategy for split-package: " + type);
669 return SPLIT_DEFAULT;
670 }
671
672 /**
673 * Matches the instructions against a package.
674 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000675 * @param instructions
676 * The list of instructions
677 * @param pack
678 * The name of the package
679 * @param unused
680 * The total list of patterns, matched patterns are removed
681 * @param source
682 * The name of the source container, can be filtered upon with
683 * the from: directive.
Stuart McCullochf3173222012-06-07 21:57:32 +0000684 * @return
685 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000686 private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
687 for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000688 Instruction pattern = entry.getKey();
689
690 // It is possible to filter on the source of the
691 // package with the from: directive. This is an
692 // instruction that must match the name of the
693 // source class path entry.
694
695 String from = entry.getValue().get(FROM_DIRECTIVE);
696 if (from != null) {
697 Instruction f = new Instruction(from);
698 if (!f.matches(source) || f.isNegated())
699 continue;
700 }
701
702 // Now do the normal
703 // matching
704 if (pattern.matches(pack)) {
705 if (unused != null)
706 unused.remove(pattern);
707 return pattern;
708 }
709 }
710 return null;
711 }
712
713 /**
714 * Parse the Bundle-Includes header. Files in the bundles Include header are
715 * included in the jar. The source can be a directory or a file.
716 *
717 * @throws IOException
718 * @throws FileNotFoundException
719 */
720 private void doIncludeResources(Jar jar) throws Exception {
721 String includes = getProperty("Bundle-Includes");
722 if (includes == null) {
723 includes = getProperty(INCLUDERESOURCE);
724 if (includes == null || includes.length() == 0)
725 includes = getProperty("Include-Resource");
Stuart McCulloch4482c702012-06-15 13:27:53 +0000726 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000727 warning("Please use -includeresource instead of Bundle-Includes");
728
729 doIncludeResource(jar, includes);
730
731 }
732
733 private void doIncludeResource(Jar jar, String includes) throws Exception {
734 Parameters clauses = parseHeader(includes);
735 doIncludeResource(jar, clauses);
736 }
737
Stuart McCulloch4482c702012-06-15 13:27:53 +0000738 private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
739 for (Entry<String,Attrs> entry : clauses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000740 doIncludeResource(jar, entry.getKey(), entry.getValue());
741 }
742 }
743
Stuart McCulloch4482c702012-06-15 13:27:53 +0000744 private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
745 Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000746
747 boolean preprocess = false;
748 boolean absentIsOk = false;
749
750 if (name.startsWith("{") && name.endsWith("}")) {
751 preprocess = true;
752 name = name.substring(1, name.length() - 1).trim();
753 }
754
755 String parts[] = name.split("\\s*=\\s*");
756 String source = parts[0];
757 String destination = parts[0];
758 if (parts.length == 2)
759 source = parts[1];
760
761 if (source.startsWith("-")) {
762 source = source.substring(1);
763 absentIsOk = true;
764 }
765
766 if (source.startsWith("@")) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000767 extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
768 } else if (extra.containsKey("cmd")) {
769 doCommand(jar, source, destination, extra, preprocess, absentIsOk);
770 } else if (extra.containsKey("literal")) {
771 String literal = extra.get("literal");
772 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
773 String x = extra.get("extra");
774 if (x != null)
775 r.setExtra(x);
776 jar.putResource(name, r);
777 } else {
778 File sourceFile;
779 String destinationPath;
780
781 sourceFile = getFile(source);
782 if (parts.length == 1) {
783 // Directories should be copied to the root
784 // but files to their file name ...
785 if (sourceFile.isDirectory())
786 destinationPath = "";
787 else
788 destinationPath = sourceFile.getName();
789 } else {
790 destinationPath = parts[0];
Stuart McCullochf3173222012-06-07 21:57:32 +0000791 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000792 // Handle directories
793 if (sourceFile.isDirectory()) {
794 destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
795 return;
796 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000797
Stuart McCulloch4482c702012-06-15 13:27:53 +0000798 // destinationPath = checkDestinationPath(destinationPath);
Stuart McCullochf3173222012-06-07 21:57:32 +0000799
Stuart McCulloch4482c702012-06-15 13:27:53 +0000800 if (!sourceFile.exists()) {
801 if (absentIsOk)
802 return;
Stuart McCullochf3173222012-06-07 21:57:32 +0000803
Stuart McCulloch4482c702012-06-15 13:27:53 +0000804 noSuchFile(jar, name, extra, source, destinationPath);
805 } else
806 copy(jar, destinationPath, sourceFile, preprocess, extra);
807 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000808 }
809
810 /**
811 * It is possible in Include-Resource to use a system command that generates
812 * the contents, this is indicated with {@code cmd} attribute. The command
813 * can be repeated for a number of source files with the {@code for}
814 * attribute which indicates a list of repetitions, often down with the
815 * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
816 * repetition will repeat the given command for each item. The @} macro can
817 * be used to replace the current item. If no {@code for} is given, the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000818 * source is used as the only item. If the destination contains a macro,
819 * each iteration will create a new file, otherwise the destination name is
820 * used. The execution of the command is delayed until the JAR is actually
821 * written to the file system for performance reasons.
Stuart McCullochf3173222012-06-07 21:57:32 +0000822 *
823 * @param jar
824 * @param source
825 * @param destination
826 * @param extra
827 * @param preprocess
828 * @param absentIsOk
829 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000830 private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
831 boolean absentIsOk) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000832 String repeat = extra.get("for"); // TODO constant
833 if (repeat == null)
834 repeat = source;
835
836 Collection<String> requires = split(extra.get("requires"));
837 long lastModified = 0;
838 for (String required : requires) {
839 File file = getFile(required);
840 if (!file.isFile()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000841 error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
842 file.getAbsoluteFile());
843 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000844 lastModified = Math.max(lastModified, file.lastModified());
845 }
846
847 String cmd = extra.get("cmd");
848
849 Collection<String> items = Processor.split(repeat);
850
851 CombinedResource cr = null;
852
853 if (!destination.contains("${@}")) {
854 cr = new CombinedResource();
855 }
856 trace("last modified requires %s", lastModified);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000857
Stuart McCullochf3173222012-06-07 21:57:32 +0000858 for (String item : items) {
859 setProperty("@", item);
860 try {
861 String path = getReplacer().process(destination);
862 String command = getReplacer().process(cmd);
863 File file = getFile(item);
864
865 Resource r = new CommandResource(command, this, Math.max(lastModified,
Stuart McCulloch4482c702012-06-15 13:27:53 +0000866 file.exists() ? file.lastModified() : 0L));
Stuart McCullochf3173222012-06-07 21:57:32 +0000867
868 if (preprocess)
869 r = new PreprocessResource(this, r);
870
871 if (cr == null)
872 jar.putResource(path, r);
873 else
874 cr.addResource(r);
875 }
876 finally {
877 unsetProperty("@");
878 }
879 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000880
Stuart McCullochf3173222012-06-07 21:57:32 +0000881 // Add last so the correct modification date is used
882 // to update the modified time.
Stuart McCulloch4482c702012-06-15 13:27:53 +0000883 if (cr != null)
Stuart McCullochf3173222012-06-07 21:57:32 +0000884 jar.putResource(destination, cr);
885 }
886
Stuart McCulloch4482c702012-06-15 13:27:53 +0000887 private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
888 String destinationPath) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000889 String filter = extra.get("filter:");
890 boolean flatten = isTrue(extra.get("flatten:"));
891 boolean recursive = true;
892 String directive = extra.get("recursive:");
893 if (directive != null) {
894 recursive = isTrue(directive);
895 }
896
897 Instruction.Filter iFilter = null;
898 if (filter != null) {
899 iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000900 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000901 iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
902 }
903
Stuart McCulloch4482c702012-06-15 13:27:53 +0000904 Map<String,File> files = newMap();
Stuart McCullochf3173222012-06-07 21:57:32 +0000905 resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
906
Stuart McCulloch4482c702012-06-15 13:27:53 +0000907 for (Map.Entry<String,File> entry : files.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000908 copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
909 }
910 return destinationPath;
911 }
912
Stuart McCulloch4482c702012-06-15 13:27:53 +0000913 private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
914 boolean flatten) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000915
916 if (doNotCopy(dir.getName())) {
917 return;
918 }
919
920 File[] fs = dir.listFiles(filter);
921 for (File file : fs) {
922 if (file.isDirectory()) {
923 if (recursive) {
924 String nextPath;
925 if (flatten)
926 nextPath = path;
927 else
928 nextPath = appendPath(path, file.getName());
929
930 resolveFiles(file, filter, recursive, nextPath, files, flatten);
931 }
932 // Directories are ignored otherwise
Stuart McCulloch4482c702012-06-15 13:27:53 +0000933 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000934 String p = appendPath(path, file.getName());
935 if (files.containsKey(p))
936 warning("Include-Resource overwrites entry %s from file %s", p, file);
937 files.put(p, file);
938 }
939 }
940 }
941
Stuart McCulloch669423b2012-06-26 16:34:24 +0000942 private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000943 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000944 Jar src = getJarFromName(source, "Include-Resource " + source);
945 if (src != null) {
946 // Do not touch the manifest so this also
947 // works for signed files.
948 src.setDoNotTouchManifest();
949 JarResource jarResource = new JarResource(src);
950 jar.putResource(destinationPath, jarResource);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000951 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000952 Resource lastChance = make.process(source);
953 if (lastChance != null) {
954 String x = extra.get("extra");
955 if (x != null)
956 lastChance.setExtra(x);
957 jar.putResource(destinationPath, lastChance);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000958 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000959 error("Input file does not exist: " + source);
960 }
961 }
962
963 /**
964 * Extra resources from a Jar and add them to the given jar. The clause is
965 * the
966 *
967 * @param jar
968 * @param clauses
969 * @param i
970 * @throws ZipException
971 * @throws IOException
972 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000973 private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
974 IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000975 // Inline all resources and classes from another jar
976 // optionally appended with a modified regular expression
977 // like @zip.jar!/META-INF/MANIFEST.MF
978 int n = source.lastIndexOf("!/");
979 Instruction instr = null;
980 if (n > 0) {
981 instr = new Instruction(source.substring(n + 2));
982 source = source.substring(0, n);
983 }
984
985 // Pattern filter = null;
986 // if (n > 0) {
987 // String fstring = source.substring(n + 2);
988 // source = source.substring(0, n);
989 // filter = wildcard(fstring);
990 // }
991 Jar sub = getJarFromName(source, "extract from jar");
992 if (sub == null) {
993 if (absentIsOk)
994 return;
995
996 error("Can not find JAR file " + source);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000997 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000998 addAll(jar, sub, instr, destination);
999 }
1000 }
1001
1002 /**
1003 * Add all the resources in the given jar that match the given filter.
1004 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001005 * @param sub
1006 * the jar
1007 * @param filter
1008 * a pattern that should match the resoures in sub to be added
Stuart McCullochf3173222012-06-07 21:57:32 +00001009 */
1010 public boolean addAll(Jar to, Jar sub, Instruction filter) {
1011 return addAll(to, sub, filter, "");
1012 }
1013
1014 /**
1015 * Add all the resources in the given jar that match the given filter.
1016 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001017 * @param sub
1018 * the jar
1019 * @param filter
1020 * a pattern that should match the resoures in sub to be added
Stuart McCullochf3173222012-06-07 21:57:32 +00001021 */
1022 public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
1023 boolean dupl = false;
1024 for (String name : sub.getResources().keySet()) {
1025 if ("META-INF/MANIFEST.MF".equals(name))
1026 continue;
1027
1028 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch4482c702012-06-15 13:27:53 +00001029 dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochf3173222012-06-07 21:57:32 +00001030 }
1031 return dupl;
1032 }
1033
Stuart McCulloch4482c702012-06-15 13:27:53 +00001034 private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001035 if (doNotCopy(from.getName()))
1036 return;
1037
1038 if (from.isDirectory()) {
1039
1040 File files[] = from.listFiles();
1041 for (int i = 0; i < files.length; i++) {
1042 copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
1043 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001044 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001045 if (from.exists()) {
1046 Resource resource = new FileResource(from);
1047 if (preprocess) {
1048 resource = new PreprocessResource(this, resource);
1049 }
1050 String x = extra.get("extra");
1051 if (x != null)
1052 resource.setExtra(x);
1053 if (path.endsWith("/"))
1054 path = path + from.getName();
1055 jar.putResource(path, resource);
1056
1057 if (isTrue(extra.get(LIB_DIRECTIVE))) {
1058 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
1059 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001060 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001061 error("Input file does not exist: " + from);
1062 }
1063 }
1064 }
1065
1066 public void setSourcepath(File[] files) {
1067 for (int i = 0; i < files.length; i++)
1068 addSourcepath(files[i]);
1069 }
1070
1071 public void addSourcepath(File cp) {
1072 if (!cp.exists())
1073 warning("File on sourcepath that does not exist: " + cp);
1074
1075 sourcePath.add(cp);
1076 }
1077
1078 public void close() {
1079 super.close();
1080 }
1081
1082 /**
1083 * Build Multiple jars. If the -sub command is set, we filter the file with
1084 * the given patterns.
1085 *
1086 * @return
1087 * @throws Exception
1088 */
1089 public Jar[] builds() throws Exception {
1090 begin();
1091
1092 // Are we acting as a conduit for another JAR?
1093 String conduit = getProperty(CONDUIT);
1094 if (conduit != null) {
1095 Parameters map = parseHeader(conduit);
1096 Jar[] result = new Jar[map.size()];
1097 int n = 0;
1098 for (String file : map.keySet()) {
1099 Jar c = new Jar(getFile(file));
1100 addClose(c);
1101 String name = map.get(file).get("name");
1102 if (name != null)
1103 c.setName(name);
1104
1105 result[n++] = c;
1106 }
1107 return result;
1108 }
1109
1110 List<Jar> result = new ArrayList<Jar>();
1111 List<Builder> builders;
1112
1113 builders = getSubBuilders();
1114
1115 for (Builder builder : builders) {
1116 try {
1117 Jar jar = builder.build();
1118 jar.setName(builder.getBsn());
1119 result.add(jar);
1120 }
1121 catch (Exception e) {
1122 e.printStackTrace();
1123 error("Sub Building " + builder.getBsn(), e);
1124 }
1125 if (builder != this)
1126 getInfo(builder, builder.getBsn() + ": ");
1127 }
1128 return result.toArray(new Jar[result.size()]);
1129 }
1130
1131 /**
1132 * Answer a list of builders that represent this file or a list of files
1133 * specified in -sub. This list can be empty. These builders represents to
1134 * be created artifacts and are each scoped to such an artifacts. The
1135 * builders can be used to build the bundles or they can be used to find out
1136 * information about the to be generated bundles.
1137 *
1138 * @return List of 0..n builders representing artifacts.
1139 * @throws Exception
1140 */
1141 public List<Builder> getSubBuilders() throws Exception {
1142 String sub = getProperty(SUB);
1143 if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
1144 return Arrays.asList(this);
1145
1146 List<Builder> builders = new ArrayList<Builder>();
1147 if (isTrue(getProperty(NOBUNDLES)))
1148 return builders;
1149
1150 Parameters subsMap = parseHeader(sub);
1151 for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
1152 File file = getFile(i.next());
1153 if (file.isFile()) {
1154 builders.add(getSubBuilder(file));
1155 i.remove();
1156 }
1157 }
1158
1159 Instructions instructions = new Instructions(subsMap);
1160
1161 List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
1162
1163 nextFile: while (members.size() > 0) {
1164
1165 File file = members.remove(0);
1166
1167 // Check if the file is one of our parents
1168 Processor p = this;
1169 while (p != null) {
1170 if (file.equals(p.getPropertiesFile()))
1171 continue nextFile;
1172 p = p.getParent();
1173 }
1174
1175 for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
1176
1177 Instruction instruction = i.next();
1178 if (instruction.matches(file.getName())) {
1179
1180 if (!instruction.isNegated()) {
1181 builders.add(getSubBuilder(file));
1182 }
1183
1184 // Because we matched (even though we could be negated)
1185 // we skip any remaining searches
1186 continue nextFile;
1187 }
1188 }
1189 }
1190 return builders;
1191 }
1192
1193 public Builder getSubBuilder(File file) throws Exception {
1194 Builder builder = getSubBuilder();
1195 if (builder != null) {
1196 builder.setProperties(file);
1197 addClose(builder);
1198 }
1199 return builder;
1200 }
1201
1202 public Builder getSubBuilder() throws Exception {
1203 Builder builder = new Builder(this);
1204 builder.setBase(getBase());
1205
1206 for (Jar file : getClasspath()) {
1207 builder.addClasspath(file);
1208 }
1209
1210 return builder;
1211 }
1212
1213 /**
1214 * A macro to convert a maven version to an OSGi version
1215 */
1216
1217 public String _maven_version(String args[]) {
1218 if (args.length > 2)
1219 error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
Stuart McCulloch4482c702012-06-15 13:27:53 +00001220 else if (args.length < 2)
1221 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1222 else {
1223 return cleanupVersion(args[1]);
1224 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001225 return null;
1226 }
1227
Stuart McCulloch4482c702012-06-15 13:27:53 +00001228 public String _permissions(String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001229 StringBuilder sb = new StringBuilder();
1230
1231 for (String arg : args) {
1232 if ("packages".equals(arg) || "all".equals(arg)) {
1233 for (PackageRef imp : getImports().keySet()) {
1234 if (!imp.isJava()) {
1235 sb.append("(org.osgi.framework.PackagePermission \"");
1236 sb.append(imp);
1237 sb.append("\" \"import\")\r\n");
1238 }
1239 }
1240 for (PackageRef exp : getExports().keySet()) {
1241 sb.append("(org.osgi.framework.PackagePermission \"");
1242 sb.append(exp);
1243 sb.append("\" \"export\")\r\n");
1244 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001245 } else if ("admin".equals(arg) || "all".equals(arg)) {
1246 sb.append("(org.osgi.framework.AdminPermission)");
1247 } else if ("permissions".equals(arg))
1248 ;
Stuart McCullochf3173222012-06-07 21:57:32 +00001249 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001250 error("Invalid option in ${permissions}: %s", arg);
Stuart McCullochf3173222012-06-07 21:57:32 +00001251 }
1252 return sb.toString();
1253 }
1254
1255 /**
1256 *
1257 */
1258 public void removeBundleSpecificHeaders() {
1259 Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
1260 setForceLocal(set);
1261 }
1262
1263 /**
1264 * Check if the given resource is in scope of this bundle. That is, it
1265 * checks if the Include-Resource includes this resource or if it is a class
Stuart McCulloch4482c702012-06-15 13:27:53 +00001266 * file it is on the class path and the Export-Package or Private-Package
Stuart McCullochf3173222012-06-07 21:57:32 +00001267 * include this resource.
1268 *
1269 * @param f
1270 * @return
1271 */
1272 public boolean isInScope(Collection<File> resources) throws Exception {
1273 Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
1274 clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
1275 if (isTrue(getProperty(Constants.UNDERTEST))) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001276 clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
Stuart McCullochf3173222012-06-07 21:57:32 +00001277 }
1278
1279 Collection<String> ir = getIncludedResourcePrefixes();
1280
1281 Instructions instructions = new Instructions(clauses);
1282
1283 for (File r : resources) {
1284 String cpEntry = getClasspathEntrySuffix(r);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001285
Stuart McCullochf3173222012-06-07 21:57:32 +00001286 if (cpEntry != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001287
1288 if (cpEntry.equals("")) // Meaning we actually have a CPE
1289 return true;
1290
Stuart McCullochf3173222012-06-07 21:57:32 +00001291 String pack = Descriptors.getPackage(cpEntry);
1292 Instruction i = matches(instructions, pack, null, r.getName());
1293 if (i != null)
1294 return !i.isNegated();
1295 }
1296
1297 // Check if this resource starts with one of the I-C header
1298 // paths.
1299 String path = r.getAbsolutePath();
1300 for (String p : ir) {
1301 if (path.startsWith(p))
1302 return true;
1303 }
1304 }
1305 return false;
1306 }
1307
1308 /**
1309 * Extra the paths for the directories and files that are used in the
1310 * Include-Resource header.
1311 *
1312 * @return
1313 */
1314 private Collection<String> getIncludedResourcePrefixes() {
1315 List<String> prefixes = new ArrayList<String>();
1316 Parameters includeResource = getIncludeResource();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001317 for (Entry<String,Attrs> p : includeResource.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001318 if (p.getValue().containsKey("literal"))
1319 continue;
1320
1321 Matcher m = IR_PATTERN.matcher(p.getKey());
1322 if (m.matches()) {
1323 File f = getFile(m.group(1));
1324 prefixes.add(f.getAbsolutePath());
1325 }
1326 }
1327 return prefixes;
1328 }
1329
1330 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001331 * Answer the string of the resource that it has in the container. It is
1332 * possible that the resource is a classpath entry. In that case an empty
1333 * string is returned.
Stuart McCullochf3173222012-06-07 21:57:32 +00001334 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001335 * @param resource
1336 * The resource to look for
1337 * @return A suffix on the classpath or "" if the resource is a class path
1338 * entry
Stuart McCullochf3173222012-06-07 21:57:32 +00001339 * @throws Exception
1340 */
1341 public String getClasspathEntrySuffix(File resource) throws Exception {
1342 for (Jar jar : getClasspath()) {
1343 File source = jar.getSource();
1344 if (source != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001345
Stuart McCullochf3173222012-06-07 21:57:32 +00001346 source = source.getCanonicalFile();
1347 String sourcePath = source.getAbsolutePath();
1348 String resourcePath = resource.getAbsolutePath();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001349 if (sourcePath.equals(resourcePath))
1350 return ""; // Matches a classpath entry
Stuart McCullochf3173222012-06-07 21:57:32 +00001351
1352 if (resourcePath.startsWith(sourcePath)) {
1353 // Make sure that the path name is translated correctly
1354 // i.e. on Windows the \ must be translated to /
1355 String filePath = resourcePath.substring(sourcePath.length() + 1);
1356
1357 return filePath.replace(File.separatorChar, '/');
1358 }
1359 }
1360 }
1361 return null;
1362 }
1363
1364 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001365 * doNotCopy The doNotCopy variable maintains a patter for files that should
1366 * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
1367 * ca be overridden with the {@link Constants#DONOTCOPY} property.
Stuart McCullochf3173222012-06-07 21:57:32 +00001368 */
1369
1370 public boolean doNotCopy(String v) {
1371 return getDoNotCopy().matcher(v).matches();
1372 }
1373
1374 public Pattern getDoNotCopy() {
1375 if (xdoNotCopy == null) {
1376 String string = null;
1377 try {
1378 string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
1379 xdoNotCopy = Pattern.compile(string);
1380 }
1381 catch (Exception e) {
1382 error("Invalid value for %s, value is %s", DONOTCOPY, string);
1383 xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
1384 }
1385 }
1386 return xdoNotCopy;
1387 }
1388
1389 /**
1390 */
1391
1392 static MakeBnd makeBnd = new MakeBnd();
1393 static MakeCopy makeCopy = new MakeCopy();
1394 static ServiceComponent serviceComponent = new ServiceComponent();
1395 static DSAnnotations dsAnnotations = new DSAnnotations();
1396 static MetatypePlugin metatypePlugin = new MetatypePlugin();
1397
1398 @Override
1399 protected void setTypeSpecificPlugins(Set<Object> list) {
1400 list.add(makeBnd);
1401 list.add(makeCopy);
1402 list.add(serviceComponent);
1403 list.add(dsAnnotations);
1404 list.add(metatypePlugin);
1405 super.setTypeSpecificPlugins(list);
1406 }
1407
1408 /**
1409 * Diff this bundle to another bundle for the given packages.
1410 *
1411 * @throws Exception
1412 */
1413
Stuart McCulloch669423b2012-06-26 16:34:24 +00001414 public void doDiff(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001415 Parameters diffs = parseHeader(getProperty("-diff"));
1416 if (diffs.isEmpty())
1417 return;
1418
1419 trace("diff %s", diffs);
1420
1421 if (tree == null)
1422 tree = differ.tree(this);
1423
Stuart McCulloch4482c702012-06-15 13:27:53 +00001424 for (Entry<String,Attrs> entry : diffs.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001425 String path = entry.getKey();
1426 File file = getFile(path);
1427 if (!file.isFile()) {
1428 error("Diffing against %s that is not a file", file);
1429 continue;
1430 }
1431
1432 boolean full = entry.getValue().get("--full") != null;
1433 boolean warning = entry.getValue().get("--warning") != null;
1434
1435 Tree other = differ.tree(file);
1436 Diff api = tree.diff(other).get("<api>");
1437 Instructions instructions = new Instructions(entry.getValue().get("--pack"));
1438
1439 trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
1440 for (Diff p : api.getChildren()) {
1441 String pname = p.getName();
1442 if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
1443 if (p.getDelta() != Delta.UNCHANGED) {
1444
1445 if (!full)
1446 if (warning)
1447 warning("Differ %s", p);
1448 else
1449 error("Differ %s", p);
1450 else {
1451 if (warning)
Stuart McCulloch4482c702012-06-15 13:27:53 +00001452 warning("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochf3173222012-06-07 21:57:32 +00001453 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001454 error("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochf3173222012-06-07 21:57:32 +00001455 show(p, "", warning);
1456 }
1457 }
1458 }
1459 }
1460 }
1461 }
1462
1463 /**
1464 * Show the diff recursively
1465 *
1466 * @param p
1467 * @param i
1468 */
1469 private void show(Diff p, String indent, boolean warning) {
1470 Delta d = p.getDelta();
1471 if (d == Delta.UNCHANGED)
1472 return;
1473
1474 if (warning)
1475 warning("%s%s", indent, p);
1476 else
1477 error("%s%s", indent, p);
1478
1479 indent = indent + " ";
1480 switch (d) {
1481 case CHANGED :
1482 case MAJOR :
1483 case MINOR :
1484 case MICRO :
1485 break;
1486
1487 default :
1488 return;
1489 }
1490 for (Diff c : p.getChildren())
1491 show(c, indent, warning);
1492 }
1493
1494 /**
1495 * Base line against a previous version
1496 *
1497 * @throws Exception
1498 */
1499
1500 private void doBaseline(Jar dot) throws Exception {
1501 Parameters diffs = parseHeader(getProperty("-baseline"));
1502 if (diffs.isEmpty())
1503 return;
1504
1505 System.err.printf("baseline %s%n", diffs);
1506
1507 Baseline baseline = new Baseline(this, differ);
1508
Stuart McCulloch4482c702012-06-15 13:27:53 +00001509 for (Entry<String,Attrs> entry : diffs.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001510 String path = entry.getKey();
1511 File file = getFile(path);
1512 if (!file.isFile()) {
1513 error("Diffing against %s that is not a file", file);
1514 continue;
1515 }
1516 Jar other = new Jar(file);
1517 Set<Info> infos = baseline.baseline(dot, other, null);
1518 for (Info info : infos) {
1519 if (info.mismatch) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001520 error("%s %-50s %-10s %-10s %-10s %-10s %-10s\n", info.mismatch ? '*' : ' ', info.packageName,
1521 info.packageDiff.getDelta(), info.newerVersion, info.olderVersion, info.suggestedVersion,
Stuart McCullochf3173222012-06-07 21:57:32 +00001522 info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
1523 }
1524 }
1525 }
1526 }
1527
1528 public void addSourcepath(Collection<File> sourcepath) {
1529 for (File f : sourcepath) {
1530 addSourcepath(f);
1531 }
1532 }
1533
1534}