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