blob: 725b065fdda3707c6e1f6af5cd39ef8dff8ef62f [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.lib.osgi;
2
3import static aQute.lib.io.IO.*;
4
5import java.io.*;
6import java.security.*;
7import java.util.*;
8import java.util.jar.*;
9import java.util.regex.*;
10import java.util.zip.*;
11
12import aQute.lib.base64.*;
13import aQute.lib.io.*;
Stuart McCulloch81d48de2012-06-29 19:23:09 +000014import aQute.service.reporter.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000015
16public class Jar implements Closeable {
17 public enum Compression {
18 DEFLATE, STORE
19 }
20
Stuart McCulloch2286f232012-06-15 13:27:53 +000021 public static final Object[] EMPTY_ARRAY = new Jar[0];
22 final Map<String,Resource> resources = new TreeMap<String,Resource>();
23 final Map<String,Map<String,Resource>> directories = new TreeMap<String,Map<String,Resource>>();
24 Manifest manifest;
25 boolean manifestFirst;
26 String name;
27 File source;
28 ZipFile zipFile;
29 long lastModified;
30 String lastModifiedReason;
31 Reporter reporter;
32 boolean doNotTouchManifest;
33 boolean nomanifest;
34 Compression compression = Compression.DEFLATE;
35 boolean closed;
Stuart McCullochbb014372012-06-07 21:57:32 +000036
37 public Jar(String name) {
38 this.name = name;
39 }
40
41 public Jar(String name, File dirOrFile, Pattern doNotCopy) throws ZipException, IOException {
42 this(name);
43 source = dirOrFile;
44 if (dirOrFile.isDirectory())
45 FileResource.build(this, dirOrFile, doNotCopy);
46 else if (dirOrFile.isFile()) {
47 zipFile = ZipResource.build(this, dirOrFile);
48 } else {
Stuart McCulloch2286f232012-06-15 13:27:53 +000049 throw new IllegalArgumentException("A Jar can only accept a valid file or directory: " + dirOrFile);
Stuart McCullochbb014372012-06-07 21:57:32 +000050 }
51 }
52
53 public Jar(String name, InputStream in, long lastModified) throws IOException {
54 this(name);
55 EmbeddedResource.build(this, in, lastModified);
56 }
57
58 public Jar(String name, String path) throws IOException {
59 this(name);
60 File f = new File(path);
61 InputStream in = new FileInputStream(f);
62 EmbeddedResource.build(this, in, f.lastModified());
63 in.close();
64 }
65
66 public Jar(File f) throws IOException {
67 this(getName(f), f, null);
68 }
69
70 /**
71 * Make the JAR file name the project name if we get a src or bin directory.
72 *
73 * @param f
74 * @return
75 */
76 private static String getName(File f) {
77 f = f.getAbsoluteFile();
78 String name = f.getName();
79 if (name.equals("bin") || name.equals("src"))
80 return f.getParentFile().getName();
Stuart McCullochd4826102012-06-26 16:34:24 +000081 if (name.endsWith(".jar"))
82 name = name.substring(0, name.length() - 4);
83 return name;
Stuart McCullochbb014372012-06-07 21:57:32 +000084 }
85
86 public Jar(String string, InputStream resourceAsStream) throws IOException {
87 this(string, resourceAsStream, 0);
88 }
89
90 public Jar(String string, File file) throws ZipException, IOException {
91 this(string, file, Pattern.compile(Constants.DEFAULT_DO_NOT_COPY));
92 }
93
94 public void setName(String name) {
95 this.name = name;
96 }
97
98 public String toString() {
99 return "Jar:" + name;
100 }
101
102 public boolean putResource(String path, Resource resource) {
103 check();
104 return putResource(path, resource, true);
105 }
106
107 public boolean putResource(String path, Resource resource, boolean overwrite) {
108 check();
109 updateModified(resource.lastModified(), path);
110 while (path.startsWith("/"))
111 path = path.substring(1);
112
113 if (path.equals("META-INF/MANIFEST.MF")) {
114 manifest = null;
115 if (resources.isEmpty())
116 manifestFirst = true;
117 }
118 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000119 Map<String,Resource> s = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000120 if (s == null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000121 s = new TreeMap<String,Resource>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000122 directories.put(dir, s);
123 int n = dir.lastIndexOf('/');
124 while (n > 0) {
125 String dd = dir.substring(0, n);
126 if (directories.containsKey(dd))
127 break;
128 directories.put(dd, null);
129 n = dd.lastIndexOf('/');
130 }
131 }
132 boolean duplicate = s.containsKey(path);
133 if (!duplicate || overwrite) {
134 resources.put(path, resource);
135 s.put(path, resource);
136 }
137 return duplicate;
138 }
139
140 public Resource getResource(String path) {
141 check();
142 if (resources == null)
143 return null;
144 return resources.get(path);
145 }
146
147 private String getDirectory(String path) {
148 check();
149 int n = path.lastIndexOf('/');
150 if (n < 0)
151 return "";
152
153 return path.substring(0, n);
154 }
155
Stuart McCulloch2286f232012-06-15 13:27:53 +0000156 public Map<String,Map<String,Resource>> getDirectories() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000157 check();
158 return directories;
159 }
160
Stuart McCulloch2286f232012-06-15 13:27:53 +0000161 public Map<String,Resource> getResources() {
Stuart McCullochbb014372012-06-07 21:57:32 +0000162 check();
163 return resources;
164 }
165
Stuart McCulloch2286f232012-06-15 13:27:53 +0000166 public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000167 check();
168 boolean duplicates = false;
169 if (directory == null)
170 return false;
171
Stuart McCulloch2286f232012-06-15 13:27:53 +0000172 for (Map.Entry<String,Resource> entry : directory.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000173 String key = entry.getKey();
174 if (!key.endsWith(".java")) {
175 duplicates |= putResource(key, entry.getValue(), overwrite);
176 }
177 }
178 return duplicates;
179 }
180
181 public Manifest getManifest() throws Exception {
182 check();
183 if (manifest == null) {
184 Resource manifestResource = getResource("META-INF/MANIFEST.MF");
185 if (manifestResource != null) {
186 InputStream in = manifestResource.openInputStream();
187 manifest = new Manifest(in);
188 in.close();
189 }
190 }
191 return manifest;
192 }
193
194 public boolean exists(String path) {
195 check();
196 return resources.containsKey(path);
197 }
198
199 public void setManifest(Manifest manifest) {
200 check();
201 manifestFirst = true;
202 this.manifest = manifest;
203 }
204
205 public void setManifest(File file) throws IOException {
206 check();
207 FileInputStream fin = new FileInputStream(file);
208 try {
209 Manifest m = new Manifest(fin);
210 setManifest(m);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000211 }
212 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000213 fin.close();
214 }
215 }
216
217 public void write(File file) throws Exception {
218 check();
219 try {
220 OutputStream out = new FileOutputStream(file);
221 try {
222 write(out);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000223 }
224 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000225 IO.close(out);
226 }
227 return;
228
Stuart McCulloch2286f232012-06-15 13:27:53 +0000229 }
230 catch (Exception t) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000231 file.delete();
232 throw t;
233 }
234 }
235
236 public void write(String file) throws Exception {
237 check();
238 write(new File(file));
239 }
240
241 public void write(OutputStream out) throws Exception {
242 check();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000243 ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
Stuart McCullochbb014372012-06-07 21:57:32 +0000244
245 switch (compression) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000246 case STORE :
247 jout.setMethod(ZipOutputStream.DEFLATED);
248 break;
Stuart McCullochbb014372012-06-07 21:57:32 +0000249
Stuart McCulloch2286f232012-06-15 13:27:53 +0000250 default :
251 // default is DEFLATED
Stuart McCullochbb014372012-06-07 21:57:32 +0000252 }
253
254 Set<String> done = new HashSet<String>();
255
256 Set<String> directories = new HashSet<String>();
257 if (doNotTouchManifest) {
258 Resource r = getResource("META-INF/MANIFEST.MF");
259 if (r != null) {
260 writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
261 done.add("META-INF/MANIFEST.MF");
262 }
263 } else
264 doManifest(done, jout);
265
Stuart McCulloch2286f232012-06-15 13:27:53 +0000266 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000267 // Skip metainf contents
268 if (!done.contains(entry.getKey()))
269 writeResource(jout, directories, entry.getKey(), entry.getValue());
270 }
271 jout.finish();
272 }
273
274 private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
275 check();
276 if (nomanifest)
277 return;
278
279 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
280 jout.putNextEntry(ze);
281 writeManifest(jout);
282 jout.closeEntry();
283 done.add(ze.getName());
284 }
285
286 /**
287 * Cleanup the manifest for writing. Cleaning up consists of adding a space
288 * after any \n to prevent the manifest to see this newline as a delimiter.
289 *
290 * @param out
291 * Output
292 * @throws IOException
293 */
294
295 public void writeManifest(OutputStream out) throws Exception {
296 check();
297 writeManifest(getManifest(), out);
298 }
299
300 public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
301 if (manifest == null)
302 return;
303
304 manifest = clean(manifest);
305 outputManifest(manifest, out);
306 }
307
308 /**
309 * Unfortunately we have to write our own manifest :-( because of a stupid
310 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
Stuart McCulloch2286f232012-06-15 13:27:53 +0000311 * it makes the bytes platform dependent. So the following code outputs the
312 * manifest. A Manifest consists of
Stuart McCullochbb014372012-06-07 21:57:32 +0000313 *
314 * <pre>
315 * 'Manifest-Version: 1.0\r\n'
316 * main-attributes *
317 * \r\n
318 * name-section
319 *
320 * main-attributes ::= attributes
321 * attributes ::= key ': ' value '\r\n'
322 * name-section ::= 'Name: ' name '\r\n' attributes
323 * </pre>
324 *
325 * Lines in the manifest should not exceed 72 bytes (! this is where the
326 * manifest screwed up as well when 16 bit unicodes were used).
Stuart McCullochbb014372012-06-07 21:57:32 +0000327 * <p>
328 * As a bonus, we can now sort the manifest!
329 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000330 static byte[] CONTINUE = new byte[] {
331 '\r', '\n', ' '
332 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000333
334 /**
335 * Main function to output a manifest properly in UTF-8.
336 *
337 * @param manifest
338 * The manifest to output
339 * @param out
340 * The output stream
341 * @throws IOException
342 * when something fails
343 */
344 public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
345 writeEntry(out, "Manifest-Version", "1.0");
346 attributes(manifest.getMainAttributes(), out);
347
348 TreeSet<String> keys = new TreeSet<String>();
349 for (Object o : manifest.getEntries().keySet())
350 keys.add(o.toString());
351
352 for (String key : keys) {
353 write(out, 0, "\r\n");
354 writeEntry(out, "Name", key);
355 attributes(manifest.getAttributes(key), out);
356 }
357 out.flush();
358 }
359
360 /**
361 * Write out an entry, handling proper unicode and line length constraints
Stuart McCullochbb014372012-06-07 21:57:32 +0000362 */
363 private static void writeEntry(OutputStream out, String name, String value) throws IOException {
364 int n = write(out, 0, name + ": ");
365 write(out, n, value);
366 write(out, 0, "\r\n");
367 }
368
369 /**
370 * Convert a string to bytes with UTF8 and then output in max 72 bytes
371 *
372 * @param out
373 * the output string
374 * @param i
375 * the current width
376 * @param s
377 * the string to output
378 * @return the new width
379 * @throws IOException
380 * when something fails
381 */
382 private static int write(OutputStream out, int i, String s) throws IOException {
383 byte[] bytes = s.getBytes("UTF8");
384 return write(out, i, bytes);
385 }
386
387 /**
388 * Write the bytes but ensure that the line length does not exceed 72
389 * characters. If it is more than 70 characters, we just put a cr/lf +
390 * space.
391 *
392 * @param out
393 * The output stream
394 * @param width
395 * The nr of characters output in a line before this method
396 * started
397 * @param bytes
398 * the bytes to output
399 * @return the nr of characters in the last line
400 * @throws IOException
401 * if something fails
402 */
403 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
404 int w = width;
405 for (int i = 0; i < bytes.length; i++) {
406 if (w >= 72) { // we need to add the \n\r!
407 out.write(CONTINUE);
408 w = 1;
409 }
410 out.write(bytes[i]);
411 w++;
412 }
413 return w;
414 }
415
416 /**
417 * Output an Attributes map. We will sort this map before outputing.
418 *
419 * @param value
420 * the attrbutes
421 * @param out
422 * the output stream
423 * @throws IOException
424 * when something fails
425 */
426 private static void attributes(Attributes value, OutputStream out) throws IOException {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000427 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
428 for (Map.Entry<Object,Object> entry : value.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000429 map.put(entry.getKey().toString(), entry.getValue().toString());
430 }
431
432 map.remove("Manifest-Version"); // get rid of
433 // manifest
434 // version
Stuart McCulloch2286f232012-06-15 13:27:53 +0000435 for (Map.Entry<String,String> entry : map.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000436 writeEntry(out, entry.getKey(), entry.getValue());
437 }
438 }
439
440 private static Manifest clean(Manifest org) {
441
442 Manifest result = new Manifest();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000443 for (Map.Entry< ? , ? > entry : org.getMainAttributes().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000444 String nice = clean((String) entry.getValue());
445 result.getMainAttributes().put(entry.getKey(), nice);
446 }
447 for (String name : org.getEntries().keySet()) {
448 Attributes attrs = result.getAttributes(name);
449 if (attrs == null) {
450 attrs = new Attributes();
451 result.getEntries().put(name, attrs);
452 }
453
Stuart McCulloch2286f232012-06-15 13:27:53 +0000454 for (Map.Entry< ? , ? > entry : org.getAttributes(name).entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000455 String nice = clean((String) entry.getValue());
Stuart McCullochd4826102012-06-26 16:34:24 +0000456 attrs.put(entry.getKey(), nice);
Stuart McCullochbb014372012-06-07 21:57:32 +0000457 }
458 }
459 return result;
460 }
461
462 private static String clean(String s) {
463 if (s.indexOf('\n') < 0)
464 return s;
465
466 StringBuilder sb = new StringBuilder(s);
467 for (int i = 0; i < sb.length(); i++) {
468 if (sb.charAt(i) == '\n')
469 sb.insert(++i, ' ');
470 }
471 return sb.toString();
472 }
473
Stuart McCulloch2286f232012-06-15 13:27:53 +0000474 private void writeResource(ZipOutputStream jout, Set<String> directories, String path, Resource resource)
475 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000476 if (resource == null)
477 return;
478 try {
479 createDirectories(directories, jout, path);
480 ZipEntry ze = new ZipEntry(path);
481 ze.setMethod(ZipEntry.DEFLATED);
482 long lastModified = resource.lastModified();
483 if (lastModified == 0L) {
484 lastModified = System.currentTimeMillis();
485 }
486 ze.setTime(lastModified);
487 if (resource.getExtra() != null)
488 ze.setExtra(resource.getExtra().getBytes("UTF-8"));
489 jout.putNextEntry(ze);
490 resource.write(jout);
491 jout.closeEntry();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000492 }
493 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000494 throw new Exception("Problem writing resource " + path, e);
495 }
496 }
497
Stuart McCulloch2286f232012-06-15 13:27:53 +0000498 void createDirectories(Set<String> directories, ZipOutputStream zip, String name) throws IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000499 int index = name.lastIndexOf('/');
500 if (index > 0) {
501 String path = name.substring(0, index);
502 if (directories.contains(path))
503 return;
504 createDirectories(directories, zip, path);
505 ZipEntry ze = new ZipEntry(path + '/');
506 zip.putNextEntry(ze);
507 zip.closeEntry();
508 directories.add(path);
509 }
510 }
511
512 public String getName() {
513 return name;
514 }
515
516 /**
517 * Add all the resources in the given jar that match the given filter.
518 *
519 * @param sub
520 * the jar
521 * @param filter
522 * a pattern that should match the resoures in sub to be added
523 */
524 public boolean addAll(Jar sub, Instruction filter) {
525 return addAll(sub, filter, "");
526 }
527
528 /**
529 * Add all the resources in the given jar that match the given filter.
530 *
531 * @param sub
532 * the jar
533 * @param filter
534 * a pattern that should match the resoures in sub to be added
535 */
536 public boolean addAll(Jar sub, Instruction filter, String destination) {
537 check();
538 boolean dupl = false;
539 for (String name : sub.getResources().keySet()) {
540 if ("META-INF/MANIFEST.MF".equals(name))
541 continue;
542
543 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000544 dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +0000545 }
546 return dupl;
547 }
548
549 public void close() {
550 this.closed = true;
551 if (zipFile != null)
552 try {
553 zipFile.close();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000554 }
555 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000556 // Ignore
557 }
558 resources.clear();
559 directories.clear();
560 manifest = null;
561 source = null;
562 }
563
564 public long lastModified() {
565 return lastModified;
566 }
567
568 public void updateModified(long time, String reason) {
569 if (time > lastModified) {
570 lastModified = time;
571 lastModifiedReason = reason;
572 }
573 }
574
575 public void setReporter(Reporter reporter) {
576 this.reporter = reporter;
577 }
578
579 public boolean hasDirectory(String path) {
580 check();
581 return directories.get(path) != null;
582 }
583
584 public List<String> getPackages() {
585 check();
586 List<String> list = new ArrayList<String>(directories.size());
587
Stuart McCulloch2286f232012-06-15 13:27:53 +0000588 for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000589 if (i.getValue() != null) {
590 String path = i.getKey();
591 String pack = path.replace('/', '.');
592 list.add(pack);
593 }
594 }
595 return list;
596 }
597
598 public File getSource() {
599 check();
600 return source;
601 }
602
603 public boolean addAll(Jar src) {
604 check();
605 return addAll(src, null);
606 }
607
608 public boolean rename(String oldPath, String newPath) {
609 check();
610 Resource resource = remove(oldPath);
611 if (resource == null)
612 return false;
613
614 return putResource(newPath, resource);
615 }
616
617 public Resource remove(String path) {
618 check();
619 Resource resource = resources.remove(path);
620 String dir = getDirectory(path);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000621 Map<String,Resource> mdir = directories.get(dir);
Stuart McCullochbb014372012-06-07 21:57:32 +0000622 // must be != null
623 mdir.remove(path);
624 return resource;
625 }
626
627 /**
628 * Make sure nobody touches the manifest! If the bundle is signed, we do not
629 * want anybody to touch the manifest after the digests have been
630 * calculated.
631 */
632 public void setDoNotTouchManifest() {
633 doNotTouchManifest = true;
634 }
635
636 /**
637 * Calculate the checksums and set them in the manifest.
638 */
639
640 public void calcChecksums(String algorithms[]) throws Exception {
641 check();
642 if (algorithms == null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000643 algorithms = new String[] {
644 "SHA", "MD5"
645 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000646
647 Manifest m = getManifest();
648 if (m == null) {
649 m = new Manifest();
650 setManifest(m);
651 }
652
653 MessageDigest digests[] = new MessageDigest[algorithms.length];
654 int n = 0;
655 for (String algorithm : algorithms)
656 digests[n++] = MessageDigest.getInstance(algorithm);
657
658 byte buffer[] = new byte[30000];
659
Stuart McCulloch2286f232012-06-15 13:27:53 +0000660 for (Map.Entry<String,Resource> entry : resources.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000661
662 // Skip the manifest
663 if (entry.getKey().equals("META-INF/MANIFEST.MF"))
664 continue;
665
666 Resource r = entry.getValue();
667 Attributes attributes = m.getAttributes(entry.getKey());
668 if (attributes == null) {
669 attributes = new Attributes();
670 getManifest().getEntries().put(entry.getKey(), attributes);
671 }
672 InputStream in = r.openInputStream();
673 try {
674 for (MessageDigest d : digests)
675 d.reset();
676 int size = in.read(buffer);
677 while (size > 0) {
678 for (MessageDigest d : digests)
679 d.update(buffer, 0, size);
680 size = in.read(buffer);
681 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000682 }
683 finally {
Stuart McCullochbb014372012-06-07 21:57:32 +0000684 in.close();
685 }
686 for (MessageDigest d : digests)
687 attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
688 }
689 }
690
691 Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
692
693 public String getBsn() throws Exception {
694 check();
695 Manifest m = getManifest();
696 if (m == null)
697 return null;
698
699 String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
700 if (s == null)
701 return null;
702
703 Matcher matcher = BSN.matcher(s);
704 if (matcher.matches()) {
705 return matcher.group(1);
706 }
707 return null;
708 }
709
710 public String getVersion() throws Exception {
711 check();
712 Manifest m = getManifest();
713 if (m == null)
714 return null;
715
716 String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
717 if (s == null)
718 return null;
719
720 return s.trim();
721 }
722
723 /**
724 * Expand the JAR file to a directory.
725 *
726 * @param dir
727 * the dst directory, is not required to exist
728 * @throws Exception
729 * if anything does not work as expected.
730 */
731 public void expand(File dir) throws Exception {
732 check();
733 dir = dir.getAbsoluteFile();
734 dir.mkdirs();
735 if (!dir.isDirectory()) {
736 throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
737 }
738
Stuart McCulloch2286f232012-06-15 13:27:53 +0000739 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000740 File f = getFile(dir, entry.getKey());
741 f.getParentFile().mkdirs();
742 IO.copy(entry.getValue().openInputStream(), f);
743 }
744 }
745
746 /**
747 * Make sure we have a manifest
748 *
749 * @throws Exception
750 */
751 public void ensureManifest() throws Exception {
752 if (getManifest() != null)
753 return;
754 manifest = new Manifest();
755 }
756
757 /**
758 * Answer if the manifest was the first entry
759 */
760
761 public boolean isManifestFirst() {
762 return manifestFirst;
763 }
764
765 public void copy(Jar srce, String path, boolean overwrite) {
766 check();
767 addDirectory(srce.getDirectories().get(path), overwrite);
768 }
769
770 public void setCompression(Compression compression) {
771 this.compression = compression;
772 }
773
774 public Compression hasCompression() {
775 return this.compression;
776 }
777
778 void check() {
779 if (closed)
780 throw new RuntimeException("Already closed " + name);
781 }
782}