blob: 11c0dab467502f286f1474dcb8af907450843739 [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
100 public String toString() {
101 return "Jar:" + name;
102 }
103
104 public boolean putResource(String path, Resource resource) {
105 check();
106 return putResource(path, resource, true);
107 }
108
109 public boolean putResource(String path, Resource resource, boolean overwrite) {
110 check();
111 updateModified(resource.lastModified(), path);
112 while (path.startsWith("/"))
113 path = path.substring(1);
114
115 if (path.equals("META-INF/MANIFEST.MF")) {
116 manifest = null;
117 if (resources.isEmpty())
118 manifestFirst = true;
119 }
120 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000121 Map<String,Resource> s = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000122 if (s == null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000123 s = new TreeMap<String,Resource>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000124 directories.put(dir, s);
125 int n = dir.lastIndexOf('/');
126 while (n > 0) {
127 String dd = dir.substring(0, n);
128 if (directories.containsKey(dd))
129 break;
130 directories.put(dd, null);
131 n = dd.lastIndexOf('/');
132 }
133 }
134 boolean duplicate = s.containsKey(path);
135 if (!duplicate || overwrite) {
136 resources.put(path, resource);
137 s.put(path, resource);
138 }
139 return duplicate;
140 }
141
142 public Resource getResource(String path) {
143 check();
144 if (resources == null)
145 return null;
146 return resources.get(path);
147 }
148
149 private String getDirectory(String path) {
150 check();
151 int n = path.lastIndexOf('/');
152 if (n < 0)
153 return "";
154
155 return path.substring(0, n);
156 }
157
Stuart McCulloch2286f232012-06-15 13:27:53 +0000158 public Map<String,Map<String,Resource>> getDirectories() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000159 check();
160 return directories;
161 }
162
Stuart McCulloch2286f232012-06-15 13:27:53 +0000163 public Map<String,Resource> getResources() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000164 check();
165 return resources;
166 }
167
Stuart McCulloch2286f232012-06-15 13:27:53 +0000168 public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000169 check();
170 boolean duplicates = false;
171 if (directory == null)
172 return false;
173
Stuart McCulloch2286f232012-06-15 13:27:53 +0000174 for (Map.Entry<String,Resource> entry : directory.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000175 String key = entry.getKey();
176 if (!key.endsWith(".java")) {
177 duplicates |= putResource(key, entry.getValue(), overwrite);
178 }
179 }
180 return duplicates;
181 }
182
183 public Manifest getManifest() throws Exception {
184 check();
185 if (manifest == null) {
186 Resource manifestResource = getResource("META-INF/MANIFEST.MF");
187 if (manifestResource != null) {
188 InputStream in = manifestResource.openInputStream();
189 manifest = new Manifest(in);
190 in.close();
191 }
192 }
193 return manifest;
194 }
195
196 public boolean exists(String path) {
197 check();
198 return resources.containsKey(path);
199 }
200
201 public void setManifest(Manifest manifest) {
202 check();
203 manifestFirst = true;
204 this.manifest = manifest;
205 }
206
207 public void setManifest(File file) throws IOException {
208 check();
209 FileInputStream fin = new FileInputStream(file);
210 try {
211 Manifest m = new Manifest(fin);
212 setManifest(m);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000213 }
214 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000215 fin.close();
216 }
217 }
218
219 public void write(File file) throws Exception {
220 check();
221 try {
222 OutputStream out = new FileOutputStream(file);
223 try {
224 write(out);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000225 }
226 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000227 IO.close(out);
228 }
229 return;
230
Stuart McCulloch2286f232012-06-15 13:27:53 +0000231 }
232 catch (Exception t) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000233 file.delete();
234 throw t;
235 }
236 }
237
238 public void write(String file) throws Exception {
239 check();
240 write(new File(file));
241 }
242
243 public void write(OutputStream out) throws Exception {
244 check();
Stuart McCulloch6a046662012-07-19 13:11:20 +0000245
246 if (!doNotTouchManifest && !nomanifest && algorithms != null) {
247
248 // ok, we have a request to create digests
249 // of the resources. Since we have to output
250 // the manifest first, we have a slight problem.
251 // We can also not make multiple passes over the resource
252 // because some resources are not idempotent and/or can
253 // take significant time. So we just copy the jar
254 // to a temporary file, read it in again, calculate
255 // the checksums and save.
256
257 String[] algs = algorithms;
258 algorithms = null;
259 try {
260 File f = File.createTempFile(getName(), ".jar");
261 System.out.println("Created tmp file " + f);
262 write(f);
263 Jar tmp = new Jar(f);
264 try {
265 tmp.calcChecksums(algorithms);
266 tmp.write(out);
267 }
268 finally {
269 f.delete();
270 tmp.close();
271 }
272 }
273 finally {
274 algorithms = algs;
275 }
276 return;
277 }
278
Stuart McCulloch2286f232012-06-15 13:27:53 +0000279 ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
Stuart McCullochbb014372012-06-07 21:57:32 +0000280
281 switch (compression) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000282 case STORE :
283 jout.setMethod(ZipOutputStream.DEFLATED);
284 break;
Stuart McCullochbb014372012-06-07 21:57:32 +0000285
Stuart McCulloch2286f232012-06-15 13:27:53 +0000286 default :
287 // default is DEFLATED
Stuart McCullochbb014372012-06-07 21:57:32 +0000288 }
289
290 Set<String> done = new HashSet<String>();
291
292 Set<String> directories = new HashSet<String>();
293 if (doNotTouchManifest) {
294 Resource r = getResource("META-INF/MANIFEST.MF");
295 if (r != null) {
296 writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
297 done.add("META-INF/MANIFEST.MF");
298 }
299 } else
300 doManifest(done, jout);
301
Stuart McCulloch2286f232012-06-15 13:27:53 +0000302 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000303 // Skip metainf contents
304 if (!done.contains(entry.getKey()))
305 writeResource(jout, directories, entry.getKey(), entry.getValue());
306 }
307 jout.finish();
308 }
309
310 private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
311 check();
312 if (nomanifest)
313 return;
314
315 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
Stuart McCulloch6a046662012-07-19 13:11:20 +0000316
Stuart McCullochbb014372012-06-07 21:57:32 +0000317 jout.putNextEntry(ze);
318 writeManifest(jout);
319 jout.closeEntry();
320 done.add(ze.getName());
321 }
322
323 /**
324 * Cleanup the manifest for writing. Cleaning up consists of adding a space
325 * after any \n to prevent the manifest to see this newline as a delimiter.
326 *
327 * @param out
328 * Output
329 * @throws IOException
330 */
331
332 public void writeManifest(OutputStream out) throws Exception {
333 check();
334 writeManifest(getManifest(), out);
335 }
336
337 public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
338 if (manifest == null)
339 return;
340
341 manifest = clean(manifest);
342 outputManifest(manifest, out);
343 }
344
345 /**
346 * Unfortunately we have to write our own manifest :-( because of a stupid
347 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
Stuart McCulloch2286f232012-06-15 13:27:53 +0000348 * it makes the bytes platform dependent. So the following code outputs the
349 * manifest. A Manifest consists of
Stuart McCullochbb014372012-06-07 21:57:32 +0000350 *
351 * <pre>
352 * 'Manifest-Version: 1.0\r\n'
353 * main-attributes *
354 * \r\n
355 * name-section
356 *
357 * main-attributes ::= attributes
358 * attributes ::= key ': ' value '\r\n'
359 * name-section ::= 'Name: ' name '\r\n' attributes
360 * </pre>
361 *
362 * Lines in the manifest should not exceed 72 bytes (! this is where the
363 * manifest screwed up as well when 16 bit unicodes were used).
Stuart McCullochbb014372012-06-07 21:57:32 +0000364 * <p>
365 * As a bonus, we can now sort the manifest!
366 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000367 static byte[] CONTINUE = new byte[] {
368 '\r', '\n', ' '
369 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000370
371 /**
372 * Main function to output a manifest properly in UTF-8.
373 *
374 * @param manifest
375 * The manifest to output
376 * @param out
377 * The output stream
378 * @throws IOException
379 * when something fails
380 */
381 public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
382 writeEntry(out, "Manifest-Version", "1.0");
383 attributes(manifest.getMainAttributes(), out);
384
385 TreeSet<String> keys = new TreeSet<String>();
386 for (Object o : manifest.getEntries().keySet())
387 keys.add(o.toString());
388
389 for (String key : keys) {
390 write(out, 0, "\r\n");
391 writeEntry(out, "Name", key);
392 attributes(manifest.getAttributes(key), out);
393 }
394 out.flush();
395 }
396
397 /**
398 * Write out an entry, handling proper unicode and line length constraints
Stuart McCullochbb014372012-06-07 21:57:32 +0000399 */
400 private static void writeEntry(OutputStream out, String name, String value) throws IOException {
401 int n = write(out, 0, name + ": ");
402 write(out, n, value);
403 write(out, 0, "\r\n");
404 }
405
406 /**
407 * Convert a string to bytes with UTF8 and then output in max 72 bytes
408 *
409 * @param out
410 * the output string
411 * @param i
412 * the current width
413 * @param s
414 * the string to output
415 * @return the new width
416 * @throws IOException
417 * when something fails
418 */
419 private static int write(OutputStream out, int i, String s) throws IOException {
420 byte[] bytes = s.getBytes("UTF8");
421 return write(out, i, bytes);
422 }
423
424 /**
425 * Write the bytes but ensure that the line length does not exceed 72
426 * characters. If it is more than 70 characters, we just put a cr/lf +
427 * space.
428 *
429 * @param out
430 * The output stream
431 * @param width
432 * The nr of characters output in a line before this method
433 * started
434 * @param bytes
435 * the bytes to output
436 * @return the nr of characters in the last line
437 * @throws IOException
438 * if something fails
439 */
440 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
441 int w = width;
442 for (int i = 0; i < bytes.length; i++) {
443 if (w >= 72) { // we need to add the \n\r!
444 out.write(CONTINUE);
445 w = 1;
446 }
447 out.write(bytes[i]);
448 w++;
449 }
450 return w;
451 }
452
453 /**
454 * Output an Attributes map. We will sort this map before outputing.
455 *
456 * @param value
457 * the attrbutes
458 * @param out
459 * the output stream
460 * @throws IOException
461 * when something fails
462 */
463 private static void attributes(Attributes value, OutputStream out) throws IOException {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000464 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
465 for (Map.Entry<Object,Object> entry : value.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000466 map.put(entry.getKey().toString(), entry.getValue().toString());
467 }
468
469 map.remove("Manifest-Version"); // get rid of
470 // manifest
471 // version
Stuart McCulloch2286f232012-06-15 13:27:53 +0000472 for (Map.Entry<String,String> entry : map.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000473 writeEntry(out, entry.getKey(), entry.getValue());
474 }
475 }
476
477 private static Manifest clean(Manifest org) {
478
479 Manifest result = new Manifest();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000480 for (Map.Entry< ? , ? > entry : org.getMainAttributes().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000481 String nice = clean((String) entry.getValue());
482 result.getMainAttributes().put(entry.getKey(), nice);
483 }
484 for (String name : org.getEntries().keySet()) {
485 Attributes attrs = result.getAttributes(name);
486 if (attrs == null) {
487 attrs = new Attributes();
488 result.getEntries().put(name, attrs);
489 }
490
Stuart McCulloch2286f232012-06-15 13:27:53 +0000491 for (Map.Entry< ? , ? > entry : org.getAttributes(name).entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000492 String nice = clean((String) entry.getValue());
Stuart McCullochd4826102012-06-26 16:34:24 +0000493 attrs.put(entry.getKey(), nice);
Stuart McCullochbb014372012-06-07 21:57:32 +0000494 }
495 }
496 return result;
497 }
498
499 private static String clean(String s) {
500 if (s.indexOf('\n') < 0)
501 return s;
502
503 StringBuilder sb = new StringBuilder(s);
504 for (int i = 0; i < sb.length(); i++) {
505 if (sb.charAt(i) == '\n')
506 sb.insert(++i, ' ');
507 }
508 return sb.toString();
509 }
510
Stuart McCulloch2286f232012-06-15 13:27:53 +0000511 private void writeResource(ZipOutputStream jout, Set<String> directories, String path, Resource resource)
512 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000513 if (resource == null)
514 return;
515 try {
516 createDirectories(directories, jout, path);
517 ZipEntry ze = new ZipEntry(path);
518 ze.setMethod(ZipEntry.DEFLATED);
519 long lastModified = resource.lastModified();
520 if (lastModified == 0L) {
521 lastModified = System.currentTimeMillis();
522 }
523 ze.setTime(lastModified);
524 if (resource.getExtra() != null)
525 ze.setExtra(resource.getExtra().getBytes("UTF-8"));
526 jout.putNextEntry(ze);
527 resource.write(jout);
528 jout.closeEntry();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000529 }
530 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000531 throw new Exception("Problem writing resource " + path, e);
532 }
533 }
534
Stuart McCulloch2286f232012-06-15 13:27:53 +0000535 void createDirectories(Set<String> directories, ZipOutputStream zip, String name) throws IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000536 int index = name.lastIndexOf('/');
537 if (index > 0) {
538 String path = name.substring(0, index);
539 if (directories.contains(path))
540 return;
541 createDirectories(directories, zip, path);
542 ZipEntry ze = new ZipEntry(path + '/');
543 zip.putNextEntry(ze);
544 zip.closeEntry();
545 directories.add(path);
546 }
547 }
548
549 public String getName() {
550 return name;
551 }
552
553 /**
554 * Add all the resources in the given jar that match the given filter.
555 *
556 * @param sub
557 * the jar
558 * @param filter
559 * a pattern that should match the resoures in sub to be added
560 */
561 public boolean addAll(Jar sub, Instruction filter) {
562 return addAll(sub, filter, "");
563 }
564
565 /**
566 * Add all the resources in the given jar that match the given filter.
567 *
568 * @param sub
569 * the jar
570 * @param filter
571 * a pattern that should match the resoures in sub to be added
572 */
573 public boolean addAll(Jar sub, Instruction filter, String destination) {
574 check();
575 boolean dupl = false;
576 for (String name : sub.getResources().keySet()) {
577 if ("META-INF/MANIFEST.MF".equals(name))
578 continue;
579
580 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000581 dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000582 }
583 return dupl;
584 }
585
586 public void close() {
587 this.closed = true;
588 if (zipFile != null)
589 try {
590 zipFile.close();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000591 }
592 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000593 // Ignore
594 }
595 resources.clear();
596 directories.clear();
597 manifest = null;
598 source = null;
599 }
600
601 public long lastModified() {
602 return lastModified;
603 }
604
605 public void updateModified(long time, String reason) {
606 if (time > lastModified) {
607 lastModified = time;
608 lastModifiedReason = reason;
609 }
610 }
611
612 public void setReporter(Reporter reporter) {
613 this.reporter = reporter;
614 }
615
616 public boolean hasDirectory(String path) {
617 check();
618 return directories.get(path) != null;
619 }
620
621 public List<String> getPackages() {
622 check();
623 List<String> list = new ArrayList<String>(directories.size());
624
Stuart McCulloch2286f232012-06-15 13:27:53 +0000625 for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000626 if (i.getValue() != null) {
627 String path = i.getKey();
628 String pack = path.replace('/', '.');
629 list.add(pack);
630 }
631 }
632 return list;
633 }
634
635 public File getSource() {
636 check();
637 return source;
638 }
639
640 public boolean addAll(Jar src) {
641 check();
642 return addAll(src, null);
643 }
644
645 public boolean rename(String oldPath, String newPath) {
646 check();
647 Resource resource = remove(oldPath);
648 if (resource == null)
649 return false;
650
651 return putResource(newPath, resource);
652 }
653
654 public Resource remove(String path) {
655 check();
656 Resource resource = resources.remove(path);
657 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000658 Map<String,Resource> mdir = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000659 // must be != null
660 mdir.remove(path);
661 return resource;
662 }
663
664 /**
665 * Make sure nobody touches the manifest! If the bundle is signed, we do not
666 * want anybody to touch the manifest after the digests have been
667 * calculated.
668 */
669 public void setDoNotTouchManifest() {
670 doNotTouchManifest = true;
671 }
672
673 /**
674 * Calculate the checksums and set them in the manifest.
675 */
676
677 public void calcChecksums(String algorithms[]) throws Exception {
678 check();
679 if (algorithms == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000680 algorithms = new String[] {
681 "SHA", "MD5"
682 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000683
684 Manifest m = getManifest();
685 if (m == null) {
686 m = new Manifest();
687 setManifest(m);
688 }
689
690 MessageDigest digests[] = new MessageDigest[algorithms.length];
691 int n = 0;
692 for (String algorithm : algorithms)
693 digests[n++] = MessageDigest.getInstance(algorithm);
694
695 byte buffer[] = new byte[30000];
696
Stuart McCulloch2286f232012-06-15 13:27:53 +0000697 for (Map.Entry<String,Resource> entry : resources.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000698
699 // Skip the manifest
700 if (entry.getKey().equals("META-INF/MANIFEST.MF"))
701 continue;
702
703 Resource r = entry.getValue();
704 Attributes attributes = m.getAttributes(entry.getKey());
705 if (attributes == null) {
706 attributes = new Attributes();
707 getManifest().getEntries().put(entry.getKey(), attributes);
708 }
709 InputStream in = r.openInputStream();
710 try {
711 for (MessageDigest d : digests)
712 d.reset();
713 int size = in.read(buffer);
714 while (size > 0) {
715 for (MessageDigest d : digests)
716 d.update(buffer, 0, size);
717 size = in.read(buffer);
718 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000719 }
720 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000721 in.close();
722 }
723 for (MessageDigest d : digests)
724 attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
725 }
726 }
727
728 Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
729
730 public String getBsn() throws Exception {
731 check();
732 Manifest m = getManifest();
733 if (m == null)
734 return null;
735
736 String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
737 if (s == null)
738 return null;
739
740 Matcher matcher = BSN.matcher(s);
741 if (matcher.matches()) {
742 return matcher.group(1);
743 }
744 return null;
745 }
746
747 public String getVersion() throws Exception {
748 check();
749 Manifest m = getManifest();
750 if (m == null)
751 return null;
752
753 String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
754 if (s == null)
755 return null;
756
757 return s.trim();
758 }
759
760 /**
761 * Expand the JAR file to a directory.
762 *
763 * @param dir
764 * the dst directory, is not required to exist
765 * @throws Exception
766 * if anything does not work as expected.
767 */
768 public void expand(File dir) throws Exception {
769 check();
770 dir = dir.getAbsoluteFile();
771 dir.mkdirs();
772 if (!dir.isDirectory()) {
773 throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
774 }
775
Stuart McCulloch2286f232012-06-15 13:27:53 +0000776 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000777 File f = getFile(dir, entry.getKey());
778 f.getParentFile().mkdirs();
779 IO.copy(entry.getValue().openInputStream(), f);
780 }
781 }
782
783 /**
784 * Make sure we have a manifest
785 *
786 * @throws Exception
787 */
788 public void ensureManifest() throws Exception {
789 if (getManifest() != null)
790 return;
791 manifest = new Manifest();
792 }
793
794 /**
795 * Answer if the manifest was the first entry
796 */
797
798 public boolean isManifestFirst() {
799 return manifestFirst;
800 }
801
802 public void copy(Jar srce, String path, boolean overwrite) {
803 check();
804 addDirectory(srce.getDirectories().get(path), overwrite);
805 }
806
807 public void setCompression(Compression compression) {
808 this.compression = compression;
809 }
810
811 public Compression hasCompression() {
812 return this.compression;
813 }
814
815 void check() {
816 if (closed)
817 throw new RuntimeException("Already closed " + name);
818 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000819
820 /**
821 * Return a data uri from the JAR. The data must be less than 32k
822 *
823 * @param jar
824 * The jar to load the data from
825 * @param path
826 * the path in the jar
827 * @param mime
828 * the mime type
829 * @return a URI or null if conversion could not take place
830 */
831
832 public URI getDataURI(String path, String mime, int max) throws Exception {
833 Resource r = getResource(path);
834
835 if (r.size() >= max || r.size() <= 0)
836 return null;
837
838 byte[] data = new byte[(int) r.size()];
839 DataInputStream din = new DataInputStream(r.openInputStream());
840 try {
841 din.readFully(data);
842 String encoded = Base64.encodeBase64(data);
843 return new URI("data:" + mime + ";base64," + encoded);
844 }
845 finally {
846 din.close();
847 }
848 }
Stuart McCulloch6a046662012-07-19 13:11:20 +0000849
850 public void setDigestAlgorithms(String[] algorithms) {
851 this.algorithms = algorithms;
852 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000853}