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