blob: dfed21f754c90d776bf40080fb35c096c6b79382 [file] [log] [blame]
Stuart McCullochf6a3e7b2009-07-13 10:06:47 +00001/* Copyright 2006 aQute SARL
2 * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
3package aQute.lib.osgi;
4
5import java.io.*;
6import java.util.*;
7import java.util.jar.*;
8import java.util.regex.*;
9import java.util.zip.*;
10
11import aQute.libg.reporter.*;
12
13public class Jar implements Closeable {
14 public static final Object[] EMPTY_ARRAY = new Jar[0];
15 Map<String, Resource> resources = new TreeMap<String, Resource>();
16 Map<String, Map<String, Resource>> directories = new TreeMap<String, Map<String, Resource>>();
17 Manifest manifest;
18 boolean manifestFirst;
19 String name;
20 File source;
21 ZipFile zipFile;
22 long lastModified;
23 String lastModifiedReason;
24 Reporter reporter;
25 boolean doNotTouchManifest;
26 boolean nomanifest;
27
28 public Jar(String name) {
29 this.name = name;
30 }
31
32 public Jar(String name, File dirOrFile) throws ZipException, IOException {
33 this(name);
34 source = dirOrFile;
35 if (dirOrFile.isDirectory())
36 FileResource.build(this, dirOrFile, Analyzer.doNotCopy);
37 else {
38 zipFile = ZipResource.build(this, dirOrFile);
39 }
40 }
41
42 public Jar(String name, InputStream in, long lastModified)
43 throws IOException {
44 this(name);
45 EmbeddedResource.build(this, in, lastModified);
46 }
47
48 public Jar(String name, String path) throws IOException {
49 this(name);
50 File f = new File(path);
51 InputStream in = new FileInputStream(f);
52 EmbeddedResource.build(this, in, f.lastModified());
53 in.close();
54 }
55
56 public Jar(File jar) throws IOException {
57 this(getName(jar), jar);
58 }
59
60 /**
61 * Make the JAR file name the project name if we get a src or bin directory.
62 *
63 * @param f
64 * @return
65 */
66 private static String getName(File f) {
67 f = f.getAbsoluteFile();
68 String name = f.getName();
69 if (name.equals("bin") || name.equals("src"))
70 return f.getParentFile().getName();
71 else {
72 if (name.endsWith(".jar"))
73 name = name.substring(0, name.length() - 4);
74 return name;
75 }
76 }
77
78 public Jar(String string, InputStream resourceAsStream) throws IOException {
79 this(string, resourceAsStream, 0);
80 }
81
82 public void setName(String name) {
83 this.name = name;
84 }
85
86 public String toString() {
87 return "Jar:" + name;
88 }
89
90 public boolean putResource(String path, Resource resource) {
91 return putResource(path, resource, true);
92 }
93
94 public boolean putResource(String path, Resource resource, boolean overwrite) {
95 updateModified(resource.lastModified(), path);
96
97 if (path.equals("META-INF/MANIFEST.MF")) {
98 manifest = null;
99 if (resources.isEmpty())
100 manifestFirst = true;
101 }
102 String dir = getDirectory(path);
103 Map<String, Resource> s = directories.get(dir);
104 if (s == null) {
105 s = new TreeMap<String, Resource>();
106 directories.put(dir, s);
107 int n = dir.lastIndexOf('/');
108 while (n > 0) {
109 String dd = dir.substring(0, n);
110 if (directories.containsKey(dd))
111 break;
112 directories.put(dd, null);
113 n = dd.lastIndexOf('/');
114 }
115 }
116 boolean duplicate = s.containsKey(path);
117 if (!duplicate || overwrite) {
118 resources.put(path, resource);
119 s.put(path, resource);
120 }
121 return duplicate;
122 }
123
124 public Resource getResource(String path) {
125 return resources.get(path);
126 }
127
128 private String getDirectory(String path) {
129 int n = path.lastIndexOf('/');
130 if (n < 0)
131 return "";
132
133 return path.substring(0, n);
134 }
135
136 public Map<String, Map<String, Resource>> getDirectories() {
137 return directories;
138 }
139
140 public Map<String, Resource> getResources() {
141 return resources;
142 }
143
144 public boolean addDirectory(Map<String, Resource> directory,
145 boolean overwrite) {
146 boolean duplicates = false;
147 if (directory == null)
148 return false;
149
150 for (Map.Entry<String, Resource> entry : directory.entrySet()) {
151 String key = entry.getKey();
152 if (!key.endsWith(".java")) {
153 duplicates |= putResource(key, (Resource) entry.getValue(),
154 overwrite);
155 }
156 }
157 return duplicates;
158 }
159
160 public Manifest getManifest() throws IOException {
161 if (manifest == null) {
162 Resource manifestResource = getResource("META-INF/MANIFEST.MF");
163 if (manifestResource != null) {
164 InputStream in = manifestResource.openInputStream();
165 manifest = new Manifest(in);
166 in.close();
167 }
168 }
169 return manifest;
170 }
171
172 public boolean exists(String path) {
173 return resources.containsKey(path);
174 }
175
176 public void setManifest(Manifest manifest) {
177 manifestFirst = true;
178 this.manifest = manifest;
179 }
180
181 public void write(File file) throws Exception {
182 try {
183 OutputStream out = new FileOutputStream(file);
184 write(out);
185 out.close();
186 return;
187
188 } catch (Exception t) {
189 file.delete();
190 throw t;
191 }
192 }
193
194 public void write(String file) throws Exception {
195 write(new File(file));
196 }
197
198 public void write(OutputStream out) throws IOException {
199 ZipOutputStream jout = nomanifest ? new ZipOutputStream(out) : new JarOutputStream(out);
200 Set<String> done = new HashSet<String>();
201
202 Set<String> directories = new HashSet<String>();
203 if (doNotTouchManifest) {
204 writeResource(jout, directories, "META-INF/MANIFEST.MF",
205 getResource("META-INF/MANIFEST.MF"));
206 done.add("META-INF/MANIFEST.MF");
207 } else if (!nomanifest)
208 doManifest(done, jout);
209
210 for (Map.Entry<String, Resource> entry : getResources().entrySet()) {
211 // Skip metainf contents
212 if (!done.contains(entry.getKey()))
213 writeResource(jout, directories, (String) entry.getKey(),
214 (Resource) entry.getValue());
215 }
216 jout.finish();
217 }
218
219 private void doManifest(Set<String> done, ZipOutputStream jout)
220 throws IOException {
221 if ( nomanifest )
222 return;
223
224 JarEntry ze = new JarEntry("META-INF/MANIFEST.MF");
225 jout.putNextEntry(ze);
226 writeManifest(jout);
227 jout.closeEntry();
228 done.add(ze.getName());
229 }
230
231 /**
232 * Cleanup the manifest for writing. Cleaning up consists of adding a space
233 * after any \n to prevent the manifest to see this newline as a delimiter.
234 *
235 * @param out
236 * Output
237 * @throws IOException
238 */
239
240 public void writeManifest(OutputStream out) throws IOException {
241 writeManifest(getManifest(), out);
242 }
243
244 public static void writeManifest(Manifest manifest, OutputStream out)
245 throws IOException {
246
247 manifest = clean(manifest);
248 manifest.write(out);
249 }
250
251 private static Manifest clean(Manifest org) {
252
253 Manifest result = new Manifest();
254 for (Map.Entry<?, ?> entry : org.getMainAttributes().entrySet()) {
255 String nice = clean((String) entry.getValue());
256 result.getMainAttributes().put(entry.getKey(), nice);
257 }
258 for (String name : org.getEntries().keySet()) {
259 Attributes attrs = result.getAttributes(name);
260 if (attrs == null) {
261 attrs = new Attributes();
262 result.getEntries().put(name, attrs);
263 }
264
265 for (Map.Entry<?, ?> entry : org.getAttributes(name).entrySet()) {
266 String nice = clean((String) entry.getValue());
267 attrs.put((Attributes.Name) entry.getKey(), nice);
268 }
269 }
270 return result;
271 }
272
273 private static String clean(String s) {
274 if (s.indexOf('\n') < 0)
275 return s;
276
277 StringBuffer sb = new StringBuffer(s);
278 for (int i = 0; i < sb.length(); i++) {
279 if (sb.charAt(i) == '\n')
280 sb.insert(++i, ' ');
281 }
282 return sb.toString();
283 }
284
285 private void writeResource(ZipOutputStream jout, Set<String> directories,
286 String path, Resource resource) throws IOException {
287 if (resource == null)
288 return;
289
290 createDirectories(directories, jout, path);
291 ZipEntry ze = new ZipEntry(path);
292 ze.setMethod(ZipEntry.DEFLATED);
293 long lastModified = resource.lastModified();
294 if (lastModified == 0L) {
295 lastModified = System.currentTimeMillis();
296 }
297 ze.setTime(lastModified);
298 if (resource.getExtra() != null)
299 ze.setExtra(resource.getExtra().getBytes());
300 jout.putNextEntry(ze);
301 try {
302 resource.write(jout);
303 } catch (Exception e) {
304 throw new IllegalArgumentException("Cannot write resource: " + path
305 + " " + e);
306 }
307 jout.closeEntry();
308 }
309
310 void createDirectories(Set<String> directories, ZipOutputStream zip,
311 String name) throws IOException {
312 int index = name.lastIndexOf('/');
313 if (index > 0) {
314 String path = name.substring(0, index);
315 if (directories.contains(path))
316 return;
317 createDirectories(directories, zip, path);
318 ZipEntry ze = new ZipEntry(path + '/');
319 zip.putNextEntry(ze);
320 zip.closeEntry();
321 directories.add(path);
322 }
323 }
324
325 public String getName() {
326 return name;
327 }
328
329 /**
330 * Add all the resources in the given jar that match the given filter.
331 *
332 * @param sub
333 * the jar
334 * @param filter
335 * a pattern that should match the resoures in sub to be added
336 */
337 public boolean addAll(Jar sub, Pattern filter) {
338 boolean dupl = false;
339 for (String name : sub.getResources().keySet()) {
340 if ("META-INF/MANIFEST.MF".equals(name))
341 continue;
342
343 if (filter == null || filter.matcher(name).matches())
344 dupl |= putResource(name, sub.getResource(name), true);
345 }
346 return dupl;
347 }
348
349 public void close() {
350 if (zipFile != null)
351 try {
352 zipFile.close();
353 } catch (IOException e) {
354 // Ignore
355 }
356 resources = null;
357 directories = null;
358 manifest = null;
359 source = null;
360 }
361
362 public long lastModified() {
363 return lastModified;
364 }
365
366 public void updateModified(long time, String reason) {
367 if (time > lastModified) {
368 lastModified = time;
369 lastModifiedReason = reason;
370 }
371 }
372
373 public void setReporter(Reporter reporter) {
374 this.reporter = reporter;
375 }
376
377 public boolean hasDirectory(String path) {
378 return directories.get(path) != null;
379 }
380
381 public List<String> getPackages() {
382 List<String> list = new ArrayList<String>(directories.size());
383
384 for (Iterator<String> i = directories.keySet().iterator(); i.hasNext();) {
385 String path = i.next();
386 String pack = path.replace('/', '.');
387 list.add(pack);
388 }
389 return list;
390 }
391
392 public File getSource() {
393 return source;
394 }
395
396 public boolean addAll(Jar src) {
397 return addAll(src, null);
398 }
399
400 public boolean rename(String oldPath, String newPath) {
401 Resource resource = remove(oldPath);
402 if (resource == null)
403 return false;
404
405 return putResource(newPath, resource);
406 }
407
408 public Resource remove(String path) {
409 Resource resource = resources.remove(path);
410 String dir = getDirectory(path);
411 Map<String, Resource> mdir = directories.get(dir);
412 // must be != null
413 mdir.remove(path);
414 return resource;
415 }
416
417 /**
418 * Make sure nobody touches the manifest! If the bundle is signed, we do not
419 * want anybody to touch the manifest after the digests have been
420 * calculated.
421 */
422 public void setDoNotTouchManifest() {
423 doNotTouchManifest = true;
424 }
425
426 public void setNoManifest(boolean b) {
427 nomanifest = b;
428 }
429}