blob: 11c8863b88e39a8610d9b70d16cd9c081f719cc7 [file] [log] [blame]
Stuart McCulloched3bd712009-09-03 01:55:34 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.util.*;
5import java.util.jar.*;
Stuart McCulloched3bd712009-09-03 01:55:34 +00006import java.util.zip.*;
7
8import aQute.bnd.make.*;
Richard S. Hall2c9e5922010-10-25 19:07:06 +00009import aQute.bnd.maven.*;
Stuart McCulloched3bd712009-09-03 01:55:34 +000010import aQute.bnd.service.*;
11
12/**
13 * Include-Resource: ( [name '=' ] file )+
Richard S. Hall2c9e5922010-10-25 19:07:06 +000014 *
Stuart McCulloched3bd712009-09-03 01:55:34 +000015 * Private-Package: package-decl ( ',' package-decl )*
Richard S. Hall2c9e5922010-10-25 19:07:06 +000016 *
Stuart McCulloched3bd712009-09-03 01:55:34 +000017 * Export-Package: package-decl ( ',' package-decl )*
Richard S. Hall2c9e5922010-10-25 19:07:06 +000018 *
Stuart McCulloched3bd712009-09-03 01:55:34 +000019 * Import-Package: package-decl ( ',' package-decl )*
Richard S. Hall2c9e5922010-10-25 19:07:06 +000020 *
21 * @version $Revision: 1.27 $
Stuart McCulloched3bd712009-09-03 01:55:34 +000022 */
23public class Builder extends Analyzer {
Richard S. Hall2c9e5922010-10-25 19:07:06 +000024 private static final int SPLIT_MERGE_LAST = 1;
25 private static final int SPLIT_MERGE_FIRST = 2;
26 private static final int SPLIT_ERROR = 3;
27 private static final int SPLIT_FIRST = 4;
28 private static final int SPLIT_DEFAULT = 0;
Stuart McCulloched3bd712009-09-03 01:55:34 +000029
Richard S. Hall2c9e5922010-10-25 19:07:06 +000030 List<File> sourcePath = new ArrayList<File>();
Stuart McCulloched3bd712009-09-03 01:55:34 +000031
Richard S. Hall2c9e5922010-10-25 19:07:06 +000032 Make make = new Make(this);
Stuart McCulloched3bd712009-09-03 01:55:34 +000033
Richard S. Hall2c9e5922010-10-25 19:07:06 +000034 public Builder(Processor parent) {
35 super(parent);
36 }
Stuart McCulloched3bd712009-09-03 01:55:34 +000037
Richard S. Hall2c9e5922010-10-25 19:07:06 +000038 public Builder() {
39 }
Stuart McCulloched3bd712009-09-03 01:55:34 +000040
Richard S. Hall2c9e5922010-10-25 19:07:06 +000041 public Map<String, Clazz> analyzeBundleClasspath(Jar dot,
42 Map<String, Map<String, String>> bundleClasspath,
43 Map<String, Map<String, String>> contained,
44 Map<String, Map<String, String>> referred,
45 Map<String, Set<String>> uses) throws IOException {
46 return super.analyzeBundleClasspath(dot, bundleClasspath, contained, referred, uses);
Stuart McCulloched3bd712009-09-03 01:55:34 +000047 }
48
49 public Jar build() throws Exception {
Richard S. Hall2c9e5922010-10-25 19:07:06 +000050 init();
51 if (isTrue(getProperty(NOBUNDLES)))
52 return null;
Stuart McCulloched3bd712009-09-03 01:55:34 +000053
Richard S. Hall2c9e5922010-10-25 19:07:06 +000054 if (getProperty(CONDUIT) != null)
55 error("Specified " + CONDUIT
56 + " but calls build() instead of builds() (might be a programmer error");
Stuart McCulloched3bd712009-09-03 01:55:34 +000057
Richard S. Hall2c9e5922010-10-25 19:07:06 +000058 dot = new Jar("dot");
59 addClose(dot);
60 try {
61 long modified = Long.parseLong(getProperty("base.modified"));
62 dot.updateModified(modified, "Base modified");
63 } catch (Exception e) {
64 }
Stuart McCulloched3bd712009-09-03 01:55:34 +000065
Richard S. Hall2c9e5922010-10-25 19:07:06 +000066 doExpand(dot);
67 doIncludeResources(dot);
68 doConditional(dot);
69 dot = doWab(dot);
Stuart McCulloched3bd712009-09-03 01:55:34 +000070
Richard S. Hall2c9e5922010-10-25 19:07:06 +000071 // NEW!
72 // Check if we override the calculation of the
73 // manifest. We still need to calculated it because
74 // we need to have analyzed the classpath.
Stuart McCulloched3bd712009-09-03 01:55:34 +000075
Richard S. Hall2c9e5922010-10-25 19:07:06 +000076 Manifest manifest = calcManifest();
Stuart McCulloched3bd712009-09-03 01:55:34 +000077
Richard S. Hall2c9e5922010-10-25 19:07:06 +000078 String mf = getProperty(MANIFEST);
79 if (mf != null) {
80 File mff = getFile(mf);
81 if (mff.isFile()) {
82 try {
83 InputStream in = new FileInputStream(mff);
84 manifest = new Manifest(in);
85 in.close();
86 } catch (Exception e) {
87 error(MANIFEST + " while reading manifest file", e);
88 }
89 } else {
90 error(MANIFEST + ", no such file " + mf);
91 }
92 }
Stuart McCulloched3bd712009-09-03 01:55:34 +000093
Richard S. Hall2c9e5922010-10-25 19:07:06 +000094 if (getProperty(NOMANIFEST) == null)
95 dot.setManifest(manifest);
96 else
97 dot.setDoNotTouchManifest();
Stuart McCulloched3bd712009-09-03 01:55:34 +000098
Richard S. Hall2c9e5922010-10-25 19:07:06 +000099 // This must happen after we analyzed so
100 // we know what it is on the classpath
101 addSources(dot);
Stuart McCulloched3bd712009-09-03 01:55:34 +0000102
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000103 if (getProperty(POM) != null)
104 dot.putResource("pom.xml", new PomResource(dot.getManifest()));
Stuart McCulloched3bd712009-09-03 01:55:34 +0000105
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000106 if (!isNoBundle())
107 doVerify(dot);
Stuart McCulloched3bd712009-09-03 01:55:34 +0000108
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000109 if (dot.getResources().isEmpty())
110 error("The JAR is empty: " + dot.getName());
Stuart McCulloched3bd712009-09-03 01:55:34 +0000111
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000112 dot.updateModified(lastModified(), "Last Modified Processor");
113 dot.setName(getBsn());
Stuart McCulloched3bd712009-09-03 01:55:34 +0000114
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000115 sign(dot);
Stuart McCulloched3bd712009-09-03 01:55:34 +0000116
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000117 doSaveManifest(dot);
118 return dot;
119 }
Stuart McCulloched3bd712009-09-03 01:55:34 +0000120
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000121 /**
122 * Allow any local initialization by subclasses before we build. Default is
123 * do nothing.
124 */
125 public void init() throws Exception {
126
127 }
128
129 /**
130 * Turn this normal bundle in a web and add any resources.
131 *
132 * @throws Exception
133 */
134 private Jar doWab(Jar dot) throws Exception {
135 String wab = getProperty(WAB);
136 String wablib = getProperty(WABLIB);
137 if (wab == null && wablib == null)
138 return dot;
139
140 setProperty(BUNDLE_CLASSPATH, append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
141
142 Jar next = new Jar(dot.getName());
143 addClose(next);
144
145 for (Map.Entry<String, Resource> entry : dot.getResources().entrySet()) {
146 String path = entry.getKey();
147 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
148 trace("wab: moving: %s", path);
149 next.putResource("WEB-INF/classes/" + path, entry.getValue());
150 } else {
151 trace("wab: not moving: %s", path);
152 next.putResource(path, entry.getValue());
153 }
154 }
155
156 Map<String, Map<String, String>> clauses = parseHeader(getProperty(WABLIB));
157 for (String key : clauses.keySet()) {
158 File f = getFile(key);
159 addWabLib(next, f);
160 }
161 doIncludeResource(next, wab);
162 return next;
163 }
164
165 /**
166 * Add a wab lib to the jar.
167 *
168 * @param f
169 */
170 private void addWabLib(Jar dot, File f) throws Exception {
171 if (f.exists()) {
172 Jar jar = new Jar(f);
173 jar.setDoNotTouchManifest();
174 addClose(jar);
175 String path = "WEB-INF/lib/" + f.getName();
176 dot.putResource(path, new JarResource(jar));
177 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
178
179 Manifest m = jar.getManifest();
180 String cp = m.getMainAttributes().getValue("Class-Path");
181 if (cp != null) {
182 Collection<String> parts = split(cp, ",");
183 for (String part : parts) {
184 File sub = getFile(f.getParentFile(), part);
185 if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
186 warning(
187 "Invalid Class-Path entry %s in %s, must exist and must reside in same directory",
188 sub, f);
189 } else {
190 addWabLib(dot, sub);
191 }
192 }
193 }
194 } else {
195 error("WAB lib does not exist %s", f);
196 }
197 }
198
199 /**
200 * Get the manifest and write it out separately if -savemanifest is set
201 *
202 * @param dot
203 */
204 private void doSaveManifest(Jar dot) throws IOException {
205 String output = getProperty(SAVEMANIFEST);
206 if (output == null)
207 return;
208
209 File f = getFile(output);
210 if (f.isDirectory()) {
211 f = new File(f, "MANIFEST.MF");
212 }
213 f.delete();
214 f.getParentFile().mkdirs();
215 OutputStream out = new FileOutputStream(f);
216 try {
217 Jar.writeManifest(dot.getManifest(), out);
218 } finally {
219 out.close();
220 }
221 changedFile(f);
222 }
223
224 protected void changedFile(File f) {
225 }
226
227 /**
228 * Sign the jar file.
229 *
230 * -sign : <alias> [ ';' 'password:=' <password> ] [ ';' 'keystore:='
231 * <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
232 *
233 * @return
234 */
235
236 void sign(Jar jar) throws Exception {
237 String signing = getProperty("-sign");
238 if (signing == null)
239 return;
240
241 trace("Signing %s, with %s", getBsn(), signing);
242 List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
243
244 Map<String, Map<String, String>> infos = parseHeader(signing);
245 for (Map.Entry<String, Map<String, String>> entry : infos.entrySet()) {
246 for (SignerPlugin signer : signers) {
247 signer.sign(this, entry.getKey());
248 }
249 }
250 }
251
252 public boolean hasSources() {
253 return isTrue(getProperty(SOURCES));
254 }
255
256 protected String getImportPackages() {
257 String ip = super.getImportPackages();
258 if (ip != null)
259 return ip;
260
261 return "*";
262 }
263
264 private void doConditional(Jar dot) throws Exception {
265 Map<String, Map<String, String>> conditionals = getHeader(CONDITIONAL_PACKAGE);
266 if (conditionals.isEmpty())
267 return;
268
269 int size;
270 do {
271 size = dot.getDirectories().size();
272 analyze();
273 analyzed = false;
274 Map<String, Map<String, String>> imports = getImports();
275
276 // Match the packages specified in conditionals
277 // against the imports. Any match must become a
278 // Private-Package
279 Map<String, Map<String, String>> filtered = merge(CONDITIONAL_PACKAGE, conditionals,
280 imports, new HashSet<String>(), null);
281
282 // Imports can also specify a private import. These
283 // packages must also be copied to the bundle
284 for (Map.Entry<String, Map<String, String>> entry : getImports().entrySet()) {
285 String type = entry.getValue().get(IMPORT_DIRECTIVE);
286 if (type != null && type.equals("private"))
287 filtered.put(entry.getKey(), entry.getValue());
288 }
289
290 // remove existing packages to prevent merge errors
291 filtered.keySet().removeAll(dot.getPackages());
292 doExpand(dot, CONDITIONAL_PACKAGE + " Private imports", Instruction
293 .replaceWithInstruction(filtered), false);
294 } while (dot.getDirectories().size() > size);
295 analyzed = true;
296 }
297
298 /**
299 * Intercept the call to analyze and cleanup versions after we have analyzed
300 * the setup. We do not want to cleanup if we are going to verify.
301 */
302
303 public void analyze() throws Exception {
304 super.analyze();
305 cleanupVersion(imports);
306 cleanupVersion(exports);
307 String version = getProperty(BUNDLE_VERSION);
308 if (version != null)
309 setProperty(BUNDLE_VERSION, cleanupVersion(version));
310 }
311
312 public void cleanupVersion(Map<String, Map<String, String>> mapOfMap) {
313 for (Iterator<Map.Entry<String, Map<String, String>>> e = mapOfMap.entrySet().iterator(); e
314 .hasNext();) {
315 Map.Entry<String, Map<String, String>> entry = e.next();
316 Map<String, String> attributes = entry.getValue();
317 if (attributes.containsKey("version")) {
318 attributes.put("version", cleanupVersion(attributes.get("version")));
319 }
320 }
321 }
322
323 /**
324 *
Stuart McCulloched3bd712009-09-03 01:55:34 +0000325 */
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000326 private void addSources(Jar dot) {
327 if (!hasSources())
328 return;
Stuart McCulloched3bd712009-09-03 01:55:34 +0000329
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000330 Set<String> packages = new HashSet<String>();
Stuart McCulloched3bd712009-09-03 01:55:34 +0000331
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000332 try {
333 ByteArrayOutputStream out = new ByteArrayOutputStream();
334 getProperties().store(out, "Generated by BND, at " + new Date());
335 dot.putResource("OSGI-OPT/bnd.bnd", new EmbeddedResource(out.toByteArray(), 0));
336 out.close();
337 } catch (Exception e) {
338 error("Can not embed bnd file in JAR: " + e);
339 }
Stuart McCulloched3bd712009-09-03 01:55:34 +0000340
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000341 for (Iterator<String> cpe = classspace.keySet().iterator(); cpe.hasNext();) {
342 String path = cpe.next();
343 path = path.substring(0, path.length() - ".class".length()) + ".java";
344 String pack = getPackage(path).replace('.', '/');
345 if (pack.length() > 1)
346 pack = pack + "/";
347 boolean found = false;
348 String[] fixed = { "packageinfo", "package.html", "module-info.java",
349 "package-info.java" };
350 for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
351 File root = i.next();
352 File f = getFile(root, path);
353 if (f.exists()) {
354 found = true;
355 if (!packages.contains(pack)) {
356 packages.add(pack);
357 File bdir = getFile(root, pack);
358 for (int j = 0; j < fixed.length; j++) {
359 File ff = getFile(bdir, fixed[j]);
360 if (ff.isFile()) {
361 dot.putResource("OSGI-OPT/src/" + pack + fixed[j],
362 new FileResource(ff));
363 }
364 }
365 }
366 dot.putResource("OSGI-OPT/src/" + path, new FileResource(f));
367 }
368 }
369 if (!found) {
370 for (Jar jar : classpath) {
371 Resource resource = jar.getResource(path);
372 if (resource != null) {
373 dot.putResource("OSGI-OPT/src", resource);
374 } else {
375 resource = jar.getResource("OSGI-OPT/src/" + path);
376 if (resource != null) {
377 dot.putResource("OSGI-OPT/src", resource);
378 }
379 }
380 }
381 }
382 if (getSourcePath().isEmpty())
383 warning("Including sources but " + SOURCEPATH
384 + " does not contain any source directories ");
385 // TODO copy from the jars where they came from
386 }
387 }
Stuart McCulloched3bd712009-09-03 01:55:34 +0000388
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000389 boolean firstUse = true;
Stuart McCulloched3bd712009-09-03 01:55:34 +0000390
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000391 public Collection<File> getSourcePath() {
392 if (firstUse) {
393 firstUse = false;
394 String sp = getProperty(SOURCEPATH);
395 if (sp != null) {
396 Map<String, Map<String, String>> map = parseHeader(sp);
397 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
398 String file = i.next();
399 if (!isDuplicate(file)) {
400 File f = getFile(file);
401 if (!f.isDirectory()) {
402 error("Adding a sourcepath that is not a directory: " + f);
403 } else {
404 sourcePath.add(f);
405 }
406 }
407 }
408 }
409 }
410 return sourcePath;
411 }
Stuart McCulloched3bd712009-09-03 01:55:34 +0000412
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000413 private void doVerify(Jar dot) throws Exception {
414 Verifier verifier = new Verifier(dot, getProperties());
415 verifier.setPedantic(isPedantic());
Stuart McCulloched3bd712009-09-03 01:55:34 +0000416
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000417 // Give the verifier the benefit of our analysis
418 // prevents parsing the files twice
419 verifier.setClassSpace(classspace, contained, referred, uses);
420 verifier.verify();
421 getInfo(verifier);
422 }
Stuart McCulloched3bd712009-09-03 01:55:34 +0000423
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000424 private void doExpand(Jar jar) throws IOException {
425 if (getClasspath().size() == 0
426 && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
427 warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
Stuart McCulloched3bd712009-09-03 01:55:34 +0000428
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000429 Map<Instruction, Map<String, String>> privateMap = Instruction
430 .replaceWithInstruction(getHeader(PRIVATE_PACKAGE));
431 Map<Instruction, Map<String, String>> exportMap = Instruction
432 .replaceWithInstruction(getHeader(EXPORT_PACKAGE));
Stuart McCulloched3bd712009-09-03 01:55:34 +0000433
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000434 if (isTrue(getProperty(Constants.UNDERTEST))) {
435 privateMap.putAll(Instruction.replaceWithInstruction(parseHeader(getProperty(
436 Constants.TESTPACKAGES, "test;presence:=optional"))));
437 }
438 if (!privateMap.isEmpty())
439 doExpand(jar, "Private-Package, or -testpackages", privateMap, true);
Stuart McCulloched3bd712009-09-03 01:55:34 +0000440
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000441 if (!exportMap.isEmpty() ) {
442 Jar exports = new Jar("exports");
443 doExpand(exports, EXPORT_PACKAGE, exportMap, true);
444 jar.addAll(exports);
445 exports.close();
446 }
447
448 if (!isNoBundle()) {
449 if (privateMap.isEmpty() && exportMap.isEmpty() && !isResourceOnly()
450 && getProperty(EXPORT_CONTENTS) == null) {
451 warning("None of Export-Package, Provide-Package, Private-Package, -testpackages, or -exportcontents is set, therefore no packages will be included");
452 }
453 }
454 }
455
456 /**
457 *
458 * @param jar
459 * @param name
460 * @param instructions
461 */
462 private void doExpand(Jar jar, String name, Map<Instruction, Map<String, String>> instructions,
463 boolean mandatory) {
464 Set<Instruction> superfluous = removeMarkedDuplicates(instructions.keySet());
465
466 for (Iterator<Jar> c = getClasspath().iterator(); c.hasNext();) {
467 Jar now = c.next();
468 doExpand(jar, instructions, now, superfluous);
469 }
470
471 if (mandatory && superfluous.size() > 0) {
472 StringBuilder sb = new StringBuilder();
473 String del = "Instructions in " + name + " that are never used: ";
474 for (Iterator<Instruction> i = superfluous.iterator(); i.hasNext();) {
475 Instruction p = i.next();
476 sb.append(del);
477 sb.append(p.toString());
478 del = "\n ";
479 }
480 sb.append("\nClasspath: ");
481 sb.append(Processor.join(getClasspath()));
482 sb.append("\n");
483
484 warning(sb.toString());
485 if (isPedantic())
486 diagnostics = true;
487 }
488 }
489
490 /**
491 * Iterate over each directory in the class path entry and check if that
492 * directory is a desired package.
493 *
494 * @param included
495 * @param classpathEntry
496 */
497 private void doExpand(Jar jar, Map<Instruction, Map<String, String>> included,
498 Jar classpathEntry, Set<Instruction> superfluous) {
499
500 loop: for (Map.Entry<String, Map<String, Resource>> directory : classpathEntry
501 .getDirectories().entrySet()) {
502 String path = directory.getKey();
503
504 if (doNotCopy.matcher(getName(path)).matches())
505 continue;
506
507 if (directory.getValue() == null)
508 continue;
509
510 String pack = path.replace('/', '.');
511 Instruction instr = matches(included.keySet(), pack, superfluous);
512 if (instr != null) {
513 // System.out.println("Pattern match: " + pack + " " +
514 // instr.getPattern() + " " + instr.isNegated());
515 if (!instr.isNegated()) {
516 Map<String, Resource> contents = directory.getValue();
517
518 // What to do with split packages? Well if this
519 // directory already exists, we will check the strategy
520 // and react accordingly.
521 boolean overwriteResource = true;
522 if (jar.hasDirectory(path)) {
523 Map<String, String> directives = included.get(instr);
524
525 switch (getSplitStrategy((String) directives.get(SPLIT_PACKAGE_DIRECTIVE))) {
526 case SPLIT_MERGE_LAST:
527 overwriteResource = true;
528 break;
529
530 case SPLIT_MERGE_FIRST:
531 overwriteResource = false;
532 break;
533
534 case SPLIT_ERROR:
535 error(diagnostic(pack, getClasspath(), classpathEntry.source));
536 continue loop;
537
538 case SPLIT_FIRST:
539 continue loop;
540
541 default:
542 warning(diagnostic(pack, getClasspath(), classpathEntry.source));
543 overwriteResource = false;
544 break;
545 }
546 }
547
548 jar.addDirectory(contents, overwriteResource);
549
550 String key = path + "/bnd.info";
551 Resource r = jar.getResource(key);
552 if (r != null)
553 jar.putResource(key, new PreprocessResource(this, r));
554
555 if (hasSources()) {
556 String srcPath = "OSGI-OPT/src/" + path;
557 Map<String, Resource> srcContents = classpathEntry.getDirectories().get(
558 srcPath);
559 if (srcContents != null) {
560 jar.addDirectory(srcContents, overwriteResource);
561 }
562 }
563 }
564 }
565 }
566 }
567
568 /**
569 * Analyze the classpath for a split package
570 *
571 * @param pack
572 * @param classpath
573 * @param source
574 * @return
575 */
576 private String diagnostic(String pack, List<Jar> classpath, File source) {
577 // Default is like merge-first, but with a warning
578 // Find the culprits
579 pack = pack.replace('.', '/');
580 List<Jar> culprits = new ArrayList<Jar>();
581 for (Iterator<Jar> i = classpath.iterator(); i.hasNext();) {
582 Jar culprit = (Jar) i.next();
583 if (culprit.getDirectories().containsKey(pack)) {
584 culprits.add(culprit);
585 }
586 }
587 return "Split package "
588 + pack
589 + "\nUse directive -split-package:=(merge-first|merge-last|error|first) on Export/Private Package instruction to get rid of this warning\n"
590 + "Package found in " + culprits + "\n" + "Reference from " + source + "\n"
591 + "Classpath " + classpath;
592 }
593
594 private int getSplitStrategy(String type) {
595 if (type == null)
596 return SPLIT_DEFAULT;
597
598 if (type.equals("merge-last"))
599 return SPLIT_MERGE_LAST;
600
601 if (type.equals("merge-first"))
602 return SPLIT_MERGE_FIRST;
603
604 if (type.equals("error"))
605 return SPLIT_ERROR;
606
607 if (type.equals("first"))
608 return SPLIT_FIRST;
609
610 error("Invalid strategy for split-package: " + type);
611 return SPLIT_DEFAULT;
612 }
613
614 private Instruction matches(Collection<Instruction> instructions, String pack,
615 Set<Instruction> superfluousPatterns) {
616 for (Instruction pattern : instructions) {
617 if (pattern.matches(pack)) {
618 if (superfluousPatterns != null)
619 superfluousPatterns.remove(pattern);
620 return pattern;
621 }
622 }
623 return null;
624 }
625
626 private Map<String, Map<String, String>> getHeader(String string) {
627 if (string == null)
628 return Collections.emptyMap();
629 return parseHeader(getProperty(string));
630 }
631
632 /**
633 * Parse the Bundle-Includes header. Files in the bundles Include header are
634 * included in the jar. The source can be a directory or a file.
635 *
636 * @throws IOException
637 * @throws FileNotFoundException
638 */
639 private void doIncludeResources(Jar jar) throws Exception {
640 String includes = getProperty("Bundle-Includes");
641 if (includes == null) {
642 includes = getProperty(INCLUDERESOURCE);
643 if (includes == null || includes.length() == 0)
644 includes = getProperty("Include-Resource");
645 } else
646 warning("Please use -includeresource instead of Bundle-Includes");
647
648 doIncludeResource(jar, includes);
649
650 }
651
652 private void doIncludeResource(Jar jar, String includes) throws Exception {
653 Map<String, Map<String, String>> clauses = parseHeader(includes);
654 doIncludeResource(jar, clauses);
655 }
656
657 private void doIncludeResource(Jar jar, Map<String, Map<String, String>> clauses)
658 throws ZipException, IOException, Exception {
659 for (Map.Entry<String, Map<String, String>> entry : clauses.entrySet()) {
660 doIncludeResource(jar, entry.getKey(), entry.getValue());
661 }
662 }
663
664 private void doIncludeResource(Jar jar, String name, Map<String, String> extra)
665 throws ZipException, IOException, Exception {
666 boolean preprocess = false;
667 if (name.startsWith("{") && name.endsWith("}")) {
668 preprocess = true;
669 name = name.substring(1, name.length() - 1).trim();
670 }
671
672 String parts[] = name.split("\\s*=\\s*");
673 String source = parts[0];
674 String destination = parts[0];
675 if (parts.length == 2)
676 source = parts[1];
677
678 if (source.startsWith("@")) {
679 extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination);
680 } else if (extra.containsKey("literal")) {
681 String literal = (String) extra.get("literal");
682 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
683 String x = (String) extra.get("extra");
684 if (x != null)
685 r.setExtra(x);
686 jar.putResource(name, r);
687 } else {
688 File sourceFile;
689 String destinationPath;
690
691 sourceFile = getFile(source);
692 if (parts.length == 1) {
693 // Directories should be copied to the root
694 // but files to their file name ...
695 if (sourceFile.isDirectory())
696 destinationPath = "";
697 else
698 destinationPath = sourceFile.getName();
699 } else {
700 destinationPath = parts[0];
701 }
702 // Handle directories
703 if (sourceFile.isDirectory()) {
704 destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile,
705 destinationPath);
706 return;
707 }
708
709 // destinationPath = checkDestinationPath(destinationPath);
710
711 if (!sourceFile.exists()) {
712 noSuchFile(jar, name, extra, source, destinationPath);
713 } else
714 copy(jar, destinationPath, sourceFile, preprocess, extra);
715 }
716 }
717
718 private String doResourceDirectory(Jar jar, Map<String, String> extra, boolean preprocess,
719 File sourceFile, String destinationPath) throws Exception {
720 String filter = extra.get("filter:");
721 boolean flatten = isTrue(extra.get("flatten:"));
722 boolean recursive = true;
723 String directive = extra.get("recursive:");
724 if (directive != null) {
725 recursive = isTrue(directive);
726 }
727
728 InstructionFilter iFilter = null;
729 if (filter != null) {
730 iFilter = new InstructionFilter(Instruction.getPattern(filter), recursive);
731 } else {
732 iFilter = new InstructionFilter(null, recursive);
733 }
734
735 Map<String, File> files = newMap();
736 resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
737
738 for (Map.Entry<String, File> entry : files.entrySet()) {
739 copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
740 }
741 return destinationPath;
742 }
743
744 private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path,
745 Map<String, File> files, boolean flatten) {
746
747 if (Analyzer.doNotCopy.matcher(dir.getName()).matches()) {
748 return;
749 }
750
751 File[] fs = dir.listFiles(filter);
752 for (File file : fs) {
753 if (file.isDirectory()) {
754 if (recursive) {
755 String nextPath;
756 if (flatten)
757 nextPath = path;
758 else
759 nextPath = appendPath(path, file.getName());
760
761 resolveFiles(file, filter, recursive, nextPath, files, flatten);
762 }
763 // Directories are ignored otherwise
764 } else {
765 String p = appendPath(path, file.getName());
766 if (files.containsKey(p))
767 warning("Include-Resource overwrites entry %s from file %s", p, file);
768 files.put(p, file);
769 }
770 }
771 }
772
773 private void noSuchFile(Jar jar, String clause, Map<String, String> extra, String source,
774 String destinationPath) throws Exception {
775 Jar src = getJarFromName(source, "Include-Resource " + source);
776 if (src != null) {
777 JarResource jarResource = new JarResource(src);
778 jar.putResource(destinationPath, jarResource);
779 } else {
780 Resource lastChance = make.process(source);
781 if (lastChance != null) {
782 String x = extra.get("extra");
783 if (x != null)
784 lastChance.setExtra(x);
785 jar.putResource(destinationPath, lastChance);
786 } else
787 error("Input file does not exist: " + source);
788 }
789 }
790
791 /**
792 * Extra resources from a Jar and add them to the given jar. The clause is
793 * the
794 *
795 * @param jar
796 * @param clauses
797 * @param i
798 * @throws ZipException
799 * @throws IOException
800 */
801 private void extractFromJar(Jar jar, String source, String destination) throws ZipException,
802 IOException {
803 // Inline all resources and classes from another jar
804 // optionally appended with a modified regular expression
805 // like @zip.jar!/META-INF/MANIFEST.MF
806 int n = source.lastIndexOf("!/");
807 Instruction instr = null;
808 if (n > 0) {
809 instr = Instruction.getPattern(source.substring(n + 2));
810 source = source.substring(0, n);
811 }
812
813 // Pattern filter = null;
814 // if (n > 0) {
815 // String fstring = source.substring(n + 2);
816 // source = source.substring(0, n);
817 // filter = wildcard(fstring);
818 // }
819 Jar sub = getJarFromName(source, "extract from jar");
820 if (sub == null)
821 error("Can not find JAR file " + source);
822 else {
823 jar.addAll(sub, instr, destination);
824 }
825 }
826
827 private void copy(Jar jar, String path, File from, boolean preprocess, Map<String, String> extra)
828 throws Exception {
829 if (doNotCopy.matcher(from.getName()).matches())
830 return;
831
832 if (from.isDirectory()) {
833
834 File files[] = from.listFiles();
835 for (int i = 0; i < files.length; i++) {
836 copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
837 }
838 } else {
839 if (from.exists()) {
840 Resource resource = new FileResource(from);
841 if (preprocess) {
842 resource = new PreprocessResource(this, resource);
843 }
844 String x = extra.get("extra");
845 if (x != null)
846 resource.setExtra(x);
847 if (path.endsWith("/"))
848 path = path + from.getName();
849 jar.putResource(path, resource);
850
851 if (isTrue(extra.get(LIB_DIRECTIVE))) {
852 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
853 }
854 } else {
855 error("Input file does not exist: " + from);
856 }
857 }
858 }
859
860 private String getName(String where) {
861 int n = where.lastIndexOf('/');
862 if (n < 0)
863 return where;
864
865 return where.substring(n + 1);
866 }
867
868 public void setSourcepath(File[] files) {
869 for (int i = 0; i < files.length; i++)
870 addSourcepath(files[i]);
871 }
872
873 public void addSourcepath(File cp) {
874 if (!cp.exists())
875 warning("File on sourcepath that does not exist: " + cp);
876
877 sourcePath.add(cp);
878 }
879
880 public void close() {
881 super.close();
882 }
883
884 /**
885 * Build Multiple jars. If the -sub command is set, we filter the file with
886 * the given patterns.
887 *
888 * @return
889 * @throws Exception
890 */
891 public Jar[] builds() throws Exception {
892 begin();
893
894 // Are we acting as a conduit for another JAR?
895 String conduit = getProperty(CONDUIT);
896 if (conduit != null) {
897 Map<String, Map<String, String>> map = parseHeader(conduit);
898 Jar[] result = new Jar[map.size()];
899 int n = 0;
900 for (String file : map.keySet()) {
901 Jar c = new Jar(getFile(file));
902 addClose(c);
903 String name = map.get(file).get("name");
904 if (name != null)
905 c.setName(name);
906
907 result[n++] = c;
908 }
909 return result;
910 }
911
912 List<Jar> result = new ArrayList<Jar>();
913 List<Builder> builders;
914
915 builders = getSubBuilders();
916
917 for (Builder builder : builders) {
918 try {
919 Jar jar = builder.build();
920 jar.setName(builder.getBsn());
921 result.add(jar);
922 } catch (Exception e) {
923 error("Sub Building " + builder.getBsn(), e);
924 }
925 if (builder != this)
926 getInfo(builder, builder.getBsn() + ": ");
927 }
928 return result.toArray(new Jar[result.size()]);
929 }
930
931 /**
932 * Answer a list of builders that represent this file or a list of files
933 * specified in -sub. This list can be empty. These builders represents to
934 * be created artifacts and are each scoped to such an artifacts. The
935 * builders can be used to build the bundles or they can be used to find out
936 * information about the to be generated bundles.
937 *
938 * @return List of 0..n builders representing artifacts.
939 * @throws Exception
940 */
941 public List<Builder> getSubBuilders() throws Exception {
942 String sub = (String) getProperty(SUB);
943 if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
944 return Arrays.asList(this);
945
946 List<Builder> builders = new ArrayList<Builder>();
947 if (isTrue(getProperty(NOBUNDLES)))
948 return builders;
949
950 Set<Instruction> subs = Instruction.replaceWithInstruction(parseHeader(sub)).keySet();
951
952 List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
953
954 nextFile: while (members.size() > 0) {
955
956 File file = members.remove(0);
957
958 // Check if the file is one of our parents
959 Processor p = this;
960 while (p != null) {
961 if (file.equals(p.getPropertiesFile()))
962 continue nextFile;
963 p = p.getParent();
964 }
965
966 // if
967 // (file.getCanonicalFile().equals(getPropertiesFile().getCanonicalFile()))
968 // continue nextFile;
969
970 for (Iterator<Instruction> i = subs.iterator(); i.hasNext();) {
971
972 Instruction instruction = i.next();
973 if (instruction.matches(file.getName())) {
974
975 if (!instruction.isNegated()) {
976
977 Builder builder = getSubBuilder();
978 if (builder != null) {
979 builder.setProperties(file);
980 addClose(builder);
981 builders.add(builder);
982 }
983 }
984
985 // Because we matched (even though we could be negated)
986 // we skip any remaining searches
987 continue nextFile;
988 }
989 }
990 }
991 return builders;
992 }
993
994 public Builder getSubBuilder() throws Exception {
995 Builder builder = new Builder(this);
996 builder.setBase(getBase());
997
998 for (Jar file : getClasspath()) {
999 builder.addClasspath(file);
1000 }
1001
1002 return builder;
1003 }
1004
1005 /**
1006 * A macro to convert a maven version to an OSGi version
1007 */
1008
1009 public String _maven_version(String args[]) {
1010 if (args.length > 2)
1011 error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
1012 else if (args.length < 2)
1013 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1014 else {
1015 return cleanupVersion(args[1]);
1016 }
1017 return null;
1018 }
1019
1020 public String _permissions(String args[]) throws IOException {
1021 StringBuilder sb = new StringBuilder();
1022
1023 for (String arg : args) {
1024 if ("packages".equals(arg) || "all".equals(arg)) {
1025 for (String imp : getImports().keySet()) {
1026 if (!imp.startsWith("java.")) {
1027 sb.append("(org.osgi.framework.PackagePermission \"");
1028 sb.append(imp);
1029 sb.append("\" \"import\")\r\n");
1030 }
1031 }
1032 for (String exp : getExports().keySet()) {
1033 sb.append("(org.osgi.framework.PackagePermission \"");
1034 sb.append(exp);
1035 sb.append("\" \"export\")\r\n");
1036 }
1037 } else if ("admin".equals(arg) || "all".equals(arg)) {
1038 sb.append("(org.osgi.framework.AdminPermission)");
1039 } else if ("permissions".equals(arg))
1040 ;
1041 else
1042 error("Invalid option in ${permissions}: %s", arg);
1043 }
1044 return sb.toString();
1045 }
1046
1047 /**
1048 *
Stuart McCulloched3bd712009-09-03 01:55:34 +00001049 */
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001050 public void removeBundleSpecificHeaders() {
1051 Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
1052 setForceLocal(set);
1053 }
Stuart McCulloched3bd712009-09-03 01:55:34 +00001054
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001055 /**
1056 * Check if the given resource is in scope of this bundle. That is, it
1057 * checks if the Include-Resource includes this resource or if it is a class
1058 * file it is on the class path and the Export-Pacakge or Private-Package
1059 * include this resource.
1060 *
1061 * For now, include resources are skipped.
1062 *
1063 * @param f
1064 * @return
1065 */
1066 public boolean isInScope(Collection<File> resources) throws Exception {
1067 Map<String, Map<String, String>> clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
1068 clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
1069 if (isTrue(getProperty(Constants.UNDERTEST))) {
1070 clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES,
1071 "test;presence:=optional")));
1072 }
1073 Collection<Instruction> instructions = Instruction.replaceWithInstruction(clauses).keySet();
Stuart McCulloched3bd712009-09-03 01:55:34 +00001074
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001075 for (File r : resources) {
1076 String cpEntry = getClasspathEntrySuffix(r);
1077 if (cpEntry != null) {
1078 String pack = Clazz.getPackage(cpEntry);
1079 Instruction i = matches(instructions, pack, null);
1080 if (i != null)
1081 return !i.isNegated();
1082 }
1083 }
1084 return false;
1085 }
Stuart McCulloched3bd712009-09-03 01:55:34 +00001086
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001087 /**
1088 * Answer the string of the resource that it has in the container.
1089 *
1090 * @param resource
1091 * The resource to look for
1092 * @return
1093 * @throws Exception
1094 */
1095 public String getClasspathEntrySuffix(File resource) throws Exception {
1096 for (Jar jar : getClasspath()) {
1097 File source = jar.getSource();
1098 if (source != null) {
1099 source = source.getCanonicalFile();
1100 String sourcePath = source.getAbsolutePath();
1101 String resourcePath = resource.getAbsolutePath();
Stuart McCulloched3bd712009-09-03 01:55:34 +00001102
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001103 if (resourcePath.startsWith(sourcePath)) {
1104 // Make sure that the path name is translated correctly
1105 // i.e. on Windows the \ must be translated to /
1106 String filePath = resourcePath.substring(sourcePath.length() + 1);
Stuart McCulloched3bd712009-09-03 01:55:34 +00001107
Richard S. Hall2c9e5922010-10-25 19:07:06 +00001108 return filePath.replace(File.separatorChar, '/');
1109 }
1110 }
1111 }
1112 return null;
1113 }
Stuart McCulloched3bd712009-09-03 01:55:34 +00001114
1115}