blob: 032a7d1b40a7d1af86f9b3686eddd65b9c990f07 [file] [log] [blame]
Stuart McCullochf3173222012-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.*;
14import aQute.libg.reporter.*;
15
16public class Jar implements Closeable {
17 public enum Compression {
18 DEFLATE, STORE
19 }
20
Stuart McCulloch4482c702012-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 McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +000049 throw new IllegalArgumentException("A Jar can only accept a valid file or directory: " + dirOrFile);
Stuart McCullochf3173222012-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();
81 else {
82 if (name.endsWith(".jar"))
83 name = name.substring(0, name.length() - 4);
84 return name;
85 }
86 }
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 McCulloch4482c702012-06-15 13:27:53 +0000121 Map<String,Resource> s = directories.get(dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000122 if (s == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000123 s = new TreeMap<String,Resource>();
Stuart McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +0000158 public Map<String,Map<String,Resource>> getDirectories() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000159 check();
160 return directories;
161 }
162
Stuart McCulloch4482c702012-06-15 13:27:53 +0000163 public Map<String,Resource> getResources() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000164 check();
165 return resources;
166 }
167
Stuart McCulloch4482c702012-06-15 13:27:53 +0000168 public boolean addDirectory(Map<String,Resource> directory, boolean overwrite) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000169 check();
170 boolean duplicates = false;
171 if (directory == null)
172 return false;
173
Stuart McCulloch4482c702012-06-15 13:27:53 +0000174 for (Map.Entry<String,Resource> entry : directory.entrySet()) {
Stuart McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +0000213 }
214 finally {
Stuart McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +0000225 }
226 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000227 IO.close(out);
228 }
229 return;
230
Stuart McCulloch4482c702012-06-15 13:27:53 +0000231 }
232 catch (Exception t) {
Stuart McCullochf3173222012-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 McCulloch4482c702012-06-15 13:27:53 +0000245 ZipOutputStream jout = nomanifest || doNotTouchManifest ? new ZipOutputStream(out) : new JarOutputStream(out);
Stuart McCullochf3173222012-06-07 21:57:32 +0000246
247 switch (compression) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000248 case STORE :
249 jout.setMethod(ZipOutputStream.DEFLATED);
250 break;
Stuart McCullochf3173222012-06-07 21:57:32 +0000251
Stuart McCulloch4482c702012-06-15 13:27:53 +0000252 default :
253 // default is DEFLATED
Stuart McCullochf3173222012-06-07 21:57:32 +0000254 }
255
256 Set<String> done = new HashSet<String>();
257
258 Set<String> directories = new HashSet<String>();
259 if (doNotTouchManifest) {
260 Resource r = getResource("META-INF/MANIFEST.MF");
261 if (r != null) {
262 writeResource(jout, directories, "META-INF/MANIFEST.MF", r);
263 done.add("META-INF/MANIFEST.MF");
264 }
265 } else
266 doManifest(done, jout);
267
Stuart McCulloch4482c702012-06-15 13:27:53 +0000268 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000269 // Skip metainf contents
270 if (!done.contains(entry.getKey()))
271 writeResource(jout, directories, entry.getKey(), entry.getValue());
272 }
273 jout.finish();
274 }
275
276 private void doManifest(Set<String> done, ZipOutputStream jout) throws Exception {
277 check();
278 if (nomanifest)
279 return;
280
281 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
282 jout.putNextEntry(ze);
283 writeManifest(jout);
284 jout.closeEntry();
285 done.add(ze.getName());
286 }
287
288 /**
289 * Cleanup the manifest for writing. Cleaning up consists of adding a space
290 * after any \n to prevent the manifest to see this newline as a delimiter.
291 *
292 * @param out
293 * Output
294 * @throws IOException
295 */
296
297 public void writeManifest(OutputStream out) throws Exception {
298 check();
299 writeManifest(getManifest(), out);
300 }
301
302 public static void writeManifest(Manifest manifest, OutputStream out) throws IOException {
303 if (manifest == null)
304 return;
305
306 manifest = clean(manifest);
307 outputManifest(manifest, out);
308 }
309
310 /**
311 * Unfortunately we have to write our own manifest :-( because of a stupid
312 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
Stuart McCulloch4482c702012-06-15 13:27:53 +0000313 * it makes the bytes platform dependent. So the following code outputs the
314 * manifest. A Manifest consists of
Stuart McCullochf3173222012-06-07 21:57:32 +0000315 *
316 * <pre>
317 * 'Manifest-Version: 1.0\r\n'
318 * main-attributes *
319 * \r\n
320 * name-section
321 *
322 * main-attributes ::= attributes
323 * attributes ::= key ': ' value '\r\n'
324 * name-section ::= 'Name: ' name '\r\n' attributes
325 * </pre>
326 *
327 * Lines in the manifest should not exceed 72 bytes (! this is where the
328 * manifest screwed up as well when 16 bit unicodes were used).
Stuart McCullochf3173222012-06-07 21:57:32 +0000329 * <p>
330 * As a bonus, we can now sort the manifest!
331 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000332 static byte[] CONTINUE = new byte[] {
333 '\r', '\n', ' '
334 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000335
336 /**
337 * Main function to output a manifest properly in UTF-8.
338 *
339 * @param manifest
340 * The manifest to output
341 * @param out
342 * The output stream
343 * @throws IOException
344 * when something fails
345 */
346 public static void outputManifest(Manifest manifest, OutputStream out) throws IOException {
347 writeEntry(out, "Manifest-Version", "1.0");
348 attributes(manifest.getMainAttributes(), out);
349
350 TreeSet<String> keys = new TreeSet<String>();
351 for (Object o : manifest.getEntries().keySet())
352 keys.add(o.toString());
353
354 for (String key : keys) {
355 write(out, 0, "\r\n");
356 writeEntry(out, "Name", key);
357 attributes(manifest.getAttributes(key), out);
358 }
359 out.flush();
360 }
361
362 /**
363 * Write out an entry, handling proper unicode and line length constraints
Stuart McCullochf3173222012-06-07 21:57:32 +0000364 */
365 private static void writeEntry(OutputStream out, String name, String value) throws IOException {
366 int n = write(out, 0, name + ": ");
367 write(out, n, value);
368 write(out, 0, "\r\n");
369 }
370
371 /**
372 * Convert a string to bytes with UTF8 and then output in max 72 bytes
373 *
374 * @param out
375 * the output string
376 * @param i
377 * the current width
378 * @param s
379 * the string to output
380 * @return the new width
381 * @throws IOException
382 * when something fails
383 */
384 private static int write(OutputStream out, int i, String s) throws IOException {
385 byte[] bytes = s.getBytes("UTF8");
386 return write(out, i, bytes);
387 }
388
389 /**
390 * Write the bytes but ensure that the line length does not exceed 72
391 * characters. If it is more than 70 characters, we just put a cr/lf +
392 * space.
393 *
394 * @param out
395 * The output stream
396 * @param width
397 * The nr of characters output in a line before this method
398 * started
399 * @param bytes
400 * the bytes to output
401 * @return the nr of characters in the last line
402 * @throws IOException
403 * if something fails
404 */
405 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
406 int w = width;
407 for (int i = 0; i < bytes.length; i++) {
408 if (w >= 72) { // we need to add the \n\r!
409 out.write(CONTINUE);
410 w = 1;
411 }
412 out.write(bytes[i]);
413 w++;
414 }
415 return w;
416 }
417
418 /**
419 * Output an Attributes map. We will sort this map before outputing.
420 *
421 * @param value
422 * the attrbutes
423 * @param out
424 * the output stream
425 * @throws IOException
426 * when something fails
427 */
428 private static void attributes(Attributes value, OutputStream out) throws IOException {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000429 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
430 for (Map.Entry<Object,Object> entry : value.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000431 map.put(entry.getKey().toString(), entry.getValue().toString());
432 }
433
434 map.remove("Manifest-Version"); // get rid of
435 // manifest
436 // version
Stuart McCulloch4482c702012-06-15 13:27:53 +0000437 for (Map.Entry<String,String> entry : map.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000438 writeEntry(out, entry.getKey(), entry.getValue());
439 }
440 }
441
442 private static Manifest clean(Manifest org) {
443
444 Manifest result = new Manifest();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000445 for (Map.Entry< ? , ? > entry : org.getMainAttributes().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000446 String nice = clean((String) entry.getValue());
447 result.getMainAttributes().put(entry.getKey(), nice);
448 }
449 for (String name : org.getEntries().keySet()) {
450 Attributes attrs = result.getAttributes(name);
451 if (attrs == null) {
452 attrs = new Attributes();
453 result.getEntries().put(name, attrs);
454 }
455
Stuart McCulloch4482c702012-06-15 13:27:53 +0000456 for (Map.Entry< ? , ? > entry : org.getAttributes(name).entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000457 String nice = clean((String) entry.getValue());
458 attrs.put((Attributes.Name) entry.getKey(), nice);
459 }
460 }
461 return result;
462 }
463
464 private static String clean(String s) {
465 if (s.indexOf('\n') < 0)
466 return s;
467
468 StringBuilder sb = new StringBuilder(s);
469 for (int i = 0; i < sb.length(); i++) {
470 if (sb.charAt(i) == '\n')
471 sb.insert(++i, ' ');
472 }
473 return sb.toString();
474 }
475
Stuart McCulloch4482c702012-06-15 13:27:53 +0000476 private void writeResource(ZipOutputStream jout, Set<String> directories, String path, Resource resource)
477 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000478 if (resource == null)
479 return;
480 try {
481 createDirectories(directories, jout, path);
482 ZipEntry ze = new ZipEntry(path);
483 ze.setMethod(ZipEntry.DEFLATED);
484 long lastModified = resource.lastModified();
485 if (lastModified == 0L) {
486 lastModified = System.currentTimeMillis();
487 }
488 ze.setTime(lastModified);
489 if (resource.getExtra() != null)
490 ze.setExtra(resource.getExtra().getBytes("UTF-8"));
491 jout.putNextEntry(ze);
492 resource.write(jout);
493 jout.closeEntry();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000494 }
495 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000496 throw new Exception("Problem writing resource " + path, e);
497 }
498 }
499
Stuart McCulloch4482c702012-06-15 13:27:53 +0000500 void createDirectories(Set<String> directories, ZipOutputStream zip, String name) throws IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +0000501 int index = name.lastIndexOf('/');
502 if (index > 0) {
503 String path = name.substring(0, index);
504 if (directories.contains(path))
505 return;
506 createDirectories(directories, zip, path);
507 ZipEntry ze = new ZipEntry(path + '/');
508 zip.putNextEntry(ze);
509 zip.closeEntry();
510 directories.add(path);
511 }
512 }
513
514 public String getName() {
515 return name;
516 }
517
518 /**
519 * Add all the resources in the given jar that match the given filter.
520 *
521 * @param sub
522 * the jar
523 * @param filter
524 * a pattern that should match the resoures in sub to be added
525 */
526 public boolean addAll(Jar sub, Instruction filter) {
527 return addAll(sub, filter, "");
528 }
529
530 /**
531 * Add all the resources in the given jar that match the given filter.
532 *
533 * @param sub
534 * the jar
535 * @param filter
536 * a pattern that should match the resoures in sub to be added
537 */
538 public boolean addAll(Jar sub, Instruction filter, String destination) {
539 check();
540 boolean dupl = false;
541 for (String name : sub.getResources().keySet()) {
542 if ("META-INF/MANIFEST.MF".equals(name))
543 continue;
544
545 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000546 dupl |= putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochf3173222012-06-07 21:57:32 +0000547 }
548 return dupl;
549 }
550
551 public void close() {
552 this.closed = true;
553 if (zipFile != null)
554 try {
555 zipFile.close();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000556 }
557 catch (IOException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000558 // Ignore
559 }
560 resources.clear();
561 directories.clear();
562 manifest = null;
563 source = null;
564 }
565
566 public long lastModified() {
567 return lastModified;
568 }
569
570 public void updateModified(long time, String reason) {
571 if (time > lastModified) {
572 lastModified = time;
573 lastModifiedReason = reason;
574 }
575 }
576
577 public void setReporter(Reporter reporter) {
578 this.reporter = reporter;
579 }
580
581 public boolean hasDirectory(String path) {
582 check();
583 return directories.get(path) != null;
584 }
585
586 public List<String> getPackages() {
587 check();
588 List<String> list = new ArrayList<String>(directories.size());
589
Stuart McCulloch4482c702012-06-15 13:27:53 +0000590 for (Map.Entry<String,Map<String,Resource>> i : directories.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000591 if (i.getValue() != null) {
592 String path = i.getKey();
593 String pack = path.replace('/', '.');
594 list.add(pack);
595 }
596 }
597 return list;
598 }
599
600 public File getSource() {
601 check();
602 return source;
603 }
604
605 public boolean addAll(Jar src) {
606 check();
607 return addAll(src, null);
608 }
609
610 public boolean rename(String oldPath, String newPath) {
611 check();
612 Resource resource = remove(oldPath);
613 if (resource == null)
614 return false;
615
616 return putResource(newPath, resource);
617 }
618
619 public Resource remove(String path) {
620 check();
621 Resource resource = resources.remove(path);
622 String dir = getDirectory(path);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000623 Map<String,Resource> mdir = directories.get(dir);
Stuart McCullochf3173222012-06-07 21:57:32 +0000624 // must be != null
625 mdir.remove(path);
626 return resource;
627 }
628
629 /**
630 * Make sure nobody touches the manifest! If the bundle is signed, we do not
631 * want anybody to touch the manifest after the digests have been
632 * calculated.
633 */
634 public void setDoNotTouchManifest() {
635 doNotTouchManifest = true;
636 }
637
638 /**
639 * Calculate the checksums and set them in the manifest.
640 */
641
642 public void calcChecksums(String algorithms[]) throws Exception {
643 check();
644 if (algorithms == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000645 algorithms = new String[] {
646 "SHA", "MD5"
647 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000648
649 Manifest m = getManifest();
650 if (m == null) {
651 m = new Manifest();
652 setManifest(m);
653 }
654
655 MessageDigest digests[] = new MessageDigest[algorithms.length];
656 int n = 0;
657 for (String algorithm : algorithms)
658 digests[n++] = MessageDigest.getInstance(algorithm);
659
660 byte buffer[] = new byte[30000];
661
Stuart McCulloch4482c702012-06-15 13:27:53 +0000662 for (Map.Entry<String,Resource> entry : resources.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000663
664 // Skip the manifest
665 if (entry.getKey().equals("META-INF/MANIFEST.MF"))
666 continue;
667
668 Resource r = entry.getValue();
669 Attributes attributes = m.getAttributes(entry.getKey());
670 if (attributes == null) {
671 attributes = new Attributes();
672 getManifest().getEntries().put(entry.getKey(), attributes);
673 }
674 InputStream in = r.openInputStream();
675 try {
676 for (MessageDigest d : digests)
677 d.reset();
678 int size = in.read(buffer);
679 while (size > 0) {
680 for (MessageDigest d : digests)
681 d.update(buffer, 0, size);
682 size = in.read(buffer);
683 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000684 }
685 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000686 in.close();
687 }
688 for (MessageDigest d : digests)
689 attributes.putValue(d.getAlgorithm() + "-Digest", Base64.encodeBase64(d.digest()));
690 }
691 }
692
693 Pattern BSN = Pattern.compile("\\s*([-\\w\\d\\._]+)\\s*;?.*");
694
695 public String getBsn() throws Exception {
696 check();
697 Manifest m = getManifest();
698 if (m == null)
699 return null;
700
701 String s = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
702 if (s == null)
703 return null;
704
705 Matcher matcher = BSN.matcher(s);
706 if (matcher.matches()) {
707 return matcher.group(1);
708 }
709 return null;
710 }
711
712 public String getVersion() throws Exception {
713 check();
714 Manifest m = getManifest();
715 if (m == null)
716 return null;
717
718 String s = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
719 if (s == null)
720 return null;
721
722 return s.trim();
723 }
724
725 /**
726 * Expand the JAR file to a directory.
727 *
728 * @param dir
729 * the dst directory, is not required to exist
730 * @throws Exception
731 * if anything does not work as expected.
732 */
733 public void expand(File dir) throws Exception {
734 check();
735 dir = dir.getAbsoluteFile();
736 dir.mkdirs();
737 if (!dir.isDirectory()) {
738 throw new IllegalArgumentException("Not a dir: " + dir.getAbsolutePath());
739 }
740
Stuart McCulloch4482c702012-06-15 13:27:53 +0000741 for (Map.Entry<String,Resource> entry : getResources().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000742 File f = getFile(dir, entry.getKey());
743 f.getParentFile().mkdirs();
744 IO.copy(entry.getValue().openInputStream(), f);
745 }
746 }
747
748 /**
749 * Make sure we have a manifest
750 *
751 * @throws Exception
752 */
753 public void ensureManifest() throws Exception {
754 if (getManifest() != null)
755 return;
756 manifest = new Manifest();
757 }
758
759 /**
760 * Answer if the manifest was the first entry
761 */
762
763 public boolean isManifestFirst() {
764 return manifestFirst;
765 }
766
767 public void copy(Jar srce, String path, boolean overwrite) {
768 check();
769 addDirectory(srce.getDirectories().get(path), overwrite);
770 }
771
772 public void setCompression(Compression compression) {
773 this.compression = compression;
774 }
775
776 public Compression hasCompression() {
777 return this.compression;
778 }
779
780 void check() {
781 if (closed)
782 throw new RuntimeException("Already closed " + name);
783 }
784}