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