blob: b5ed9be0e443ed3f4b9286952d8a7ae10aacd973 [file] [log] [blame]
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochf3173222012-06-07 21:57:32 +00002
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.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000012import aQute.bnd.header.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000013import aQute.bnd.make.*;
14import aQute.bnd.make.component.*;
15import aQute.bnd.make.metatype.*;
16import aQute.bnd.maven.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000017import aQute.bnd.osgi.Descriptors.PackageRef;
18import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochf3173222012-06-07 21:57:32 +000019import aQute.bnd.service.*;
20import aQute.bnd.service.diff.*;
21import aQute.lib.collections.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000022import aQute.libg.generics.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000023
24/**
Stuart McCulloch4482c702012-06-15 13:27:53 +000025 * Include-Resource: ( [name '=' ] file )+ Private-Package: package-decl ( ','
26 * package-decl )* Export-Package: package-decl ( ',' package-decl )*
Stuart McCullochf3173222012-06-07 21:57:32 +000027 * Import-Package: package-decl ( ',' package-decl )*
28 *
29 * @version $Revision$
30 */
31public class Builder extends Analyzer {
Stuart McCulloch4482c702012-06-15 13:27:53 +000032 static Pattern IR_PATTERN = Pattern.compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
Stuart McCullochf3173222012-06-07 21:57:32 +000033 private final DiffPluginImpl differ = new DiffPluginImpl();
34 private Pattern xdoNotCopy = null;
35 private static final int SPLIT_MERGE_LAST = 1;
36 private static final int SPLIT_MERGE_FIRST = 2;
37 private static final int SPLIT_ERROR = 3;
38 private static final int SPLIT_FIRST = 4;
39 private static final int SPLIT_DEFAULT = 0;
40 private final List<File> sourcePath = new ArrayList<File>();
41 private final Make make = new Make(this);
42
43 public Builder(Processor parent) {
44 super(parent);
45 }
46
Stuart McCulloch4482c702012-06-15 13:27:53 +000047 public Builder() {}
Stuart McCullochf3173222012-06-07 21:57:32 +000048
49 public Jar build() throws Exception {
50 trace("build");
51 init();
52 if (isTrue(getProperty(NOBUNDLES)))
53 return null;
54
55 if (getProperty(CONDUIT) != null)
Stuart McCulloch4482c702012-06-15 13:27:53 +000056 error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
Stuart McCullochf3173222012-06-07 21:57:32 +000057
58 Jar dot = new Jar("dot");
59 try {
60 long modified = Long.parseLong(getProperty("base.modified"));
61 dot.updateModified(modified, "Base modified");
62 }
63 catch (Exception e) {
64 // Ignore
65 }
66 setJar(dot);
67
68 doExpand(dot);
69 doIncludeResources(dot);
70 doWab(dot);
71
Stuart McCulloch4482c702012-06-15 13:27:53 +000072
Stuart McCullochf3173222012-06-07 21:57:32 +000073 // Check if we override the calculation of the
74 // manifest. We still need to calculated it because
75 // we need to have analyzed the classpath.
76
77 Manifest manifest = calcManifest();
78
79 String mf = getProperty(MANIFEST);
80 if (mf != null) {
81 File mff = getFile(mf);
82 if (mff.isFile()) {
83 try {
84 InputStream in = new FileInputStream(mff);
85 manifest = new Manifest(in);
86 in.close();
87 }
88 catch (Exception e) {
89 error(MANIFEST + " while reading manifest file", e);
90 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000091 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +000092 error(MANIFEST + ", no such file " + mf);
93 }
94 }
95
96 if (getProperty(NOMANIFEST) == null)
97 dot.setManifest(manifest);
98 else
99 dot.setDoNotTouchManifest();
100
101 // This must happen after we analyzed so
102 // we know what it is on the classpath
103 addSources(dot);
104
105 if (getProperty(POM) != null)
106 dot.putResource("pom.xml", new PomResource(dot.getManifest()));
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000107
Stuart McCullochf3173222012-06-07 21:57:32 +0000108 if (!isNoBundle())
109 doVerify(dot);
110
111 if (dot.getResources().isEmpty())
112 warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
113 getBsn());
114
115 dot.updateModified(lastModified(), "Last Modified Processor");
116 dot.setName(getBsn());
117
Stuart McCullochcd1ddd72012-07-19 13:11:20 +0000118 doDigests(dot);
119
Stuart McCullochf3173222012-06-07 21:57:32 +0000120 sign(dot);
Stuart McCullochf3173222012-06-07 21:57:32 +0000121 doSaveManifest(dot);
122
123 doDiff(dot); // check if need to diff this bundle
124 doBaseline(dot); // check for a baseline
125 return dot;
126 }
127
Stuart McCulloch4482c702012-06-15 13:27:53 +0000128
129 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000130 * Check if we need to calculate any checksums.
131 *
132 * @param dot
133 * @throws Exception
134 */
135 private void doDigests(Jar dot) throws Exception {
136 Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
137 if (ps.isEmpty())
138 return;
139 trace("digests %s", ps);
140 String[] digests = ps.keySet().toArray(new String[ps.size()]);
Stuart McCullochcd1ddd72012-07-19 13:11:20 +0000141 dot.setDigestAlgorithms(digests);
Stuart McCullochf3173222012-06-07 21:57:32 +0000142 }
143
144 /**
145 * Allow any local initialization by subclasses before we build.
146 */
147 public void init() throws Exception {
148 begin();
149 doRequireBnd();
150
151 // Check if we have sensible setup
152
153 if (getClasspath().size() == 0
154 && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
155 warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
156
157 }
158
159 /**
160 * Turn this normal bundle in a web and add any resources.
161 *
162 * @throws Exception
163 */
164 private Jar doWab(Jar dot) throws Exception {
165 String wab = getProperty(WAB);
166 String wablib = getProperty(WABLIB);
167 if (wab == null && wablib == null)
168 return dot;
169
170 trace("wab %s %s", wab, wablib);
171 setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
172
173 Set<String> paths = new HashSet<String>(dot.getResources().keySet());
174
175 for (String path : paths) {
176 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
177 trace("wab: moving: %s", path);
178 dot.rename(path, "WEB-INF/classes/" + path);
179 }
180 }
181
182 Parameters clauses = parseHeader(getProperty(WABLIB));
183 for (String key : clauses.keySet()) {
184 File f = getFile(key);
185 addWabLib(dot, f);
186 }
187 doIncludeResource(dot, wab);
188 return dot;
189 }
190
191 /**
192 * Add a wab lib to the jar.
193 *
194 * @param f
195 */
196 private void addWabLib(Jar dot, File f) throws Exception {
197 if (f.exists()) {
198 Jar jar = new Jar(f);
199 jar.setDoNotTouchManifest();
200 addClose(jar);
201 String path = "WEB-INF/lib/" + f.getName();
202 dot.putResource(path, new JarResource(jar));
203 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
204
205 Manifest m = jar.getManifest();
206 String cp = m.getMainAttributes().getValue("Class-Path");
207 if (cp != null) {
208 Collection<String> parts = split(cp, ",");
209 for (String part : parts) {
210 File sub = getFile(f.getParentFile(), part);
211 if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000212 warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
213 f);
214 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000215 addWabLib(dot, sub);
216 }
217 }
218 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000219 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000220 error("WAB lib does not exist %s", f);
221 }
222 }
223
224 /**
225 * Get the manifest and write it out separately if -savemanifest is set
226 *
227 * @param dot
228 */
229 private void doSaveManifest(Jar dot) throws Exception {
230 String output = getProperty(SAVEMANIFEST);
231 if (output == null)
232 return;
233
234 File f = getFile(output);
235 if (f.isDirectory()) {
236 f = new File(f, "MANIFEST.MF");
237 }
238 f.delete();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000239 File fp = f.getParentFile();
240 if (!fp.exists() && !fp.mkdirs()) {
241 throw new IOException("Could not create directory " + fp);
242 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000243 OutputStream out = new FileOutputStream(f);
244 try {
245 Jar.writeManifest(dot.getManifest(), out);
246 }
247 finally {
248 out.close();
249 }
250 changedFile(f);
251 }
252
Stuart McCulloch669423b2012-06-26 16:34:24 +0000253 protected void changedFile(@SuppressWarnings("unused") File f) {}
Stuart McCullochf3173222012-06-07 21:57:32 +0000254
255 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000256 * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
257 * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
Stuart McCullochf3173222012-06-07 21:57:32 +0000258 *
259 * @return
260 */
261
Stuart McCulloch669423b2012-06-26 16:34:24 +0000262 void sign(@SuppressWarnings("unused") Jar jar) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000263 String signing = getProperty("-sign");
264 if (signing == null)
265 return;
266
267 trace("Signing %s, with %s", getBsn(), signing);
268 List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
269
270 Parameters infos = parseHeader(signing);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000271 for (Entry<String,Attrs> entry : infos.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000272 for (SignerPlugin signer : signers) {
273 signer.sign(this, entry.getKey());
274 }
275 }
276 }
277
278 public boolean hasSources() {
279 return isTrue(getProperty(SOURCES));
280 }
281
282 /**
283 * Answer extra packages. In this case we implement conditional package. Any
284 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000285 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000286 protected Jar getExtra() throws Exception {
287 Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
288 if (conditionals.isEmpty())
289 return null;
Stuart McCulloch669423b2012-06-26 16:34:24 +0000290 trace("do Conditional Package %s", conditionals);
Stuart McCullochf3173222012-06-07 21:57:32 +0000291 Instructions instructions = new Instructions(conditionals);
292
293 Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
294 referred.removeAll(getContained().keySet());
295
296 Jar jar = new Jar("conditional-import");
297 addClose(jar);
298 for (PackageRef pref : referred) {
299 for (Jar cpe : getClasspath()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000300 Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
Stuart McCullochf3173222012-06-07 21:57:32 +0000301 if (map != null) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000302 copy(jar, cpe, pref.getPath(), false);
303// Now use copy so that bnd.info is processed, next line should be
304// removed in the future TODO
305// jar.addDirectory(map, false);
Stuart McCullochf3173222012-06-07 21:57:32 +0000306 break;
307 }
308 }
309 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000310 if (jar.getDirectories().size() == 0) {
311 trace("extra dirs %s", jar.getDirectories());
Stuart McCullochf3173222012-06-07 21:57:32 +0000312 return null;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000313 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000314 return jar;
315 }
316
317 /**
318 * Intercept the call to analyze and cleanup versions after we have analyzed
319 * the setup. We do not want to cleanup if we are going to verify.
320 */
321
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000322 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000323 public void analyze() throws Exception {
324 super.analyze();
325 cleanupVersion(getImports(), null);
326 cleanupVersion(getExports(), getVersion());
327 String version = getProperty(BUNDLE_VERSION);
328 if (version != null) {
329 version = cleanupVersion(version);
330 if (version.endsWith(".SNAPSHOT")) {
331 version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
332 }
333 setProperty(BUNDLE_VERSION, version);
334 }
335 }
336
337 public void cleanupVersion(Packages packages, String defaultVersion) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000338 for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000339 Attrs attributes = entry.getValue();
340 String v = attributes.get(Constants.VERSION_ATTRIBUTE);
341 if (v == null && defaultVersion != null) {
342 if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
343 v = defaultVersion;
344 if (isPedantic())
345 warning("Used bundle version %s for exported package %s", v, entry.getKey());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000346 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000347 if (isPedantic())
348 warning("No export version for exported package %s", entry.getKey());
349 }
350 }
351 if (v != null)
352 attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
353 }
354 }
355
356 /**
357 *
358 */
359 private void addSources(Jar dot) {
360 if (!hasSources())
361 return;
362
363 Set<PackageRef> packages = Create.set();
364
365 for (TypeRef typeRef : getClassspace().keySet()) {
366 PackageRef packageRef = typeRef.getPackageRef();
367 String sourcePath = typeRef.getSourcePath();
368 String packagePath = packageRef.getPath();
369
370 boolean found = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000371 String[] fixed = {
372 "packageinfo", "package.html", "module-info.java", "package-info.java"
373 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000374
375 for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
376 File root = i.next();
377
378 // TODO should use bcp?
379
380 File f = getFile(root, sourcePath);
381 if (f.exists()) {
382 found = true;
383 if (!packages.contains(packageRef)) {
384 packages.add(packageRef);
385 File bdir = getFile(root, packagePath);
386 for (int j = 0; j < fixed.length; j++) {
387 File ff = getFile(bdir, fixed[j]);
388 if (ff.isFile()) {
389 String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
390 dot.putResource(name, new FileResource(ff));
391 }
392 }
393 }
394 if (packageRef.isDefaultPackage())
395 System.err.println("Duh?");
396 dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
397 }
398 }
399 if (!found) {
400 for (Jar jar : getClasspath()) {
401 Resource resource = jar.getResource(sourcePath);
402 if (resource != null) {
403 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000404 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000405 resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
406 if (resource != null) {
407 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
408 }
409 }
410 }
411 }
412 if (getSourcePath().isEmpty())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000413 warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
Stuart McCullochf3173222012-06-07 21:57:32 +0000414 // TODO copy from the jars where they came from
415 }
416 }
417
418 boolean firstUse = true;
419 private Tree tree;
420
421 public Collection<File> getSourcePath() {
422 if (firstUse) {
423 firstUse = false;
424 String sp = getProperty(SOURCEPATH);
425 if (sp != null) {
426 Parameters map = parseHeader(sp);
427 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
428 String file = i.next();
429 if (!isDuplicate(file)) {
430 File f = getFile(file);
431 if (!f.isDirectory()) {
432 error("Adding a sourcepath that is not a directory: " + f);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000433 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000434 sourcePath.add(f);
435 }
436 }
437 }
438 }
439 }
440 return sourcePath;
441 }
442
Stuart McCulloch669423b2012-06-26 16:34:24 +0000443 private void doVerify(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000444 Verifier verifier = new Verifier(this);
445 // Give the verifier the benefit of our analysis
446 // prevents parsing the files twice
447 verifier.verify();
448 getInfo(verifier);
449 }
450
Stuart McCulloch4482c702012-06-15 13:27:53 +0000451 private void doExpand(Jar dot) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000452
453 // Build an index of the class path that we can then
454 // use destructively
Stuart McCulloch4482c702012-06-15 13:27:53 +0000455 MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000456 for (Jar srce : getClasspath()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000457 for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000458 if (e.getValue() != null)
459 packages.add(e.getKey(), srce);
460 }
461 }
462
463 Parameters privatePackages = getPrivatePackage();
464 if (isTrue(getProperty(Constants.UNDERTEST))) {
465 String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
466 privatePackages.putAll(parseHeader(h));
467 }
468
469 if (!privatePackages.isEmpty()) {
470 Instructions privateFilter = new Instructions(privatePackages);
471 Set<Instruction> unused = doExpand(dot, packages, privateFilter);
472
473 if (!unused.isEmpty()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000474 warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
Stuart McCullochf3173222012-06-07 21:57:32 +0000475 }
476 }
477
478 Parameters exportedPackage = getExportPackage();
479 if (!exportedPackage.isEmpty()) {
480 Instructions exportedFilter = new Instructions(exportedPackage);
481
482 // We ignore unused instructions for exports, they should show
483 // up as errors during analysis. Otherwise any overlapping
484 // packages with the private packages should show up as
485 // unused
486
487 doExpand(dot, packages, exportedFilter);
488 }
489 }
490
491 /**
492 * Destructively filter the packages from the build up index. This index is
493 * used by the Export Package as well as the Private Package
494 *
495 * @param jar
496 * @param name
497 * @param instructions
498 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000499 private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000500 Set<Instruction> unused = Create.set();
501
Stuart McCulloch4482c702012-06-15 13:27:53 +0000502 for (Entry<Instruction,Attrs> e : filter.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000503 Instruction instruction = e.getKey();
504 if (instruction.isDuplicate())
505 continue;
506
507 Attrs directives = e.getValue();
508
509 // We can optionally filter on the
510 // source of the package. We assume
511 // they all match but this can be overridden
512 // on the instruction
513 Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
514
515 boolean used = false;
516
Stuart McCulloch4482c702012-06-15 13:27:53 +0000517 for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
518 Entry<String,List<Jar>> p = entry.next();
Stuart McCullochf3173222012-06-07 21:57:32 +0000519
520 String directory = p.getKey();
521 PackageRef packageRef = getPackageRef(directory);
522
523 // Skip * and meta data, we're talking packages!
524 if (packageRef.isMetaData() && instruction.isAny())
525 continue;
526
527 if (!instruction.matches(packageRef.getFQN()))
528 continue;
529
530 // Ensure it is never matched again
531 entry.remove();
532
533 // ! effectively removes it from consideration by others (this
534 // includes exports)
535 if (instruction.isNegated())
536 continue;
537
538 // Do the from: directive, filters on the JAR type
539 List<Jar> providers = filterFrom(from, p.getValue());
540 if (providers.isEmpty())
541 continue;
542
543 int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
544 copyPackage(jar, providers, directory, splitStrategy);
545
546 used = true;
547 }
548
549 if (!used && !isTrue(directives.get("optional:")))
550 unused.add(instruction);
551 }
552 return unused;
553 }
554
555 /**
556 * @param from
557 * @return
558 */
559 private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
560 if (from.isAny())
561 return providers;
562
563 List<Jar> np = new ArrayList<Jar>();
564 for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
565 Jar j = i.next();
566 if (from.matches(j.getName())) {
567 np.add(j);
568 }
569 }
570 return np;
571 }
572
573 /**
574 * Copy the package from the providers based on the split package strategy.
575 *
576 * @param dest
577 * @param providers
578 * @param directory
579 * @param splitStrategy
580 */
581 private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
582 switch (splitStrategy) {
583 case SPLIT_MERGE_LAST :
584 for (Jar srce : providers) {
585 copy(dest, srce, path, true);
586 }
587 break;
588
589 case SPLIT_MERGE_FIRST :
590 for (Jar srce : providers) {
591 copy(dest, srce, path, false);
592 }
593 break;
594
595 case SPLIT_ERROR :
596 error(diagnostic(path, providers));
597 break;
598
599 case SPLIT_FIRST :
600 copy(dest, providers.get(0), path, false);
601 break;
602
603 default :
604 if (providers.size() > 1)
605 warning("%s", diagnostic(path, providers));
606 for (Jar srce : providers) {
607 copy(dest, srce, path, false);
608 }
609 break;
610 }
611 }
612
613 /**
614 * Cop
615 *
616 * @param dest
617 * @param srce
618 * @param path
619 * @param overwriteResource
620 */
621 private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000622 trace("copy d=" + dest + " s=" + srce +" p="+ path);
Stuart McCullochf3173222012-06-07 21:57:32 +0000623 dest.copy(srce, path, overwrite);
Stuart McCulloch669423b2012-06-26 16:34:24 +0000624
625 // bnd.info sources must be preprocessed
626 String bndInfoPath = path + "/bnd.info";
627 Resource r = dest.getResource(bndInfoPath);
628 if ( r != null && !(r instanceof PreprocessResource)) {
629 trace("preprocessing bnd.info");
630 PreprocessResource pp = new PreprocessResource(this, r);
631 dest.putResource(bndInfoPath, pp);
632 }
633
Stuart McCullochf3173222012-06-07 21:57:32 +0000634 if (hasSources()) {
635 String srcPath = "OSGI-OPT/src/" + path;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000636 Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
Stuart McCullochf3173222012-06-07 21:57:32 +0000637 if (srcContents != null) {
638 dest.addDirectory(srcContents, overwrite);
639 }
640 }
641 }
642
643 /**
644 * Analyze the classpath for a split package
645 *
646 * @param pack
647 * @param classpath
648 * @param source
649 * @return
650 */
651 private String diagnostic(String pack, List<Jar> culprits) {
652 // Default is like merge-first, but with a warning
653 return "Split package, multiple jars provide the same package:"
654 + pack
655 + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
656 + "Package found in " + culprits + "\n" //
657 + "Class path " + getClasspath();
658 }
659
660 private int getSplitStrategy(String type) {
661 if (type == null)
662 return SPLIT_DEFAULT;
663
664 if (type.equals("merge-last"))
665 return SPLIT_MERGE_LAST;
666
667 if (type.equals("merge-first"))
668 return SPLIT_MERGE_FIRST;
669
670 if (type.equals("error"))
671 return SPLIT_ERROR;
672
673 if (type.equals("first"))
674 return SPLIT_FIRST;
675
676 error("Invalid strategy for split-package: " + type);
677 return SPLIT_DEFAULT;
678 }
679
680 /**
681 * Matches the instructions against a package.
682 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000683 * @param instructions
684 * The list of instructions
685 * @param pack
686 * The name of the package
687 * @param unused
688 * The total list of patterns, matched patterns are removed
689 * @param source
690 * The name of the source container, can be filtered upon with
691 * the from: directive.
Stuart McCullochf3173222012-06-07 21:57:32 +0000692 * @return
693 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000694 private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
695 for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000696 Instruction pattern = entry.getKey();
697
698 // It is possible to filter on the source of the
699 // package with the from: directive. This is an
700 // instruction that must match the name of the
701 // source class path entry.
702
703 String from = entry.getValue().get(FROM_DIRECTIVE);
704 if (from != null) {
705 Instruction f = new Instruction(from);
706 if (!f.matches(source) || f.isNegated())
707 continue;
708 }
709
710 // Now do the normal
711 // matching
712 if (pattern.matches(pack)) {
713 if (unused != null)
714 unused.remove(pattern);
715 return pattern;
716 }
717 }
718 return null;
719 }
720
721 /**
722 * Parse the Bundle-Includes header. Files in the bundles Include header are
723 * included in the jar. The source can be a directory or a file.
724 *
725 * @throws IOException
726 * @throws FileNotFoundException
727 */
728 private void doIncludeResources(Jar jar) throws Exception {
729 String includes = getProperty("Bundle-Includes");
730 if (includes == null) {
731 includes = getProperty(INCLUDERESOURCE);
732 if (includes == null || includes.length() == 0)
733 includes = getProperty("Include-Resource");
Stuart McCulloch4482c702012-06-15 13:27:53 +0000734 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000735 warning("Please use -includeresource instead of Bundle-Includes");
736
737 doIncludeResource(jar, includes);
738
739 }
740
741 private void doIncludeResource(Jar jar, String includes) throws Exception {
742 Parameters clauses = parseHeader(includes);
743 doIncludeResource(jar, clauses);
744 }
745
Stuart McCulloch4482c702012-06-15 13:27:53 +0000746 private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
747 for (Entry<String,Attrs> entry : clauses.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000748 doIncludeResource(jar, entry.getKey(), entry.getValue());
749 }
750 }
751
Stuart McCulloch4482c702012-06-15 13:27:53 +0000752 private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
753 Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000754
755 boolean preprocess = false;
756 boolean absentIsOk = false;
757
758 if (name.startsWith("{") && name.endsWith("}")) {
759 preprocess = true;
760 name = name.substring(1, name.length() - 1).trim();
761 }
762
763 String parts[] = name.split("\\s*=\\s*");
764 String source = parts[0];
765 String destination = parts[0];
766 if (parts.length == 2)
767 source = parts[1];
768
769 if (source.startsWith("-")) {
770 source = source.substring(1);
771 absentIsOk = true;
772 }
773
774 if (source.startsWith("@")) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000775 extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
776 } else if (extra.containsKey("cmd")) {
777 doCommand(jar, source, destination, extra, preprocess, absentIsOk);
Stuart McCulloch99fd9a72012-07-24 21:37:47 +0000778 } else if (extra.containsKey(LITERAL_ATTRIBUTE)) {
779 String literal = extra.get(LITERAL_ATTRIBUTE);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000780 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
781 String x = extra.get("extra");
782 if (x != null)
783 r.setExtra(x);
784 jar.putResource(name, r);
785 } else {
786 File sourceFile;
787 String destinationPath;
788
789 sourceFile = getFile(source);
790 if (parts.length == 1) {
791 // Directories should be copied to the root
792 // but files to their file name ...
793 if (sourceFile.isDirectory())
794 destinationPath = "";
795 else
796 destinationPath = sourceFile.getName();
797 } else {
798 destinationPath = parts[0];
Stuart McCullochf3173222012-06-07 21:57:32 +0000799 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000800 // Handle directories
801 if (sourceFile.isDirectory()) {
802 destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
803 return;
804 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000805
Stuart McCulloch4482c702012-06-15 13:27:53 +0000806 // destinationPath = checkDestinationPath(destinationPath);
Stuart McCullochf3173222012-06-07 21:57:32 +0000807
Stuart McCulloch4482c702012-06-15 13:27:53 +0000808 if (!sourceFile.exists()) {
809 if (absentIsOk)
810 return;
Stuart McCullochf3173222012-06-07 21:57:32 +0000811
Stuart McCulloch4482c702012-06-15 13:27:53 +0000812 noSuchFile(jar, name, extra, source, destinationPath);
813 } else
814 copy(jar, destinationPath, sourceFile, preprocess, extra);
815 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000816 }
817
818 /**
819 * It is possible in Include-Resource to use a system command that generates
820 * the contents, this is indicated with {@code cmd} attribute. The command
821 * can be repeated for a number of source files with the {@code for}
822 * attribute which indicates a list of repetitions, often down with the
823 * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
824 * repetition will repeat the given command for each item. The @} macro can
825 * be used to replace the current item. If no {@code for} is given, the
Stuart McCulloch4482c702012-06-15 13:27:53 +0000826 * source is used as the only item. If the destination contains a macro,
827 * each iteration will create a new file, otherwise the destination name is
828 * used. The execution of the command is delayed until the JAR is actually
829 * written to the file system for performance reasons.
Stuart McCullochf3173222012-06-07 21:57:32 +0000830 *
831 * @param jar
832 * @param source
833 * @param destination
834 * @param extra
835 * @param preprocess
836 * @param absentIsOk
837 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000838 private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
839 boolean absentIsOk) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000840 String repeat = extra.get("for"); // TODO constant
841 if (repeat == null)
842 repeat = source;
843
844 Collection<String> requires = split(extra.get("requires"));
845 long lastModified = 0;
846 for (String required : requires) {
847 File file = getFile(required);
848 if (!file.isFile()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000849 error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
850 file.getAbsoluteFile());
851 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000852 lastModified = Math.max(lastModified, file.lastModified());
853 }
854
855 String cmd = extra.get("cmd");
856
857 Collection<String> items = Processor.split(repeat);
858
859 CombinedResource cr = null;
860
861 if (!destination.contains("${@}")) {
862 cr = new CombinedResource();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000863 cr.lastModified = lastModified;
Stuart McCullochf3173222012-06-07 21:57:32 +0000864 }
865 trace("last modified requires %s", lastModified);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000866
Stuart McCullochf3173222012-06-07 21:57:32 +0000867 for (String item : items) {
868 setProperty("@", item);
869 try {
870 String path = getReplacer().process(destination);
871 String command = getReplacer().process(cmd);
872 File file = getFile(item);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000873 if ( file.exists())
874 lastModified = Math.max(lastModified, file.lastModified());
875
876 Resource r = new CommandResource(command, this, lastModified, getBase());
Stuart McCullochf3173222012-06-07 21:57:32 +0000877
878 if (preprocess)
879 r = new PreprocessResource(this, r);
880
881 if (cr == null)
882 jar.putResource(path, r);
883 else
884 cr.addResource(r);
885 }
886 finally {
887 unsetProperty("@");
888 }
889 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000890
Stuart McCullochf3173222012-06-07 21:57:32 +0000891 // Add last so the correct modification date is used
892 // to update the modified time.
Stuart McCulloch4482c702012-06-15 13:27:53 +0000893 if (cr != null)
Stuart McCullochf3173222012-06-07 21:57:32 +0000894 jar.putResource(destination, cr);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000895
896 updateModified(lastModified, "Include-Resource: cmd");
Stuart McCullochf3173222012-06-07 21:57:32 +0000897 }
898
Stuart McCulloch4482c702012-06-15 13:27:53 +0000899 private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
900 String destinationPath) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000901 String filter = extra.get("filter:");
902 boolean flatten = isTrue(extra.get("flatten:"));
903 boolean recursive = true;
904 String directive = extra.get("recursive:");
905 if (directive != null) {
906 recursive = isTrue(directive);
907 }
908
909 Instruction.Filter iFilter = null;
910 if (filter != null) {
911 iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000912 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000913 iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
914 }
915
Stuart McCulloch4482c702012-06-15 13:27:53 +0000916 Map<String,File> files = newMap();
Stuart McCullochf3173222012-06-07 21:57:32 +0000917 resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
918
Stuart McCulloch4482c702012-06-15 13:27:53 +0000919 for (Map.Entry<String,File> entry : files.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000920 copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
921 }
922 return destinationPath;
923 }
924
Stuart McCulloch4482c702012-06-15 13:27:53 +0000925 private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
926 boolean flatten) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000927
928 if (doNotCopy(dir.getName())) {
929 return;
930 }
931
932 File[] fs = dir.listFiles(filter);
933 for (File file : fs) {
934 if (file.isDirectory()) {
935 if (recursive) {
936 String nextPath;
937 if (flatten)
938 nextPath = path;
939 else
940 nextPath = appendPath(path, file.getName());
941
942 resolveFiles(file, filter, recursive, nextPath, files, flatten);
943 }
944 // Directories are ignored otherwise
Stuart McCulloch4482c702012-06-15 13:27:53 +0000945 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000946 String p = appendPath(path, file.getName());
947 if (files.containsKey(p))
948 warning("Include-Resource overwrites entry %s from file %s", p, file);
949 files.put(p, file);
950 }
951 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000952 if (fs.length == 0) {
953 File empty = new File(dir, Constants.EMPTY_HEADER);
954 files.put(appendPath(path, empty.getName()), empty);
955 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000956 }
957
Stuart McCulloch669423b2012-06-26 16:34:24 +0000958 private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000959 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000960 Jar src = getJarFromName(source, "Include-Resource " + source);
961 if (src != null) {
962 // Do not touch the manifest so this also
963 // works for signed files.
964 src.setDoNotTouchManifest();
965 JarResource jarResource = new JarResource(src);
966 jar.putResource(destinationPath, jarResource);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000967 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000968 Resource lastChance = make.process(source);
969 if (lastChance != null) {
970 String x = extra.get("extra");
971 if (x != null)
972 lastChance.setExtra(x);
973 jar.putResource(destinationPath, lastChance);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000974 } else
Stuart McCullochf3173222012-06-07 21:57:32 +0000975 error("Input file does not exist: " + source);
976 }
977 }
978
979 /**
980 * Extra resources from a Jar and add them to the given jar. The clause is
981 * the
982 *
983 * @param jar
984 * @param clauses
985 * @param i
986 * @throws ZipException
987 * @throws IOException
988 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000989 private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
990 IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000991 // Inline all resources and classes from another jar
992 // optionally appended with a modified regular expression
993 // like @zip.jar!/META-INF/MANIFEST.MF
994 int n = source.lastIndexOf("!/");
995 Instruction instr = null;
996 if (n > 0) {
997 instr = new Instruction(source.substring(n + 2));
998 source = source.substring(0, n);
999 }
1000
1001 // Pattern filter = null;
1002 // if (n > 0) {
1003 // String fstring = source.substring(n + 2);
1004 // source = source.substring(0, n);
1005 // filter = wildcard(fstring);
1006 // }
1007 Jar sub = getJarFromName(source, "extract from jar");
1008 if (sub == null) {
1009 if (absentIsOk)
1010 return;
1011
1012 error("Can not find JAR file " + source);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001013 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001014 addAll(jar, sub, instr, destination);
1015 }
1016 }
1017
1018 /**
1019 * Add all the resources in the given jar that match the given filter.
1020 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001021 * @param sub
1022 * the jar
1023 * @param filter
1024 * a pattern that should match the resoures in sub to be added
Stuart McCullochf3173222012-06-07 21:57:32 +00001025 */
1026 public boolean addAll(Jar to, Jar sub, Instruction filter) {
1027 return addAll(to, sub, filter, "");
1028 }
1029
1030 /**
1031 * Add all the resources in the given jar that match the given filter.
1032 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001033 * @param sub
1034 * the jar
1035 * @param filter
1036 * a pattern that should match the resoures in sub to be added
Stuart McCullochf3173222012-06-07 21:57:32 +00001037 */
1038 public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
1039 boolean dupl = false;
1040 for (String name : sub.getResources().keySet()) {
1041 if ("META-INF/MANIFEST.MF".equals(name))
1042 continue;
1043
1044 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch4482c702012-06-15 13:27:53 +00001045 dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochf3173222012-06-07 21:57:32 +00001046 }
1047 return dupl;
1048 }
1049
Stuart McCulloch4482c702012-06-15 13:27:53 +00001050 private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001051 if (doNotCopy(from.getName()))
1052 return;
1053
1054 if (from.isDirectory()) {
1055
1056 File files[] = from.listFiles();
1057 for (int i = 0; i < files.length; i++) {
1058 copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
1059 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001060 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001061 if (from.exists()) {
1062 Resource resource = new FileResource(from);
1063 if (preprocess) {
1064 resource = new PreprocessResource(this, resource);
1065 }
1066 String x = extra.get("extra");
1067 if (x != null)
1068 resource.setExtra(x);
1069 if (path.endsWith("/"))
1070 path = path + from.getName();
1071 jar.putResource(path, resource);
1072
1073 if (isTrue(extra.get(LIB_DIRECTIVE))) {
1074 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
1075 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001076 } else if (from.getName().equals(Constants.EMPTY_HEADER)) {
1077 jar.putResource(path, new EmbeddedResource(new byte[0], 0));
Stuart McCulloch4482c702012-06-15 13:27:53 +00001078 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +00001079 error("Input file does not exist: " + from);
1080 }
1081 }
1082 }
1083
1084 public void setSourcepath(File[] files) {
1085 for (int i = 0; i < files.length; i++)
1086 addSourcepath(files[i]);
1087 }
1088
1089 public void addSourcepath(File cp) {
1090 if (!cp.exists())
1091 warning("File on sourcepath that does not exist: " + cp);
1092
1093 sourcePath.add(cp);
1094 }
1095
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001096 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001097 public void close() {
1098 super.close();
1099 }
1100
1101 /**
1102 * Build Multiple jars. If the -sub command is set, we filter the file with
1103 * the given patterns.
1104 *
1105 * @return
1106 * @throws Exception
1107 */
1108 public Jar[] builds() throws Exception {
1109 begin();
1110
1111 // Are we acting as a conduit for another JAR?
1112 String conduit = getProperty(CONDUIT);
1113 if (conduit != null) {
1114 Parameters map = parseHeader(conduit);
1115 Jar[] result = new Jar[map.size()];
1116 int n = 0;
1117 for (String file : map.keySet()) {
1118 Jar c = new Jar(getFile(file));
1119 addClose(c);
1120 String name = map.get(file).get("name");
1121 if (name != null)
1122 c.setName(name);
1123
1124 result[n++] = c;
1125 }
1126 return result;
1127 }
1128
1129 List<Jar> result = new ArrayList<Jar>();
1130 List<Builder> builders;
1131
1132 builders = getSubBuilders();
1133
1134 for (Builder builder : builders) {
1135 try {
1136 Jar jar = builder.build();
1137 jar.setName(builder.getBsn());
1138 result.add(jar);
1139 }
1140 catch (Exception e) {
1141 e.printStackTrace();
1142 error("Sub Building " + builder.getBsn(), e);
1143 }
1144 if (builder != this)
1145 getInfo(builder, builder.getBsn() + ": ");
1146 }
1147 return result.toArray(new Jar[result.size()]);
1148 }
1149
1150 /**
1151 * Answer a list of builders that represent this file or a list of files
1152 * specified in -sub. This list can be empty. These builders represents to
1153 * be created artifacts and are each scoped to such an artifacts. The
1154 * builders can be used to build the bundles or they can be used to find out
1155 * information about the to be generated bundles.
1156 *
1157 * @return List of 0..n builders representing artifacts.
1158 * @throws Exception
1159 */
1160 public List<Builder> getSubBuilders() throws Exception {
1161 String sub = getProperty(SUB);
1162 if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
1163 return Arrays.asList(this);
1164
1165 List<Builder> builders = new ArrayList<Builder>();
1166 if (isTrue(getProperty(NOBUNDLES)))
1167 return builders;
1168
1169 Parameters subsMap = parseHeader(sub);
1170 for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
1171 File file = getFile(i.next());
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001172 if (file.isFile() && !file.getName().startsWith(".")) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001173 builders.add(getSubBuilder(file));
1174 i.remove();
1175 }
1176 }
1177
1178 Instructions instructions = new Instructions(subsMap);
1179
1180 List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
1181
1182 nextFile: while (members.size() > 0) {
1183
1184 File file = members.remove(0);
1185
1186 // Check if the file is one of our parents
1187 Processor p = this;
1188 while (p != null) {
1189 if (file.equals(p.getPropertiesFile()))
1190 continue nextFile;
1191 p = p.getParent();
1192 }
1193
1194 for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
1195
1196 Instruction instruction = i.next();
1197 if (instruction.matches(file.getName())) {
1198
1199 if (!instruction.isNegated()) {
1200 builders.add(getSubBuilder(file));
1201 }
1202
1203 // Because we matched (even though we could be negated)
1204 // we skip any remaining searches
1205 continue nextFile;
1206 }
1207 }
1208 }
1209 return builders;
1210 }
1211
1212 public Builder getSubBuilder(File file) throws Exception {
1213 Builder builder = getSubBuilder();
1214 if (builder != null) {
1215 builder.setProperties(file);
1216 addClose(builder);
1217 }
1218 return builder;
1219 }
1220
1221 public Builder getSubBuilder() throws Exception {
1222 Builder builder = new Builder(this);
1223 builder.setBase(getBase());
1224
1225 for (Jar file : getClasspath()) {
1226 builder.addClasspath(file);
1227 }
1228
1229 return builder;
1230 }
1231
1232 /**
1233 * A macro to convert a maven version to an OSGi version
1234 */
1235
1236 public String _maven_version(String args[]) {
1237 if (args.length > 2)
1238 error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
Stuart McCulloch4482c702012-06-15 13:27:53 +00001239 else if (args.length < 2)
1240 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1241 else {
1242 return cleanupVersion(args[1]);
1243 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001244 return null;
1245 }
1246
Stuart McCulloch4482c702012-06-15 13:27:53 +00001247 public String _permissions(String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001248 StringBuilder sb = new StringBuilder();
1249
1250 for (String arg : args) {
1251 if ("packages".equals(arg) || "all".equals(arg)) {
1252 for (PackageRef imp : getImports().keySet()) {
1253 if (!imp.isJava()) {
1254 sb.append("(org.osgi.framework.PackagePermission \"");
1255 sb.append(imp);
1256 sb.append("\" \"import\")\r\n");
1257 }
1258 }
1259 for (PackageRef exp : getExports().keySet()) {
1260 sb.append("(org.osgi.framework.PackagePermission \"");
1261 sb.append(exp);
1262 sb.append("\" \"export\")\r\n");
1263 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001264 } else if ("admin".equals(arg) || "all".equals(arg)) {
1265 sb.append("(org.osgi.framework.AdminPermission)");
1266 } else if ("permissions".equals(arg))
1267 ;
Stuart McCullochf3173222012-06-07 21:57:32 +00001268 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001269 error("Invalid option in ${permissions}: %s", arg);
Stuart McCullochf3173222012-06-07 21:57:32 +00001270 }
1271 return sb.toString();
1272 }
1273
1274 /**
1275 *
1276 */
1277 public void removeBundleSpecificHeaders() {
1278 Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
1279 setForceLocal(set);
1280 }
1281
1282 /**
1283 * Check if the given resource is in scope of this bundle. That is, it
1284 * checks if the Include-Resource includes this resource or if it is a class
Stuart McCulloch4482c702012-06-15 13:27:53 +00001285 * file it is on the class path and the Export-Package or Private-Package
Stuart McCullochf3173222012-06-07 21:57:32 +00001286 * include this resource.
1287 *
1288 * @param f
1289 * @return
1290 */
1291 public boolean isInScope(Collection<File> resources) throws Exception {
1292 Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
1293 clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
1294 if (isTrue(getProperty(Constants.UNDERTEST))) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001295 clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
Stuart McCullochf3173222012-06-07 21:57:32 +00001296 }
1297
1298 Collection<String> ir = getIncludedResourcePrefixes();
1299
1300 Instructions instructions = new Instructions(clauses);
1301
1302 for (File r : resources) {
1303 String cpEntry = getClasspathEntrySuffix(r);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001304
Stuart McCullochf3173222012-06-07 21:57:32 +00001305 if (cpEntry != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001306
1307 if (cpEntry.equals("")) // Meaning we actually have a CPE
1308 return true;
1309
Stuart McCullochf3173222012-06-07 21:57:32 +00001310 String pack = Descriptors.getPackage(cpEntry);
1311 Instruction i = matches(instructions, pack, null, r.getName());
1312 if (i != null)
1313 return !i.isNegated();
1314 }
1315
1316 // Check if this resource starts with one of the I-C header
1317 // paths.
1318 String path = r.getAbsolutePath();
1319 for (String p : ir) {
1320 if (path.startsWith(p))
1321 return true;
1322 }
1323 }
1324 return false;
1325 }
1326
1327 /**
1328 * Extra the paths for the directories and files that are used in the
1329 * Include-Resource header.
1330 *
1331 * @return
1332 */
1333 private Collection<String> getIncludedResourcePrefixes() {
1334 List<String> prefixes = new ArrayList<String>();
1335 Parameters includeResource = getIncludeResource();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001336 for (Entry<String,Attrs> p : includeResource.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001337 if (p.getValue().containsKey("literal"))
1338 continue;
1339
1340 Matcher m = IR_PATTERN.matcher(p.getKey());
1341 if (m.matches()) {
1342 File f = getFile(m.group(1));
1343 prefixes.add(f.getAbsolutePath());
1344 }
1345 }
1346 return prefixes;
1347 }
1348
1349 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001350 * Answer the string of the resource that it has in the container. It is
1351 * possible that the resource is a classpath entry. In that case an empty
1352 * string is returned.
Stuart McCullochf3173222012-06-07 21:57:32 +00001353 *
Stuart McCulloch4482c702012-06-15 13:27:53 +00001354 * @param resource
1355 * The resource to look for
1356 * @return A suffix on the classpath or "" if the resource is a class path
1357 * entry
Stuart McCullochf3173222012-06-07 21:57:32 +00001358 * @throws Exception
1359 */
1360 public String getClasspathEntrySuffix(File resource) throws Exception {
1361 for (Jar jar : getClasspath()) {
1362 File source = jar.getSource();
1363 if (source != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001364
Stuart McCullochf3173222012-06-07 21:57:32 +00001365 source = source.getCanonicalFile();
1366 String sourcePath = source.getAbsolutePath();
1367 String resourcePath = resource.getAbsolutePath();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001368 if (sourcePath.equals(resourcePath))
1369 return ""; // Matches a classpath entry
Stuart McCullochf3173222012-06-07 21:57:32 +00001370
1371 if (resourcePath.startsWith(sourcePath)) {
1372 // Make sure that the path name is translated correctly
1373 // i.e. on Windows the \ must be translated to /
1374 String filePath = resourcePath.substring(sourcePath.length() + 1);
1375
1376 return filePath.replace(File.separatorChar, '/');
1377 }
1378 }
1379 }
1380 return null;
1381 }
1382
1383 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001384 * doNotCopy The doNotCopy variable maintains a patter for files that should
1385 * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
1386 * ca be overridden with the {@link Constants#DONOTCOPY} property.
Stuart McCullochf3173222012-06-07 21:57:32 +00001387 */
1388
1389 public boolean doNotCopy(String v) {
1390 return getDoNotCopy().matcher(v).matches();
1391 }
1392
1393 public Pattern getDoNotCopy() {
1394 if (xdoNotCopy == null) {
1395 String string = null;
1396 try {
1397 string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
1398 xdoNotCopy = Pattern.compile(string);
1399 }
1400 catch (Exception e) {
1401 error("Invalid value for %s, value is %s", DONOTCOPY, string);
1402 xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
1403 }
1404 }
1405 return xdoNotCopy;
1406 }
1407
1408 /**
1409 */
1410
1411 static MakeBnd makeBnd = new MakeBnd();
1412 static MakeCopy makeCopy = new MakeCopy();
1413 static ServiceComponent serviceComponent = new ServiceComponent();
1414 static DSAnnotations dsAnnotations = new DSAnnotations();
1415 static MetatypePlugin metatypePlugin = new MetatypePlugin();
1416
1417 @Override
1418 protected void setTypeSpecificPlugins(Set<Object> list) {
1419 list.add(makeBnd);
1420 list.add(makeCopy);
1421 list.add(serviceComponent);
1422 list.add(dsAnnotations);
1423 list.add(metatypePlugin);
1424 super.setTypeSpecificPlugins(list);
1425 }
1426
1427 /**
1428 * Diff this bundle to another bundle for the given packages.
1429 *
1430 * @throws Exception
1431 */
1432
Stuart McCulloch669423b2012-06-26 16:34:24 +00001433 public void doDiff(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001434 Parameters diffs = parseHeader(getProperty("-diff"));
1435 if (diffs.isEmpty())
1436 return;
1437
1438 trace("diff %s", diffs);
1439
1440 if (tree == null)
1441 tree = differ.tree(this);
1442
Stuart McCulloch4482c702012-06-15 13:27:53 +00001443 for (Entry<String,Attrs> entry : diffs.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001444 String path = entry.getKey();
1445 File file = getFile(path);
1446 if (!file.isFile()) {
1447 error("Diffing against %s that is not a file", file);
1448 continue;
1449 }
1450
1451 boolean full = entry.getValue().get("--full") != null;
1452 boolean warning = entry.getValue().get("--warning") != null;
1453
1454 Tree other = differ.tree(file);
1455 Diff api = tree.diff(other).get("<api>");
1456 Instructions instructions = new Instructions(entry.getValue().get("--pack"));
1457
1458 trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
1459 for (Diff p : api.getChildren()) {
1460 String pname = p.getName();
1461 if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
1462 if (p.getDelta() != Delta.UNCHANGED) {
1463
1464 if (!full)
1465 if (warning)
1466 warning("Differ %s", p);
1467 else
1468 error("Differ %s", p);
1469 else {
1470 if (warning)
Stuart McCulloch4482c702012-06-15 13:27:53 +00001471 warning("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochf3173222012-06-07 21:57:32 +00001472 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001473 error("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochf3173222012-06-07 21:57:32 +00001474 show(p, "", warning);
1475 }
1476 }
1477 }
1478 }
1479 }
1480 }
1481
1482 /**
1483 * Show the diff recursively
1484 *
1485 * @param p
1486 * @param i
1487 */
1488 private void show(Diff p, String indent, boolean warning) {
1489 Delta d = p.getDelta();
1490 if (d == Delta.UNCHANGED)
1491 return;
1492
1493 if (warning)
1494 warning("%s%s", indent, p);
1495 else
1496 error("%s%s", indent, p);
1497
1498 indent = indent + " ";
1499 switch (d) {
1500 case CHANGED :
1501 case MAJOR :
1502 case MINOR :
1503 case MICRO :
1504 break;
1505
1506 default :
1507 return;
1508 }
1509 for (Diff c : p.getChildren())
1510 show(c, indent, warning);
1511 }
1512
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001513
Stuart McCullochf3173222012-06-07 21:57:32 +00001514 public void addSourcepath(Collection<File> sourcepath) {
1515 for (File f : sourcepath) {
1516 addSourcepath(f);
1517 }
1518 }
1519
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001520 /**
1521 * Base line against a previous version. Should be overridden in the ProjectBuilder where we have access to the repos
1522 *
1523 * @throws Exception
1524 */
Stuart McCulloch54229442012-07-12 22:12:58 +00001525
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001526 protected void doBaseline(Jar dot) throws Exception {}
Stuart McCullochf3173222012-06-07 21:57:32 +00001527}