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