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