blob: b86429766894ab737a9c6684baf191af51ac45b0 [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 McCullochbb014372012-06-07 21:57:32 +000037
38 public Jar(String name) {
39 this.name = name;
40 }
41
42 public Jar(String name, File dirOrFile, Pattern doNotCopy) throws ZipException, IOException {
43 this(name);
44 source = dirOrFile;
45 if (dirOrFile.isDirectory())
46 FileResource.build(this, dirOrFile, doNotCopy);
47 else if (dirOrFile.isFile()) {
48 zipFile = ZipResource.build(this, dirOrFile);
49 } else {
Stuart McCulloch2286f232012-06-15 13:27:53 +000050 throw new IllegalArgumentException("A Jar can only accept a valid file or directory: " + dirOrFile);
Stuart McCullochbb014372012-06-07 21:57:32 +000051 }
52 }
53
54 public Jar(String name, InputStream in, long lastModified) throws IOException {
55 this(name);
56 EmbeddedResource.build(this, in, lastModified);
57 }
58
59 public Jar(String name, String path) throws IOException {
60 this(name);
61 File f = new File(path);
62 InputStream in = new FileInputStream(f);
63 EmbeddedResource.build(this, in, f.lastModified());
64 in.close();
65 }
66
67 public Jar(File f) throws IOException {
68 this(getName(f), f, null);
69 }
70
71 /**
72 * Make the JAR file name the project name if we get a src or bin directory.
73 *
74 * @param f
75 * @return
76 */
77 private static String getName(File f) {
78 f = f.getAbsoluteFile();
79 String name = f.getName();
80 if (name.equals("bin") || name.equals("src"))
81 return f.getParentFile().getName();
Stuart McCullochd4826102012-06-26 16:34:24 +000082 if (name.endsWith(".jar"))
83 name = name.substring(0, name.length() - 4);
84 return name;
Stuart McCullochbb014372012-06-07 21:57:32 +000085 }
86
87 public Jar(String string, InputStream resourceAsStream) throws IOException {
88 this(string, resourceAsStream, 0);
89 }
90
91 public Jar(String string, File file) throws ZipException, IOException {
92 this(string, file, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
93 }
94
95 public void setName(String name) {
96 this.name = name;
97 }
98
99 public String toString() {
100 return "Jar:" + name;
101 }
102
103 public boolean putResource(String path, Resource resource) {
104 check();
105 return putResource(path, resource, true);
106 }
107
108 public boolean putResource(String path, Resource resource, boolean overwrite) {
109 check();
110 updateModified(resource.lastModified(), path);
111 while (path.startsWith("/"))
112 path = path.substring(1);
113
114 if (path.equals("META-INF/MANIFEST.MF")) {
115 manifest = null;
116 if (resources.isEmpty())
117 manifestFirst = true;
118 }
119 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000120 Map<String,Resource> s = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000121 if (s == null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000122 s = new TreeMap<String,Resource>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000123 directories.put(dir, s);
124 int n = dir.lastIndexOf('/');
125 while (n > 0) {
126 String dd = dir.substring(0, n);
127 if (directories.containsKey(dd))
128 break;
129 directories.put(dd, null);
130 n = dd.lastIndexOf('/');
131 }
132 }
133 boolean duplicate = s.containsKey(path);
134 if (!duplicate || overwrite) {
135 resources.put(path, resource);
136 s.put(path, resource);
137 }
138 return duplicate;
139 }
140
141 public Resource getResource(String path) {
142 check();
143 if (resources == null)
144 return null;
145 return resources.get(path);
146 }
147
148 private String getDirectory(String path) {
149 check();
150 int n = path.lastIndexOf('/');
151 if (n < 0)
152 return "";
153
154 return path.substring(0, n);
155 }
156
Stuart McCulloch2286f232012-06-15 13:27:53 +0000157 public Map<String,Map<String,Resource>> getDirectories() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000158 check();
159 return directories;
160 }
161
Stuart McCulloch2286f232012-06-15 13:27:53 +0000162 public Map<String,Resource> getResources() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000163 check();
164 return resources;
165 }
166
Stuart McCulloch2286f232012-06-15 13:27:53 +0000167 public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000168 check();
169 boolean duplicates = false;
170 if (directory == null)
171 return false;
172
Stuart McCulloch2286f232012-06-15 13:27:53 +0000173 for (Map.Entry<String,Resource> entry : directory.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000174 String key = entry.getKey();
175 if (!key.endsWith(".java")) {
176 duplicates |= putResource(key, entry.getValue(), overwrite);
177 }
178 }
179 return duplicates;
180 }
181
182 public Manifest getManifest() throws Exception {
183 check();
184 if (manifest == null) {
185 Resource manifestResource = getResource("META-INF/MANIFEST.MF");
186 if (manifestResource != null) {
187 InputStream in = manifestResource.openInputStream();
188 manifest = new Manifest(in);
189 in.close();
190 }
191 }
192 return manifest;
193 }
194
195 public boolean exists(String path) {
196 check();
197 return resources.containsKey(path);
198 }
199
200 public void setManifest(Manifest manifest) {
201 check();
202 manifestFirst = true;
203 this.manifest = manifest;
204 }
205
206 public void setManifest(File file) throws IOException {
207 check();
208 FileInputStream fin = new FileInputStream(file);
209 try {
210 Manifest m = new Manifest(fin);
211 setManifest(m);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000212 }
213 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000214 fin.close();
215 }
216 }
217
218 public void write(File file) throws Exception {
219 check();
220 try {
221 OutputStream out = new FileOutputStream(file);
222 try {
223 write(out);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000224 }
225 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000226 IO.close(out);
227 }
228 return;
229
Stuart McCulloch2286f232012-06-15 13:27:53 +0000230 }
231 catch (Exception t) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000232 file.delete();
233 throw t;
234 }
235 }
236
237 public void write(String file) throws Exception {
238 check();
239 write(new File(file));
240 }
241
242 public void write(OutputStream out) throws Exception {
243 check();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000244 ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
Stuart McCullochbb014372012-06-07 21:57:32 +0000245
246 switch (compression) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000247 case STORE :
248 jout.setMethod(ZipOutputStream.DEFLATED);
249 break;
Stuart McCullochbb014372012-06-07 21:57:32 +0000250
Stuart McCulloch2286f232012-06-15 13:27:53 +0000251 default :
252 // default is DEFLATED
Stuart McCullochbb014372012-06-07 21:57:32 +0000253 }
254
255 Set<String> done = new HashSet<String>();
256
257 Set<String> directories = new HashSet<String>();
258 if (doNotTouchManifest) {
259 Resource r = getResource("META-INF/MANIFEST.MF");
260 if (r != null) {
261 writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
262 done.add("META-INF/MANIFEST.MF");
263 }
264 } else
265 doManifest(done, jout);
266
Stuart McCulloch2286f232012-06-15 13:27:53 +0000267 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000268 // Skip metainf contents
269 if (!done.contains(entry.getKey()))
270 writeResource(jout, directories, entry.getKey(), entry.getValue());
271 }
272 jout.finish();
273 }
274
275 private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
276 check();
277 if (nomanifest)
278 return;
279
280 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
281 jout.putNextEntry(ze);
282 writeManifest(jout);
283 jout.closeEntry();
284 done.add(ze.getName());
285 }
286
287 /**
288 * Cleanup the manifest for writing. Cleaning up consists of adding a space
289 * after any \n to prevent the manifest to see this newline as a delimiter.
290 *
291 * @param out
292 * Output
293 * @throws IOException
294 */
295
296 public void writeManifest(OutputStream out) throws Exception {
297 check();
298 writeManifest(getManifest(), out);
299 }
300
301 public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
302 if (manifest == null)
303 return;
304
305 manifest = clean(manifest);
306 outputManifest(manifest, out);
307 }
308
309 /**
310 * Unfortunately we have to write our own manifest :-( because of a stupid
311 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
Stuart McCulloch2286f232012-06-15 13:27:53 +0000312 * it makes the bytes platform dependent. So the following code outputs the
313 * manifest. A Manifest consists of
Stuart McCullochbb014372012-06-07 21:57:32 +0000314 *
315 * <pre>
316 * 'Manifest-Version: 1.0\r\n'
317 * main-attributes *
318 * \r\n
319 * name-section
320 *
321 * main-attributes ::= attributes
322 * attributes ::= key ': ' value '\r\n'
323 * name-section ::= 'Name: ' name '\r\n' attributes
324 * </pre>
325 *
326 * Lines in the manifest should not exceed 72 bytes (! this is where the
327 * manifest screwed up as well when 16 bit unicodes were used).
Stuart McCullochbb014372012-06-07 21:57:32 +0000328 * <p>
329 * As a bonus, we can now sort the manifest!
330 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000331 static byte[] CONTINUE = new byte[] {
332 '\r', '\n', ' '
333 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000334
335 /**
336 * Main function to output a manifest properly in UTF-8.
337 *
338 * @param manifest
339 * The manifest to output
340 * @param out
341 * The output stream
342 * @throws IOException
343 * when something fails
344 */
345 public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
346 writeEntry(out, "Manifest-Version", "1.0");
347 attributes(manifest.getMainAttributes(), out);
348
349 TreeSet<String> keys = new TreeSet<String>();
350 for (Object o : manifest.getEntries().keySet())
351 keys.add(o.toString());
352
353 for (String key : keys) {
354 write(out, 0, "\r\n");
355 writeEntry(out, "Name", key);
356 attributes(manifest.getAttributes(key), out);
357 }
358 out.flush();
359 }
360
361 /**
362 * Write out an entry, handling proper unicode and line length constraints
Stuart McCullochbb014372012-06-07 21:57:32 +0000363 */
364 private static void writeEntry(OutputStream out, String name, String value) throws IOException {
365 int n = write(out, 0, name + ": ");
366 write(out, n, value);
367 write(out, 0, "\r\n");
368 }
369
370 /**
371 * Convert a string to bytes with UTF8 and then output in max 72 bytes
372 *
373 * @param out
374 * the output string
375 * @param i
376 * the current width
377 * @param s
378 * the string to output
379 * @return the new width
380 * @throws IOException
381 * when something fails
382 */
383 private static int write(OutputStream out, int i, String s) throws IOException {
384 byte[] bytes = s.getBytes("UTF8");
385 return write(out, i, bytes);
386 }
387
388 /**
389 * Write the bytes but ensure that the line length does not exceed 72
390 * characters. If it is more than 70 characters, we just put a cr/lf +
391 * space.
392 *
393 * @param out
394 * The output stream
395 * @param width
396 * The nr of characters output in a line before this method
397 * started
398 * @param bytes
399 * the bytes to output
400 * @return the nr of characters in the last line
401 * @throws IOException
402 * if something fails
403 */
404 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
405 int w = width;
406 for (int i = 0; i < bytes.length; i++) {
407 if (w >= 72) { // we need to add the \n\r!
408 out.write(CONTINUE);
409 w = 1;
410 }
411 out.write(bytes[i]);
412 w++;
413 }
414 return w;
415 }
416
417 /**
418 * Output an Attributes map. We will sort this map before outputing.
419 *
420 * @param value
421 * the attrbutes
422 * @param out
423 * the output stream
424 * @throws IOException
425 * when something fails
426 */
427 private static void attributes(Attributes value, OutputStream out) throws IOException {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000428 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
429 for (Map.Entry<Object,Object> entry : value.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000430 map.put(entry.getKey().toString(), entry.getValue().toString());
431 }
432
433 map.remove("Manifest-Version"); // get rid of
434 // manifest
435 // version
Stuart McCulloch2286f232012-06-15 13:27:53 +0000436 for (Map.Entry<String,String> entry : map.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000437 writeEntry(out, entry.getKey(), entry.getValue());
438 }
439 }
440
441 private static Manifest clean(Manifest org) {
442
443 Manifest result = new Manifest();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000444 for (Map.Entry< ? , ? > entry : org.getMainAttributes().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000445 String nice = clean((String) entry.getValue());
446 result.getMainAttributes().put(entry.getKey(), nice);
447 }
448 for (String name : org.getEntries().keySet()) {
449 Attributes attrs = result.getAttributes(name);
450 if (attrs == null) {
451 attrs = new Attributes();
452 result.getEntries().put(name, attrs);
453 }
454
Stuart McCulloch2286f232012-06-15 13:27:53 +0000455 for (Map.Entry< ? , ? > entry : org.getAttributes(name).entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000456 String nice = clean((String) entry.getValue());
Stuart McCullochd4826102012-06-26 16:34:24 +0000457 attrs.put(entry.getKey(), nice);
Stuart McCullochbb014372012-06-07 21:57:32 +0000458 }
459 }
460 return result;
461 }
462
463 private static String clean(String s) {
464 if (s.indexOf('\n') < 0)
465 return s;
466
467 StringBuilder sb = new StringBuilder(s);
468 for (int i = 0; i < sb.length(); i++) {
469 if (sb.charAt(i) == '\n')
470 sb.insert(++i, ' ');
471 }
472 return sb.toString();
473 }
474
Stuart McCulloch2286f232012-06-15 13:27:53 +0000475 private void writeResource(ZipOutputStream jout, Set<String> directories, String path, Resource resource)
476 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000477 if (resource == null)
478 return;
479 try {
480 createDirectories(directories, jout, path);
481 ZipEntry ze = new ZipEntry(path);
482 ze.setMethod(ZipEntry.DEFLATED);
483 long lastModified = resource.lastModified();
484 if (lastModified == 0L) {
485 lastModified = System.currentTimeMillis();
486 }
487 ze.setTime(lastModified);
488 if (resource.getExtra() != null)
489 ze.setExtra(resource.getExtra().getBytes("UTF-8"));
490 jout.putNextEntry(ze);
491 resource.write(jout);
492 jout.closeEntry();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000493 }
494 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000495 throw new Exception("Problem writing resource " + path, e);
496 }
497 }
498
Stuart McCulloch2286f232012-06-15 13:27:53 +0000499 void createDirectories(Set<String> directories, ZipOutputStream zip, String name) throws IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000500 int index = name.lastIndexOf('/');
501 if (index > 0) {
502 String path = name.substring(0, index);
503 if (directories.contains(path))
504 return;
505 createDirectories(directories, zip, path);
506 ZipEntry ze = new ZipEntry(path + '/');
507 zip.putNextEntry(ze);
508 zip.closeEntry();
509 directories.add(path);
510 }
511 }
512
513 public String getName() {
514 return name;
515 }
516
517 /**
518 * Add all the resources in the given jar that match the given filter.
519 *
520 * @param sub
521 * the jar
522 * @param filter
523 * a pattern that should match the resoures in sub to be added
524 */
525 public boolean addAll(Jar sub, Instruction filter) {
526 return addAll(sub, filter, "");
527 }
528
529 /**
530 * Add all the resources in the given jar that match the given filter.
531 *
532 * @param sub
533 * the jar
534 * @param filter
535 * a pattern that should match the resoures in sub to be added
536 */
537 public boolean addAll(Jar sub, Instruction filter, String destination) {
538 check();
539 boolean dupl = false;
540 for (String name : sub.getResources().keySet()) {
541 if ("META-INF/MANIFEST.MF".equals(name))
542 continue;
543
544 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000545 dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000546 }
547 return dupl;
548 }
549
550 public void close() {
551 this.closed = true;
552 if (zipFile != null)
553 try {
554 zipFile.close();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000555 }
556 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000557 // Ignore
558 }
559 resources.clear();
560 directories.clear();
561 manifest = null;
562 source = null;
563 }
564
565 public long lastModified() {
566 return lastModified;
567 }
568
569 public void updateModified(long time, String reason) {
570 if (time > lastModified) {
571 lastModified = time;
572 lastModifiedReason = reason;
573 }
574 }
575
576 public void setReporter(Reporter reporter) {
577 this.reporter = reporter;
578 }
579
580 public boolean hasDirectory(String path) {
581 check();
582 return directories.get(path) != null;
583 }
584
585 public List<String> getPackages() {
586 check();
587 List<String> list = new ArrayList<String>(directories.size());
588
Stuart McCulloch2286f232012-06-15 13:27:53 +0000589 for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000590 if (i.getValue() != null) {
591 String path = i.getKey();
592 String pack = path.replace('/', '.');
593 list.add(pack);
594 }
595 }
596 return list;
597 }
598
599 public File getSource() {
600 check();
601 return source;
602 }
603
604 public boolean addAll(Jar src) {
605 check();
606 return addAll(src, null);
607 }
608
609 public boolean rename(String oldPath, String newPath) {
610 check();
611 Resource resource = remove(oldPath);
612 if (resource == null)
613 return false;
614
615 return putResource(newPath, resource);
616 }
617
618 public Resource remove(String path) {
619 check();
620 Resource resource = resources.remove(path);
621 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000622 Map<String,Resource> mdir = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000623 // must be != null
624 mdir.remove(path);
625 return resource;
626 }
627
628 /**
629 * Make sure nobody touches the manifest! If the bundle is signed, we do not
630 * want anybody to touch the manifest after the digests have been
631 * calculated.
632 */
633 public void setDoNotTouchManifest() {
634 doNotTouchManifest = true;
635 }
636
637 /**
638 * Calculate the checksums and set them in the manifest.
639 */
640
641 public void calcChecksums(String algorithms[]) throws Exception {
642 check();
643 if (algorithms == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000644 algorithms = new String[] {
645 "SHA", "MD5"
646 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000647
648 Manifest m = getManifest();
649 if (m == null) {
650 m = new Manifest();
651 setManifest(m);
652 }
653
654 MessageDigest digests[] = new MessageDigest[algorithms.length];
655 int n = 0;
656 for (String algorithm : algorithms)
657 digests[n++] = MessageDigest.getInstance(algorithm);
658
659 byte buffer[] = new byte[30000];
660
Stuart McCulloch2286f232012-06-15 13:27:53 +0000661 for (Map.Entry<String,Resource> entry : resources.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000662
663 // Skip the manifest
664 if (entry.getKey().equals("META-INF/MANIFEST.MF"))
665 continue;
666
667 Resource r = entry.getValue();
668 Attributes attributes = m.getAttributes(entry.getKey());
669 if (attributes == null) {
670 attributes = new Attributes();
671 getManifest().getEntries().put(entry.getKey(), attributes);
672 }
673 InputStream in = r.openInputStream();
674 try {
675 for (MessageDigest d : digests)
676 d.reset();
677 int size = in.read(buffer);
678 while (size > 0) {
679 for (MessageDigest d : digests)
680 d.update(buffer, 0, size);
681 size = in.read(buffer);
682 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000683 }
684 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000685 in.close();
686 }
687 for (MessageDigest d : digests)
688 attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
689 }
690 }
691
692 Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
693
694 public String getBsn() throws Exception {
695 check();
696 Manifest m = getManifest();
697 if (m == null)
698 return null;
699
700 String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
701 if (s == null)
702 return null;
703
704 Matcher matcher = BSN.matcher(s);
705 if (matcher.matches()) {
706 return matcher.group(1);
707 }
708 return null;
709 }
710
711 public String getVersion() throws Exception {
712 check();
713 Manifest m = getManifest();
714 if (m == null)
715 return null;
716
717 String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
718 if (s == null)
719 return null;
720
721 return s.trim();
722 }
723
724 /**
725 * Expand the JAR file to a directory.
726 *
727 * @param dir
728 * the dst directory, is not required to exist
729 * @throws Exception
730 * if anything does not work as expected.
731 */
732 public void expand(File dir) throws Exception {
733 check();
734 dir = dir.getAbsoluteFile();
735 dir.mkdirs();
736 if (!dir.isDirectory()) {
737 throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
738 }
739
Stuart McCulloch2286f232012-06-15 13:27:53 +0000740 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000741 File f = getFile(dir, entry.getKey());
742 f.getParentFile().mkdirs();
743 IO.copy(entry.getValue().openInputStream(), f);
744 }
745 }
746
747 /**
748 * Make sure we have a manifest
749 *
750 * @throws Exception
751 */
752 public void ensureManifest() throws Exception {
753 if (getManifest() != null)
754 return;
755 manifest = new Manifest();
756 }
757
758 /**
759 * Answer if the manifest was the first entry
760 */
761
762 public boolean isManifestFirst() {
763 return manifestFirst;
764 }
765
766 public void copy(Jar srce, String path, boolean overwrite) {
767 check();
768 addDirectory(srce.getDirectories().get(path), overwrite);
769 }
770
771 public void setCompression(Compression compression) {
772 this.compression = compression;
773 }
774
775 public Compression hasCompression() {
776 return this.compression;
777 }
778
779 void check() {
780 if (closed)
781 throw new RuntimeException("Already closed " + name);
782 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000783
784 /**
785 * Return a data uri from the JAR. The data must be less than 32k
786 *
787 * @param jar
788 * The jar to load the data from
789 * @param path
790 * the path in the jar
791 * @param mime
792 * the mime type
793 * @return a URI or null if conversion could not take place
794 */
795
796 public URI getDataURI(String path, String mime, int max) throws Exception {
797 Resource r = getResource(path);
798
799 if (r.size() >= max || r.size() <= 0)
800 return null;
801
802 byte[] data = new byte[(int) r.size()];
803 DataInputStream din = new DataInputStream(r.openInputStream());
804 try {
805 din.readFully(data);
806 String encoded = Base64.encodeBase64(data);
807 return new URI("data:" + mime + ";base64," + encoded);
808 }
809 finally {
810 din.close();
811 }
812 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000813}