blob: 9ee404863b501e640c3c55d303bf0ef76d3a8283 [file] [log] [blame]
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochbb014372012-06-07 21:57:32 +00002
3import static aQute.lib.io.IO.*;
4
5import java.io.*;
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00006import java.net.*;
Stuart McCullochbb014372012-06-07 21:57:32 +00007import java.security.*;
8import java.util.*;
9import java.util.jar.*;
10import java.util.regex.*;
11import java.util.zip.*;
12
13import aQute.lib.base64.*;
14import aQute.lib.io.*;
Stuart McCulloch81d48de2012-06-29 19:23:09 +000015import aQute.service.reporter.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000016
17public class Jar implements Closeable {
18 public enum Compression {
19 DEFLATE, STORE
20 }
21
Stuart McCulloch2286f232012-06-15 13:27:53 +000022 public static final Object[] EMPTY_ARRAY = new Jar[0];
23 final Map<String,Resource> resources = new TreeMap<String,Resource>();
24 final Map<String,Map<String,Resource>> directories = new TreeMap<String,Map<String,Resource>>();
25 Manifest manifest;
26 boolean manifestFirst;
27 String name;
28 File source;
29 ZipFile zipFile;
30 long lastModified;
31 String lastModifiedReason;
32 Reporter reporter;
33 boolean doNotTouchManifest;
34 boolean nomanifest;
35 Compression compression = Compression.DEFLATE;
36 boolean closed;
Stuart McCulloch6a046662012-07-19 13:11:20 +000037 String[] algorithms;
Stuart McCullochbb014372012-06-07 21:57:32 +000038
39 public Jar(String name) {
40 this.name = name;
41 }
42
43 public Jar(String name, File dirOrFile, Pattern doNotCopy) throws ZipException, IOException {
44 this(name);
45 source = dirOrFile;
46 if (dirOrFile.isDirectory())
47 FileResource.build(this, dirOrFile, doNotCopy);
48 else if (dirOrFile.isFile()) {
49 zipFile = ZipResource.build(this, dirOrFile);
50 } else {
Stuart McCulloch2286f232012-06-15 13:27:53 +000051 throw new IllegalArgumentException("A Jar can only accept a valid file or directory: " + dirOrFile);
Stuart McCullochbb014372012-06-07 21:57:32 +000052 }
53 }
54
55 public Jar(String name, InputStream in, long lastModified) throws IOException {
56 this(name);
57 EmbeddedResource.build(this, in, lastModified);
58 }
59
60 public Jar(String name, String path) throws IOException {
61 this(name);
62 File f = new File(path);
63 InputStream in = new FileInputStream(f);
64 EmbeddedResource.build(this, in, f.lastModified());
65 in.close();
66 }
67
68 public Jar(File f) throws IOException {
69 this(getName(f), f, null);
70 }
71
72 /**
73 * Make the JAR file name the project name if we get a src or bin directory.
74 *
75 * @param f
76 * @return
77 */
78 private static String getName(File f) {
79 f = f.getAbsoluteFile();
80 String name = f.getName();
81 if (name.equals("bin") || name.equals("src"))
82 return f.getParentFile().getName();
Stuart McCullochd4826102012-06-26 16:34:24 +000083 if (name.endsWith(".jar"))
84 name = name.substring(0, name.length() - 4);
85 return name;
Stuart McCullochbb014372012-06-07 21:57:32 +000086 }
87
88 public Jar(String string, InputStream resourceAsStream) throws IOException {
89 this(string, resourceAsStream, 0);
90 }
91
92 public Jar(String string, File file) throws ZipException, IOException {
93 this(string, file, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
94 }
95
96 public void setName(String name) {
97 this.name = name;
98 }
99
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000100 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000101 public String toString() {
102 return "Jar:" + name;
103 }
104
105 public boolean putResource(String path, Resource resource) {
106 check();
107 return putResource(path, resource, true);
108 }
109
110 public boolean putResource(String path, Resource resource, boolean overwrite) {
111 check();
112 updateModified(resource.lastModified(), path);
113 while (path.startsWith("/"))
114 path = path.substring(1);
115
116 if (path.equals("META-INF/MANIFEST.MF")) {
117 manifest = null;
118 if (resources.isEmpty())
119 manifestFirst = true;
120 }
121 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000122 Map<String,Resource> s = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000123 if (s == null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000124 s = new TreeMap<String,Resource>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000125 directories.put(dir, s);
126 int n = dir.lastIndexOf('/');
127 while (n > 0) {
128 String dd = dir.substring(0, n);
129 if (directories.containsKey(dd))
130 break;
131 directories.put(dd, null);
132 n = dd.lastIndexOf('/');
133 }
134 }
135 boolean duplicate = s.containsKey(path);
136 if (!duplicate || overwrite) {
137 resources.put(path, resource);
138 s.put(path, resource);
139 }
140 return duplicate;
141 }
142
143 public Resource getResource(String path) {
144 check();
145 if (resources == null)
146 return null;
147 return resources.get(path);
148 }
149
150 private String getDirectory(String path) {
151 check();
152 int n = path.lastIndexOf('/');
153 if (n < 0)
154 return "";
155
156 return path.substring(0, n);
157 }
158
Stuart McCulloch2286f232012-06-15 13:27:53 +0000159 public Map<String,Map<String,Resource>> getDirectories() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000160 check();
161 return directories;
162 }
163
Stuart McCulloch2286f232012-06-15 13:27:53 +0000164 public Map<String,Resource> getResources() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000165 check();
166 return resources;
167 }
168
Stuart McCulloch2286f232012-06-15 13:27:53 +0000169 public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000170 check();
171 boolean duplicates = false;
172 if (directory == null)
173 return false;
174
Stuart McCulloch2286f232012-06-15 13:27:53 +0000175 for (Map.Entry<String,Resource> entry : directory.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000176 String key = entry.getKey();
177 if (!key.endsWith(".java")) {
178 duplicates |= putResource(key, entry.getValue(), overwrite);
179 }
180 }
181 return duplicates;
182 }
183
184 public Manifest getManifest() throws Exception {
185 check();
186 if (manifest == null) {
187 Resource manifestResource = getResource("META-INF/MANIFEST.MF");
188 if (manifestResource != null) {
189 InputStream in = manifestResource.openInputStream();
190 manifest = new Manifest(in);
191 in.close();
192 }
193 }
194 return manifest;
195 }
196
197 public boolean exists(String path) {
198 check();
199 return resources.containsKey(path);
200 }
201
202 public void setManifest(Manifest manifest) {
203 check();
204 manifestFirst = true;
205 this.manifest = manifest;
206 }
207
208 public void setManifest(File file) throws IOException {
209 check();
210 FileInputStream fin = new FileInputStream(file);
211 try {
212 Manifest m = new Manifest(fin);
213 setManifest(m);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000214 }
215 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000216 fin.close();
217 }
218 }
219
220 public void write(File file) throws Exception {
221 check();
222 try {
223 OutputStream out = new FileOutputStream(file);
224 try {
225 write(out);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000226 }
227 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000228 IO.close(out);
229 }
230 return;
231
Stuart McCulloch2286f232012-06-15 13:27:53 +0000232 }
233 catch (Exception t) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000234 file.delete();
235 throw t;
236 }
237 }
238
239 public void write(String file) throws Exception {
240 check();
241 write(new File(file));
242 }
243
244 public void write(OutputStream out) throws Exception {
245 check();
Stuart McCulloch6a046662012-07-19 13:11:20 +0000246
247 if (!doNotTouchManifest && !nomanifest && algorithms != null) {
248
249 // ok, we have a request to create digests
250 // of the resources. Since we have to output
251 // the manifest first, we have a slight problem.
252 // We can also not make multiple passes over the resource
253 // because some resources are not idempotent and/or can
254 // take significant time. So we just copy the jar
255 // to a temporary file, read it in again, calculate
256 // the checksums and save.
257
258 String[] algs = algorithms;
259 algorithms = null;
260 try {
261 File f = File.createTempFile(getName(), ".jar");
262 System.out.println("Created tmp file " + f);
263 write(f);
264 Jar tmp = new Jar(f);
265 try {
266 tmp.calcChecksums(algorithms);
267 tmp.write(out);
268 }
269 finally {
270 f.delete();
271 tmp.close();
272 }
273 }
274 finally {
275 algorithms = algs;
276 }
277 return;
278 }
279
Stuart McCulloch2286f232012-06-15 13:27:53 +0000280 ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
Stuart McCullochbb014372012-06-07 21:57:32 +0000281
282 switch (compression) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000283 case STORE :
284 jout.setMethod(ZipOutputStream.DEFLATED);
285 break;
Stuart McCullochbb014372012-06-07 21:57:32 +0000286
Stuart McCulloch2286f232012-06-15 13:27:53 +0000287 default :
288 // default is DEFLATED
Stuart McCullochbb014372012-06-07 21:57:32 +0000289 }
290
291 Set<String> done = new HashSet<String>();
292
293 Set<String> directories = new HashSet<String>();
294 if (doNotTouchManifest) {
295 Resource r = getResource("META-INF/MANIFEST.MF");
296 if (r != null) {
297 writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
298 done.add("META-INF/MANIFEST.MF");
299 }
300 } else
301 doManifest(done, jout);
302
Stuart McCulloch2286f232012-06-15 13:27:53 +0000303 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000304 // Skip metainf contents
305 if (!done.contains(entry.getKey()))
306 writeResource(jout, directories, entry.getKey(), entry.getValue());
307 }
308 jout.finish();
309 }
310
311 private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
312 check();
313 if (nomanifest)
314 return;
315
316 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
Stuart McCulloch6a046662012-07-19 13:11:20 +0000317
Stuart McCullochbb014372012-06-07 21:57:32 +0000318 jout.putNextEntry(ze);
319 writeManifest(jout);
320 jout.closeEntry();
321 done.add(ze.getName());
322 }
323
324 /**
325 * Cleanup the manifest for writing. Cleaning up consists of adding a space
326 * after any \n to prevent the manifest to see this newline as a delimiter.
327 *
328 * @param out
329 * Output
330 * @throws IOException
331 */
332
333 public void writeManifest(OutputStream out) throws Exception {
334 check();
335 writeManifest(getManifest(), out);
336 }
337
338 public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
339 if (manifest == null)
340 return;
341
342 manifest = clean(manifest);
343 outputManifest(manifest, out);
344 }
345
346 /**
347 * Unfortunately we have to write our own manifest :-( because of a stupid
348 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
Stuart McCulloch2286f232012-06-15 13:27:53 +0000349 * it makes the bytes platform dependent. So the following code outputs the
350 * manifest. A Manifest consists of
Stuart McCullochbb014372012-06-07 21:57:32 +0000351 *
352 * <pre>
353 * 'Manifest-Version: 1.0\r\n'
354 * main-attributes *
355 * \r\n
356 * name-section
357 *
358 * main-attributes ::= attributes
359 * attributes ::= key ': ' value '\r\n'
360 * name-section ::= 'Name: ' name '\r\n' attributes
361 * </pre>
362 *
363 * Lines in the manifest should not exceed 72 bytes (! this is where the
364 * manifest screwed up as well when 16 bit unicodes were used).
Stuart McCullochbb014372012-06-07 21:57:32 +0000365 * <p>
366 * As a bonus, we can now sort the manifest!
367 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000368 static byte[] CONTINUE = new byte[] {
369 '\r', '\n', ' '
370 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000371
372 /**
373 * Main function to output a manifest properly in UTF-8.
374 *
375 * @param manifest
376 * The manifest to output
377 * @param out
378 * The output stream
379 * @throws IOException
380 * when something fails
381 */
382 public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
383 writeEntry(out, "Manifest-Version", "1.0");
384 attributes(manifest.getMainAttributes(), out);
385
386 TreeSet<String> keys = new TreeSet<String>();
387 for (Object o : manifest.getEntries().keySet())
388 keys.add(o.toString());
389
390 for (String key : keys) {
391 write(out, 0, "\r\n");
392 writeEntry(out, "Name", key);
393 attributes(manifest.getAttributes(key), out);
394 }
395 out.flush();
396 }
397
398 /**
399 * Write out an entry, handling proper unicode and line length constraints
Stuart McCullochbb014372012-06-07 21:57:32 +0000400 */
401 private static void writeEntry(OutputStream out, String name, String value) throws IOException {
402 int n = write(out, 0, name + ": ");
403 write(out, n, value);
404 write(out, 0, "\r\n");
405 }
406
407 /**
408 * Convert a string to bytes with UTF8 and then output in max 72 bytes
409 *
410 * @param out
411 * the output string
412 * @param i
413 * the current width
414 * @param s
415 * the string to output
416 * @return the new width
417 * @throws IOException
418 * when something fails
419 */
420 private static int write(OutputStream out, int i, String s) throws IOException {
421 byte[] bytes = s.getBytes("UTF8");
422 return write(out, i, bytes);
423 }
424
425 /**
426 * Write the bytes but ensure that the line length does not exceed 72
427 * characters. If it is more than 70 characters, we just put a cr/lf +
428 * space.
429 *
430 * @param out
431 * The output stream
432 * @param width
433 * The nr of characters output in a line before this method
434 * started
435 * @param bytes
436 * the bytes to output
437 * @return the nr of characters in the last line
438 * @throws IOException
439 * if something fails
440 */
441 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
442 int w = width;
443 for (int i = 0; i < bytes.length; i++) {
444 if (w >= 72) { // we need to add the \n\r!
445 out.write(CONTINUE);
446 w = 1;
447 }
448 out.write(bytes[i]);
449 w++;
450 }
451 return w;
452 }
453
454 /**
455 * Output an Attributes map. We will sort this map before outputing.
456 *
457 * @param value
458 * the attrbutes
459 * @param out
460 * the output stream
461 * @throws IOException
462 * when something fails
463 */
464 private static void attributes(Attributes value, OutputStream out) throws IOException {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000465 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
466 for (Map.Entry<Object,Object> entry : value.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000467 map.put(entry.getKey().toString(), entry.getValue().toString());
468 }
469
470 map.remove("Manifest-Version"); // get rid of
471 // manifest
472 // version
Stuart McCulloch2286f232012-06-15 13:27:53 +0000473 for (Map.Entry<String,String> entry : map.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000474 writeEntry(out, entry.getKey(), entry.getValue());
475 }
476 }
477
478 private static Manifest clean(Manifest org) {
479
480 Manifest result = new Manifest();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000481 for (Map.Entry< ? , ? > entry : org.getMainAttributes().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000482 String nice = clean((String) entry.getValue());
483 result.getMainAttributes().put(entry.getKey(), nice);
484 }
485 for (String name : org.getEntries().keySet()) {
486 Attributes attrs = result.getAttributes(name);
487 if (attrs == null) {
488 attrs = new Attributes();
489 result.getEntries().put(name, attrs);
490 }
491
Stuart McCulloch2286f232012-06-15 13:27:53 +0000492 for (Map.Entry< ? , ? > entry : org.getAttributes(name).entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000493 String nice = clean((String) entry.getValue());
Stuart McCullochd4826102012-06-26 16:34:24 +0000494 attrs.put(entry.getKey(), nice);
Stuart McCullochbb014372012-06-07 21:57:32 +0000495 }
496 }
497 return result;
498 }
499
500 private static String clean(String s) {
501 if (s.indexOf('\n') < 0)
502 return s;
503
504 StringBuilder sb = new StringBuilder(s);
505 for (int i = 0; i < sb.length(); i++) {
506 if (sb.charAt(i) == '\n')
507 sb.insert(++i, ' ');
508 }
509 return sb.toString();
510 }
511
Stuart McCulloch2286f232012-06-15 13:27:53 +0000512 private void writeResource(ZipOutputStream jout, Set<String> directories, String path, Resource resource)
513 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000514 if (resource == null)
515 return;
516 try {
517 createDirectories(directories, jout, path);
Stuart McCullochb215bfd2012-09-06 18:28:06 +0000518 if (path.endsWith(Constants.EMPTY_HEADER))
519 return;
Stuart McCullochbb014372012-06-07 21:57:32 +0000520 ZipEntry ze = new ZipEntry(path);
521 ze.setMethod(ZipEntry.DEFLATED);
522 long lastModified = resource.lastModified();
523 if (lastModified == 0L) {
524 lastModified = System.currentTimeMillis();
525 }
526 ze.setTime(lastModified);
527 if (resource.getExtra() != null)
528 ze.setExtra(resource.getExtra().getBytes("UTF-8"));
529 jout.putNextEntry(ze);
530 resource.write(jout);
531 jout.closeEntry();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000532 }
533 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000534 throw new Exception("Problem writing resource " + path, e);
535 }
536 }
537
Stuart McCulloch2286f232012-06-15 13:27:53 +0000538 void createDirectories(Set<String> directories, ZipOutputStream zip, String name) throws IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000539 int index = name.lastIndexOf('/');
540 if (index > 0) {
541 String path = name.substring(0, index);
542 if (directories.contains(path))
543 return;
544 createDirectories(directories, zip, path);
545 ZipEntry ze = new ZipEntry(path + '/');
546 zip.putNextEntry(ze);
547 zip.closeEntry();
548 directories.add(path);
549 }
550 }
551
552 public String getName() {
553 return name;
554 }
555
556 /**
557 * Add all the resources in the given jar that match the given filter.
558 *
559 * @param sub
560 * the jar
561 * @param filter
562 * a pattern that should match the resoures in sub to be added
563 */
564 public boolean addAll(Jar sub, Instruction filter) {
565 return addAll(sub, filter, "");
566 }
567
568 /**
569 * Add all the resources in the given jar that match the given filter.
570 *
571 * @param sub
572 * the jar
573 * @param filter
574 * a pattern that should match the resoures in sub to be added
575 */
576 public boolean addAll(Jar sub, Instruction filter, String destination) {
577 check();
578 boolean dupl = false;
579 for (String name : sub.getResources().keySet()) {
580 if ("META-INF/MANIFEST.MF".equals(name))
581 continue;
582
583 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000584 dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000585 }
586 return dupl;
587 }
588
589 public void close() {
590 this.closed = true;
591 if (zipFile != null)
592 try {
593 zipFile.close();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000594 }
595 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000596 // Ignore
597 }
598 resources.clear();
599 directories.clear();
600 manifest = null;
601 source = null;
602 }
603
604 public long lastModified() {
605 return lastModified;
606 }
607
608 public void updateModified(long time, String reason) {
609 if (time > lastModified) {
610 lastModified = time;
611 lastModifiedReason = reason;
612 }
613 }
614
615 public void setReporter(Reporter reporter) {
616 this.reporter = reporter;
617 }
618
619 public boolean hasDirectory(String path) {
620 check();
621 return directories.get(path) != null;
622 }
623
624 public List<String> getPackages() {
625 check();
626 List<String> list = new ArrayList<String>(directories.size());
627
Stuart McCulloch2286f232012-06-15 13:27:53 +0000628 for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000629 if (i.getValue() != null) {
630 String path = i.getKey();
631 String pack = path.replace('/', '.');
632 list.add(pack);
633 }
634 }
635 return list;
636 }
637
638 public File getSource() {
639 check();
640 return source;
641 }
642
643 public boolean addAll(Jar src) {
644 check();
645 return addAll(src, null);
646 }
647
648 public boolean rename(String oldPath, String newPath) {
649 check();
650 Resource resource = remove(oldPath);
651 if (resource == null)
652 return false;
653
654 return putResource(newPath, resource);
655 }
656
657 public Resource remove(String path) {
658 check();
659 Resource resource = resources.remove(path);
660 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000661 Map<String,Resource> mdir = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000662 // must be != null
663 mdir.remove(path);
664 return resource;
665 }
666
667 /**
668 * Make sure nobody touches the manifest! If the bundle is signed, we do not
669 * want anybody to touch the manifest after the digests have been
670 * calculated.
671 */
672 public void setDoNotTouchManifest() {
673 doNotTouchManifest = true;
674 }
675
676 /**
677 * Calculate the checksums and set them in the manifest.
678 */
679
680 public void calcChecksums(String algorithms[]) throws Exception {
681 check();
682 if (algorithms == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000683 algorithms = new String[] {
684 "SHA", "MD5"
685 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000686
687 Manifest m = getManifest();
688 if (m == null) {
689 m = new Manifest();
690 setManifest(m);
691 }
692
693 MessageDigest digests[] = new MessageDigest[algorithms.length];
694 int n = 0;
695 for (String algorithm : algorithms)
696 digests[n++] = MessageDigest.getInstance(algorithm);
697
698 byte buffer[] = new byte[30000];
699
Stuart McCulloch2286f232012-06-15 13:27:53 +0000700 for (Map.Entry<String,Resource> entry : resources.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000701
702 // Skip the manifest
703 if (entry.getKey().equals("META-INF/MANIFEST.MF"))
704 continue;
705
706 Resource r = entry.getValue();
707 Attributes attributes = m.getAttributes(entry.getKey());
708 if (attributes == null) {
709 attributes = new Attributes();
710 getManifest().getEntries().put(entry.getKey(), attributes);
711 }
712 InputStream in = r.openInputStream();
713 try {
714 for (MessageDigest d : digests)
715 d.reset();
716 int size = in.read(buffer);
717 while (size > 0) {
718 for (MessageDigest d : digests)
719 d.update(buffer, 0, size);
720 size = in.read(buffer);
721 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000722 }
723 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000724 in.close();
725 }
726 for (MessageDigest d : digests)
727 attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
728 }
729 }
730
731 Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
732
733 public String getBsn() throws Exception {
734 check();
735 Manifest m = getManifest();
736 if (m == null)
737 return null;
738
739 String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
740 if (s == null)
741 return null;
742
743 Matcher matcher = BSN.matcher(s);
744 if (matcher.matches()) {
745 return matcher.group(1);
746 }
747 return null;
748 }
749
750 public String getVersion() throws Exception {
751 check();
752 Manifest m = getManifest();
753 if (m == null)
754 return null;
755
756 String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
757 if (s == null)
758 return null;
759
760 return s.trim();
761 }
762
763 /**
764 * Expand the JAR file to a directory.
765 *
766 * @param dir
767 * the dst directory, is not required to exist
768 * @throws Exception
769 * if anything does not work as expected.
770 */
771 public void expand(File dir) throws Exception {
772 check();
773 dir = dir.getAbsoluteFile();
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000774 if (!dir.exists() && !dir.mkdirs()) {
775 throw new IOException("Could not create directory " + dir);
776 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000777 if (!dir.isDirectory()) {
778 throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
779 }
780
Stuart McCulloch2286f232012-06-15 13:27:53 +0000781 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000782 File f = getFile(dir, entry.getKey());
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000783 File fp = f.getParentFile();
784 if (!fp.exists() && !fp.mkdirs()) {
785 throw new IOException("Could not create directory " + fp);
786 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000787 IO.copy(entry.getValue().openInputStream(), f);
788 }
789 }
790
791 /**
792 * Make sure we have a manifest
793 *
794 * @throws Exception
795 */
796 public void ensureManifest() throws Exception {
797 if (getManifest() != null)
798 return;
799 manifest = new Manifest();
800 }
801
802 /**
803 * Answer if the manifest was the first entry
804 */
805
806 public boolean isManifestFirst() {
807 return manifestFirst;
808 }
809
810 public void copy(Jar srce, String path, boolean overwrite) {
811 check();
812 addDirectory(srce.getDirectories().get(path), overwrite);
813 }
814
815 public void setCompression(Compression compression) {
816 this.compression = compression;
817 }
818
819 public Compression hasCompression() {
820 return this.compression;
821 }
822
823 void check() {
824 if (closed)
825 throw new RuntimeException("Already closed " + name);
826 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000827
828 /**
829 * Return a data uri from the JAR. The data must be less than 32k
830 *
831 * @param jar
832 * The jar to load the data from
833 * @param path
834 * the path in the jar
835 * @param mime
836 * the mime type
837 * @return a URI or null if conversion could not take place
838 */
839
840 public URI getDataURI(String path, String mime, int max) throws Exception {
841 Resource r = getResource(path);
842
843 if (r.size() >= max || r.size() <= 0)
844 return null;
845
846 byte[] data = new byte[(int) r.size()];
847 DataInputStream din = new DataInputStream(r.openInputStream());
848 try {
849 din.readFully(data);
850 String encoded = Base64.encodeBase64(data);
851 return new URI("data:" + mime + ";base64," + encoded);
852 }
853 finally {
854 din.close();
855 }
856 }
Stuart McCulloch6a046662012-07-19 13:11:20 +0000857
858 public void setDigestAlgorithms(String[] algorithms) {
859 this.algorithms = algorithms;
860 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000861}