blob: 7c631ae32d3b771e2aff3aa36336dbc2520c49f4 [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,
Stuart McCullochef1af6c2009-01-29 08:20:51 +0000269 new HashSet<String>(), null);
Stuart McCulloch5ec302d2008-12-04 07:58:07 +0000270
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");
Stuart McCulloch14173442009-01-29 07:46:54 +0000650 if (includes == null) {
651 includes = getProperty(INCLUDERESOURCE);
652 if ( includes == null )
653 includes = getProperty("Include-Resource");
654 }
Stuart McCulloch5ec302d2008-12-04 07:58:07 +0000655 else
Stuart McCulloch14173442009-01-29 07:46:54 +0000656 warning("Please use -includeresource instead of Bundle-Includes");
Stuart McCulloch5ec302d2008-12-04 07:58:07 +0000657
658 if (includes == null)
659 return;
660
661 Map<String, Map<String, String>> clauses = parseHeader(includes);
662
663 for (Iterator<Map.Entry<String, Map<String, String>>> i = clauses
664 .entrySet().iterator(); i.hasNext();) {
665 Map.Entry<String, Map<String, String>> entry = i.next();
666 doIncludeResource(jar, entry.getKey(), entry.getValue());
667 }
668 }
669
670 private void doIncludeResource(Jar jar, String name,
671 Map<String, String> extra) throws ZipException, IOException,
672 Exception {
673 boolean preprocess = false;
674 if (name.startsWith("{") && name.endsWith("}")) {
675 preprocess = true;
676 name = name.substring(1, name.length() - 1).trim();
677 }
678
679 if (name.startsWith("@")) {
680 extractFromJar(jar, name.substring(1));
681 } else
682 /*
683 * NEW
684 */
685 if (extra.containsKey("literal")) {
686 String literal = (String) extra.get("literal");
687 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
688 String x = (String) extra.get("extra");
689 if (x != null)
690 r.setExtra(x);
691 jar.putResource(name, r);
692 } else {
693 String source;
694 File sourceFile;
695 String destinationPath;
696
697 String parts[] = name.split("\\s*=\\s*");
698 if (parts.length == 1) {
699 // Just a copy, destination path defined by
700 // source path.
701 source = parts[0];
702 sourceFile = getFile(source);
703 // Directories should be copied to the root
704 // but files to their file name ...
705 if (sourceFile.isDirectory())
706 destinationPath = "";
707 else
708 destinationPath = sourceFile.getName();
709 } else {
710 source = parts[1];
711 sourceFile = getFile(source);
712 destinationPath = parts[0];
713 }
714
715 // Some people insist on ending a directory with
716 // a slash ... it now also works if you do /=dir
717 if (destinationPath.endsWith("/"))
718 destinationPath = destinationPath.substring(0, destinationPath
719 .length() - 1);
720
721 if (!sourceFile.exists()) {
722 noSuchFile(jar, name, extra, source, destinationPath);
723 } else
724 copy(jar, destinationPath, sourceFile, preprocess, extra);
725 }
726 }
727
728 private void noSuchFile(Jar jar, String clause, Map<String, String> extra,
729 String source, String destinationPath) throws Exception {
730 Jar src = getJarFromName(source, "Include-Resource " + source);
731 if (src != null) {
732 JarResource jarResource = new JarResource(src);
733 jar.putResource(destinationPath, jarResource);
734 } else {
735 Resource lastChance = make.process(source);
736 if (lastChance != null) {
737 jar.putResource(destinationPath, lastChance);
738 } else
739 error("Input file does not exist: " + source);
740 }
741 }
742
743 /**
744 * Extra resources from a Jar and add them to the given jar. The clause is
745 * the
746 *
747 * @param jar
748 * @param clauses
749 * @param i
750 * @throws ZipException
751 * @throws IOException
752 */
753 private void extractFromJar(Jar jar, String name) throws ZipException,
754 IOException {
755 // Inline all resources and classes from another jar
756 // optionally appended with a modified regular expression
757 // like @zip.jar!/META-INF/MANIFEST.MF
758 int n = name.lastIndexOf("!/");
759 Pattern filter = null;
760 if (n > 0) {
761 String fstring = name.substring(n + 2);
762 name = name.substring(0, n);
763 filter = wildcard(fstring);
764 }
765 Jar sub = getJarFromName(name, "extract from jar");
766 if (sub == null)
767 error("Can not find JAR file " + name);
768 else
769 jar.addAll(sub, filter);
770 }
771
772 private Pattern wildcard(String spec) {
773 StringBuffer sb = new StringBuffer();
774 for (int j = 0; j < spec.length(); j++) {
775 char c = spec.charAt(j);
776 switch (c) {
777 case '.':
778 sb.append("\\.");
779 break;
780
781 case '*':
782 // test for ** (all directories)
783 if (j < spec.length() - 1 && spec.charAt(j + 1) == '*') {
784 sb.append(".*");
785 j++;
786 } else
787 sb.append("[^/]*");
788 break;
789 default:
790 sb.append(c);
791 break;
792 }
793 }
794 String s = sb.toString();
795 try {
796 return Pattern.compile(s);
797 } catch (Exception e) {
798 error("Invalid regular expression on wildcarding: " + spec
799 + " used *");
800 }
801 return null;
802 }
803
804 private void copy(Jar jar, String path, File from, boolean preprocess,
805 Map<String, String> extra) throws Exception {
806 if (doNotCopy.matcher(from.getName()).matches())
807 return;
808
809 if (from.isDirectory()) {
810 String next = path;
811 if (next.length() != 0)
812 next += '/';
813
814 File files[] = from.listFiles();
815 for (int i = 0; i < files.length; i++) {
816 copy(jar, next + files[i].getName(), files[i], preprocess,
817 extra);
818 }
819 } else {
820 if (from.exists()) {
821 Resource resource = new FileResource(from);
822 if (preprocess) {
823 resource = new PreprocessResource(this, resource);
824 }
825 jar.putResource(path, resource);
826 } else {
827 error("Input file does not exist: " + from);
828 }
829 }
830 }
831
832 private String getName(String where) {
833 int n = where.lastIndexOf('/');
834 if (n < 0)
835 return where;
836
837 return where.substring(n + 1);
838 }
839
840 public void setSourcepath(File[] files) {
841 for (int i = 0; i < files.length; i++)
842 addSourcepath(files[i]);
843 }
844
845 public void addSourcepath(File cp) {
846 if (!cp.exists())
847 warning("File on sourcepath that does not exist: " + cp);
848
849 sourcePath.add(cp);
850 }
851
852 /**
853 * Create a POM reseource for Maven containing as much information as
854 * possible from the manifest.
855 *
856 * @param output
857 * @param builder
858 * @throws FileNotFoundException
859 * @throws IOException
860 */
861 public void doPom(Jar dot) throws FileNotFoundException, IOException {
862 {
863 Manifest manifest = dot.getManifest();
864 String name = manifest.getMainAttributes().getValue(
865 Analyzer.BUNDLE_NAME);
866 String description = manifest.getMainAttributes().getValue(
867 Analyzer.BUNDLE_DESCRIPTION);
868 String docUrl = manifest.getMainAttributes().getValue(
869 Analyzer.BUNDLE_DOCURL);
870 String version = manifest.getMainAttributes().getValue(
871 Analyzer.BUNDLE_VERSION);
872 String bundleVendor = manifest.getMainAttributes().getValue(
873 Analyzer.BUNDLE_VENDOR);
874 ByteArrayOutputStream s = new ByteArrayOutputStream();
875 PrintStream ps = new PrintStream(s);
876 String bsn = manifest.getMainAttributes().getValue(
877 Analyzer.BUNDLE_SYMBOLICNAME);
878 String licenses = manifest.getMainAttributes().getValue(
879 BUNDLE_LICENSE);
880
881 if (bsn == null) {
882 error("Can not create POM unless Bundle-SymbolicName is set");
883 return;
884 }
885
886 bsn = bsn.trim();
887 int n = bsn.lastIndexOf('.');
888 if (n <= 0) {
889 error("Can not create POM unless Bundle-SymbolicName contains a .");
890 ps.close();
891 s.close();
892 return;
893 }
894 String groupId = bsn.substring(0, n);
895 String artifactId = bsn.substring(n + 1);
896 ps
897 .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'>");
898 ps.println(" <modelVersion>4.0.0</modelVersion>");
899 ps.println(" <groupId>" + groupId + "</groupId>");
900
901 n = artifactId.indexOf(';');
902 if (n > 0)
903 artifactId = artifactId.substring(0, n).trim();
904
905 ps.println(" <artifactId>" + artifactId + "</artifactId>");
906 ps.println(" <version>" + version + "</version>");
907 if (description != null) {
908 ps.println(" <description>");
909 ps.print(" ");
910 ps.println(description);
911 ps.println(" </description>");
912 }
913 if (name != null) {
914 ps.print(" <name>");
915 ps.print(name);
916 ps.println("</name>");
917 }
918 if (docUrl != null) {
919 ps.print(" <url>");
920 ps.print(docUrl);
921 ps.println("</url>");
922 }
923
924 if (bundleVendor != null) {
925 Matcher m = NAME_URL.matcher(bundleVendor);
926 String namePart = bundleVendor;
927 String urlPart = null;
928 if (m.matches()) {
929 namePart = m.group(1);
930 urlPart = m.group(2);
931 }
932 ps.println(" <organization>");
933 ps.print(" <name>");
934 ps.print(namePart.trim());
935 ps.println("</name>");
936 if (urlPart != null) {
937 ps.print(" <url>");
938 ps.print(urlPart.trim());
939 ps.println("</url>");
940 }
941 ps.println(" </organization>");
942 }
943 if (licenses != null) {
944 ps.println(" <licenses>");
945 Map<String, Map<String, String>> map = parseHeader(licenses);
946 for (Iterator<Map.Entry<String, Map<String, String>>> e = map
947 .entrySet().iterator(); e.hasNext();) {
948 Map.Entry<String, Map<String, String>> entry = e.next();
949 ps.println(" <license>");
950 Map<String, String> values = entry.getValue();
951 print(ps, values, "name", "name", (String) values
952 .get("url"));
953 print(ps, values, "url", "url", null);
954 print(ps, values, "distribution", "distribution", "repo");
955 ps.println(" </license>");
956 }
957 ps.println(" </licenses>");
958 }
959 ps.println("</project>");
960 ps.close();
961 s.close();
962 dot
963 .putResource("pom.xml", new EmbeddedResource(s
964 .toByteArray(), 0));
965 }
966 }
967
968 /**
969 * Utility function to print a tag from a map
970 *
971 * @param ps
972 * @param values
973 * @param string
974 * @param tag
975 * @param object
976 */
977 private void print(PrintStream ps, Map<String, String> values,
978 String string, String tag, String object) {
979 String value = (String) values.get(string);
980 if (value == null)
981 value = object;
982 if (value == null)
983 return;
984 ps.println(" <" + tag + ">" + value.trim() + "</" + tag + ">");
985 }
986
987 public void close() {
988 super.close();
989 }
990
991 /**
992 * Build Multiple jars. If the -sub command is set, we filter the file with
993 * the given patterns.
994 *
995 * @return
996 * @throws Exception
997 */
998 public Jar[] builds() throws Exception {
999 begin();
1000
1001 // Are we acting as a conduit for another JAR?
1002 String conduit = getProperty(CONDUIT);
1003 if (conduit != null) {
1004 Map<String, Map<String, String>> map = parseHeader(conduit);
1005 Jar[] result = new Jar[map.size()];
1006 int n = 0;
1007 for (String file : map.keySet()) {
1008 Jar c = new Jar(getFile(file));
1009 addClose(c);
1010 String name = map.get(file).get("name");
1011 if (name != null)
1012 c.setName(name);
1013
1014 result[n++] = c;
1015 }
1016 return result;
1017 }
1018
1019 // If no -sub property, then reuse this builder object
1020 // other wise, build all the sub parts.
1021 String sub = getProperty(SUB);
1022 if (sub == null) {
1023 Jar jar = build();
1024 if (jar == null)
1025 return new Jar[0];
1026
1027 return new Jar[] { jar };
1028 }
1029
1030 List<Jar> result = new ArrayList<Jar>();
1031
1032 // Get the Instruction objects that match the sub header
1033 Set<Instruction> subs = replaceWitInstruction(parseHeader(sub), SUB)
1034 .keySet();
1035
1036 // Get the member files of this directory
1037 List<File> members = new ArrayList<File>(Arrays.asList(getBase()
1038 .listFiles()));
1039
1040 getProperties().remove(SUB);
1041 // For each member file
1042 nextFile: while (members.size() > 0) {
1043
1044 File file = members.remove(0);
1045 if (file.equals(getPropertiesFile()))
1046 continue nextFile;
1047
1048 for (Iterator<Instruction> i = subs.iterator(); i.hasNext();) {
1049
1050 Instruction instruction = i.next();
1051 if (instruction.matches(file.getName())) {
1052
1053 if (!instruction.isNegated()) {
1054
1055 Builder builder = null;
1056 try {
1057 builder = getSubBuilder();
1058 addClose(builder);
1059 builder.setProperties(file);
1060 builder.setProperty(SUB, "");
1061 // Recursively build
1062 // TODO
1063 Jar jar = builder.build();
1064 jar.setName(builder.getBsn());
1065 result.add(jar);
1066 } catch (Exception e) {
1067 e.printStackTrace();
1068 error("Sub Building " + file, e);
1069 }
1070 if (builder != null)
1071 getInfo(builder, file.getName() + ": ");
1072 }
1073
1074 // Because we matched (even though we could be negated)
1075 // we skip any remaining searches
1076 continue nextFile;
1077 }
1078 }
1079 }
1080 setProperty(SUB, sub);
1081 return result.toArray(new Jar[result.size()]);
1082 }
1083
1084 protected Builder getSubBuilder() throws Exception {
1085 return new Builder(this);
1086 }
1087
1088 /**
1089 * A macro to convert a maven version to an OSGi version
1090 */
1091
1092 public String _maven_version(String args[]) {
1093 if (args.length > 2)
1094 error("${maven_version} macro receives too many arguments "
1095 + Arrays.toString(args));
1096 else if (args.length < 2)
1097 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1098 else {
1099 return cleanupVersion(args[1]);
1100 }
1101 return null;
1102 }
1103
1104 public String _permissions(String args[]) throws IOException {
1105 StringBuilder sb = new StringBuilder();
1106
1107 for (String arg : args) {
1108 if ("packages".equals(arg) || "all".equals(arg)) {
1109 for (String imp : getImports().keySet()) {
1110 if (!imp.startsWith("java.")) {
1111 sb.append("(org.osgi.framework.PackagePermission \"");
1112 sb.append(imp);
1113 sb.append("\" \"import\")\r\n");
1114 }
1115 }
1116 for (String exp : getExports().keySet()) {
1117 sb.append("(org.osgi.framework.PackagePermission \"");
1118 sb.append(exp);
1119 sb.append("\" \"export\")\r\n");
1120 }
1121 } else if ("admin".equals(arg) || "all".equals(arg)) {
1122 sb.append("(org.osgi.framework.AdminPermission)");
1123 } else if ("permissions".equals(arg))
1124 ;
1125 else
1126 error("Invalid option in ${permissions}: %s", arg);
1127 }
1128 return sb.toString();
1129 }
1130
1131}