1 | /*
|
---|
2 | * Copyright 2000,2002-2004 The Apache Software Foundation
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
5 | * you may not use this file except in compliance with the License.
|
---|
6 | * You may obtain a copy of the License at
|
---|
7 | *
|
---|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
9 | *
|
---|
10 | * Unless required by applicable law or agreed to in writing, software
|
---|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
13 | * See the License for the specific language governing permissions and
|
---|
14 | * limitations under the License.
|
---|
15 | *
|
---|
16 | */
|
---|
17 | /**
|
---|
18 | * jlink.java links together multiple .jar files Original code by Patrick
|
---|
19 | * Beard. Modifications to work with ANT by Matthew Kuperus Heun.
|
---|
20 | *
|
---|
21 | */
|
---|
22 | package org.apache.tools.ant.taskdefs.optional.jlink;
|
---|
23 |
|
---|
24 | import java.io.BufferedInputStream;
|
---|
25 | import java.io.File;
|
---|
26 | import java.io.FileInputStream;
|
---|
27 | import java.io.FileOutputStream;
|
---|
28 | import java.io.IOException;
|
---|
29 | import java.io.InputStream;
|
---|
30 | import java.util.Enumeration;
|
---|
31 | import java.util.Vector;
|
---|
32 | import java.util.zip.CRC32;
|
---|
33 | import java.util.zip.Deflater;
|
---|
34 | import java.util.zip.ZipEntry;
|
---|
35 | import java.util.zip.ZipException;
|
---|
36 | import java.util.zip.ZipFile;
|
---|
37 | import java.util.zip.ZipOutputStream;
|
---|
38 |
|
---|
39 | public class jlink extends Object {
|
---|
40 |
|
---|
41 | /** The file that will be created by this instance of jlink. */
|
---|
42 | public void setOutfile(String outfile) {
|
---|
43 | if (outfile == null) {
|
---|
44 | return;
|
---|
45 | }
|
---|
46 | this.outfile = outfile;
|
---|
47 | }
|
---|
48 |
|
---|
49 |
|
---|
50 | /** Adds a file to be merged into the output. */
|
---|
51 | public void addMergeFile(String mergefile) {
|
---|
52 | if (mergefile == null) {
|
---|
53 | return;
|
---|
54 | }
|
---|
55 | mergefiles.addElement(mergefile);
|
---|
56 | }
|
---|
57 |
|
---|
58 |
|
---|
59 | /** Adds a file to be added into the output. */
|
---|
60 | public void addAddFile(String addfile) {
|
---|
61 | if (addfile == null) {
|
---|
62 | return;
|
---|
63 | }
|
---|
64 | addfiles.addElement(addfile);
|
---|
65 | }
|
---|
66 |
|
---|
67 |
|
---|
68 | /** Adds several files to be merged into the output. */
|
---|
69 | public void addMergeFiles(String[] mergefiles) {
|
---|
70 | if (mergefiles == null) {
|
---|
71 | return;
|
---|
72 | }
|
---|
73 | for (int i = 0; i < mergefiles.length; i++) {
|
---|
74 | addMergeFile(mergefiles[i]);
|
---|
75 | }
|
---|
76 | }
|
---|
77 |
|
---|
78 |
|
---|
79 | /** Adds several file to be added into the output. */
|
---|
80 | public void addAddFiles(String[] addfiles) {
|
---|
81 | if (addfiles == null) {
|
---|
82 | return;
|
---|
83 | }
|
---|
84 | for (int i = 0; i < addfiles.length; i++) {
|
---|
85 | addAddFile(addfiles[i]);
|
---|
86 | }
|
---|
87 | }
|
---|
88 |
|
---|
89 |
|
---|
90 | /** Determines whether output will be compressed. */
|
---|
91 | public void setCompression(boolean compress) {
|
---|
92 | this.compression = compress;
|
---|
93 | }
|
---|
94 |
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * Performs the linking of files. Addfiles are added to the output as-is.
|
---|
98 | * For example, a jar file is added to the output as a jar file. However,
|
---|
99 | * mergefiles are first examined for their type. If it is a jar or zip
|
---|
100 | * file, the contents will be extracted from the mergefile and entered
|
---|
101 | * into the output. If a zip or jar file is encountered in a subdirectory
|
---|
102 | * it will be added, not merged. If a directory is encountered, it becomes
|
---|
103 | * the root entry of all the files below it. Thus, you can provide
|
---|
104 | * multiple, disjoint directories, as addfiles: they will all be added in
|
---|
105 | * a rational manner to outfile.
|
---|
106 | */
|
---|
107 | public void link() throws Exception {
|
---|
108 | ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outfile));
|
---|
109 |
|
---|
110 | if (compression) {
|
---|
111 | output.setMethod(ZipOutputStream.DEFLATED);
|
---|
112 | output.setLevel(Deflater.DEFAULT_COMPRESSION);
|
---|
113 | } else {
|
---|
114 | output.setMethod(ZipOutputStream.STORED);
|
---|
115 | }
|
---|
116 |
|
---|
117 | Enumeration merges = mergefiles.elements();
|
---|
118 |
|
---|
119 | while (merges.hasMoreElements()) {
|
---|
120 | String path = (String) merges.nextElement();
|
---|
121 | File f = new File(path);
|
---|
122 |
|
---|
123 | if (f.getName().endsWith(".jar") || f.getName().endsWith(".zip")) {
|
---|
124 | //Do the merge
|
---|
125 | mergeZipJarContents(output, f);
|
---|
126 | } else {
|
---|
127 | //Add this file to the addfiles Vector and add it
|
---|
128 | //later at the top level of the output file.
|
---|
129 | addAddFile(path);
|
---|
130 | }
|
---|
131 | }
|
---|
132 |
|
---|
133 | Enumeration adds = addfiles.elements();
|
---|
134 |
|
---|
135 | while (adds.hasMoreElements()) {
|
---|
136 | String name = (String) adds.nextElement();
|
---|
137 | File f = new File(name);
|
---|
138 |
|
---|
139 | if (f.isDirectory()) {
|
---|
140 | //System.out.println("in jlink: adding directory contents of " + f.getPath());
|
---|
141 | addDirContents(output, f, f.getName() + '/', compression);
|
---|
142 | } else {
|
---|
143 | addFile(output, f, "", compression);
|
---|
144 | }
|
---|
145 | }
|
---|
146 | if (output != null) {
|
---|
147 | try {
|
---|
148 | output.close();
|
---|
149 | } catch (IOException ioe) {
|
---|
150 | }
|
---|
151 | }
|
---|
152 | }
|
---|
153 |
|
---|
154 |
|
---|
155 | public static void main(String[] args) {
|
---|
156 | // jlink output input1 ... inputN
|
---|
157 | if (args.length < 2) {
|
---|
158 | System.out.println("usage: jlink output input1 ... inputN");
|
---|
159 | System.exit(1);
|
---|
160 | }
|
---|
161 | jlink linker = new jlink();
|
---|
162 |
|
---|
163 | linker.setOutfile(args[0]);
|
---|
164 | // To maintain compatibility with the command-line version,
|
---|
165 | // we will only add files to be merged.
|
---|
166 | for (int i = 1; i < args.length; i++) {
|
---|
167 | linker.addMergeFile(args[i]);
|
---|
168 | }
|
---|
169 | try {
|
---|
170 | linker.link();
|
---|
171 | } catch (Exception ex) {
|
---|
172 | System.err.print(ex.getMessage());
|
---|
173 | }
|
---|
174 | }
|
---|
175 |
|
---|
176 |
|
---|
177 | /*
|
---|
178 | * Actually performs the merging of f into the output.
|
---|
179 | * f should be a zip or jar file.
|
---|
180 | */
|
---|
181 | private void mergeZipJarContents(ZipOutputStream output, File f) throws IOException {
|
---|
182 | //Check to see that the file with name "name" exists.
|
---|
183 | if (!f.exists()) {
|
---|
184 | return;
|
---|
185 | }
|
---|
186 | ZipFile zipf = new ZipFile(f);
|
---|
187 | Enumeration entries = zipf.entries();
|
---|
188 |
|
---|
189 | while (entries.hasMoreElements()) {
|
---|
190 | ZipEntry inputEntry = (ZipEntry) entries.nextElement();
|
---|
191 | //Ignore manifest entries. They're bound to cause conflicts between
|
---|
192 | //files that are being merged. User should supply their own
|
---|
193 | //manifest file when doing the merge.
|
---|
194 | String inputEntryName = inputEntry.getName();
|
---|
195 | int index = inputEntryName.indexOf("META-INF");
|
---|
196 |
|
---|
197 | if (index < 0) {
|
---|
198 | //META-INF not found in the name of the entry. Go ahead and process it.
|
---|
199 | try {
|
---|
200 | output.putNextEntry(processEntry(zipf, inputEntry));
|
---|
201 | } catch (ZipException ex) {
|
---|
202 | //If we get here, it could be because we are trying to put a
|
---|
203 | //directory entry that already exists.
|
---|
204 | //For example, we're trying to write "com", but a previous
|
---|
205 | //entry from another mergefile was called "com".
|
---|
206 | //In that case, just ignore the error and go on to the
|
---|
207 | //next entry.
|
---|
208 | String mess = ex.getMessage();
|
---|
209 |
|
---|
210 | if (mess.indexOf("duplicate") >= 0) {
|
---|
211 | //It was the duplicate entry.
|
---|
212 | continue;
|
---|
213 | } else {
|
---|
214 | // I hate to admit it, but we don't know what happened
|
---|
215 | // here. Throw the Exception.
|
---|
216 | throw ex;
|
---|
217 | }
|
---|
218 | }
|
---|
219 |
|
---|
220 | InputStream in = zipf.getInputStream(inputEntry);
|
---|
221 | int len = buffer.length;
|
---|
222 | int count = -1;
|
---|
223 |
|
---|
224 | while ((count = in.read(buffer, 0, len)) > 0) {
|
---|
225 | output.write(buffer, 0, count);
|
---|
226 | }
|
---|
227 | in.close();
|
---|
228 | output.closeEntry();
|
---|
229 | }
|
---|
230 | }
|
---|
231 | zipf.close();
|
---|
232 | }
|
---|
233 |
|
---|
234 |
|
---|
235 | /*
|
---|
236 | * Adds contents of a directory to the output.
|
---|
237 | */
|
---|
238 | private void addDirContents(ZipOutputStream output, File dir, String prefix,
|
---|
239 | boolean compress) throws IOException {
|
---|
240 | String[] contents = dir.list();
|
---|
241 |
|
---|
242 | for (int i = 0; i < contents.length; ++i) {
|
---|
243 | String name = contents[i];
|
---|
244 | File file = new File(dir, name);
|
---|
245 |
|
---|
246 | if (file.isDirectory()) {
|
---|
247 | addDirContents(output, file, prefix + name + '/', compress);
|
---|
248 | } else {
|
---|
249 | addFile(output, file, prefix, compress);
|
---|
250 | }
|
---|
251 | }
|
---|
252 | }
|
---|
253 |
|
---|
254 |
|
---|
255 | /*
|
---|
256 | * Gets the name of an entry in the file. This is the real name
|
---|
257 | * which for a class is the name of the package with the class
|
---|
258 | * name appended.
|
---|
259 | */
|
---|
260 | private String getEntryName(File file, String prefix) {
|
---|
261 | String name = file.getName();
|
---|
262 |
|
---|
263 | if (!name.endsWith(".class")) {
|
---|
264 | // see if the file is in fact a .class file, and determine its actual name.
|
---|
265 | InputStream input = null;
|
---|
266 | try {
|
---|
267 | input = new FileInputStream(file);
|
---|
268 | String className = ClassNameReader.getClassName(input);
|
---|
269 |
|
---|
270 | if (className != null) {
|
---|
271 | return className.replace('.', '/') + ".class";
|
---|
272 | }
|
---|
273 | } catch (IOException ioe) {
|
---|
274 | } finally {
|
---|
275 | if (input != null) {
|
---|
276 | try {
|
---|
277 | input.close();
|
---|
278 | } catch (IOException e) {
|
---|
279 | }
|
---|
280 | }
|
---|
281 | }
|
---|
282 | }
|
---|
283 | System.out.println("From " + file.getPath() + " and prefix " + prefix
|
---|
284 | + ", creating entry " + prefix + name);
|
---|
285 | return (prefix + name);
|
---|
286 | }
|
---|
287 |
|
---|
288 |
|
---|
289 | /*
|
---|
290 | * Adds a file to the output stream.
|
---|
291 | */
|
---|
292 | private void addFile(ZipOutputStream output, File file, String prefix,
|
---|
293 | boolean compress) throws IOException {
|
---|
294 | //Make sure file exists
|
---|
295 | if (!file.exists()) {
|
---|
296 | return;
|
---|
297 | }
|
---|
298 | ZipEntry entry = new ZipEntry(getEntryName(file, prefix));
|
---|
299 |
|
---|
300 | entry.setTime(file.lastModified());
|
---|
301 | entry.setSize(file.length());
|
---|
302 | if (!compress) {
|
---|
303 | entry.setCrc(calcChecksum(file));
|
---|
304 | }
|
---|
305 | FileInputStream input = new FileInputStream(file);
|
---|
306 |
|
---|
307 | addToOutputStream(output, input, entry);
|
---|
308 | }
|
---|
309 |
|
---|
310 |
|
---|
311 | /*
|
---|
312 | * A convenience method that several other methods might call.
|
---|
313 | */
|
---|
314 | private void addToOutputStream(ZipOutputStream output, InputStream input,
|
---|
315 | ZipEntry ze) throws IOException {
|
---|
316 | try {
|
---|
317 | output.putNextEntry(ze);
|
---|
318 | } catch (ZipException zipEx) {
|
---|
319 | //This entry already exists. So, go with the first one.
|
---|
320 | input.close();
|
---|
321 | return;
|
---|
322 | }
|
---|
323 |
|
---|
324 | int numBytes = -1;
|
---|
325 |
|
---|
326 | while ((numBytes = input.read(buffer)) > 0) {
|
---|
327 | output.write(buffer, 0, numBytes);
|
---|
328 | }
|
---|
329 | output.closeEntry();
|
---|
330 | input.close();
|
---|
331 | }
|
---|
332 |
|
---|
333 |
|
---|
334 | /*
|
---|
335 | * A method that does the work on a given entry in a mergefile.
|
---|
336 | * The big deal is to set the right parameters in the ZipEntry
|
---|
337 | * on the output stream.
|
---|
338 | */
|
---|
339 | private ZipEntry processEntry(ZipFile zip, ZipEntry inputEntry) throws IOException {
|
---|
340 | /*
|
---|
341 | First, some notes.
|
---|
342 | On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the
|
---|
343 | ZipInputStream does not work for compressed (deflated) files. Those calls return -1.
|
---|
344 | For uncompressed (stored) files, those calls do work.
|
---|
345 | However, using ZipFile.getEntries() works for both compressed and
|
---|
346 | uncompressed files.
|
---|
347 |
|
---|
348 | Now, from some simple testing I did, it seems that the value of CRC-32 is
|
---|
349 | independent of the compression setting. So, it should be easy to pass this
|
---|
350 | information on to the output entry.
|
---|
351 | */
|
---|
352 | String name = inputEntry.getName();
|
---|
353 |
|
---|
354 | if (!(inputEntry.isDirectory() || name.endsWith(".class"))) {
|
---|
355 | try {
|
---|
356 | InputStream input = zip.getInputStream(zip.getEntry(name));
|
---|
357 | String className = ClassNameReader.getClassName(input);
|
---|
358 |
|
---|
359 | input.close();
|
---|
360 | if (className != null) {
|
---|
361 | name = className.replace('.', '/') + ".class";
|
---|
362 | }
|
---|
363 | } catch (IOException ioe) {
|
---|
364 | }
|
---|
365 | }
|
---|
366 | ZipEntry outputEntry = new ZipEntry(name);
|
---|
367 |
|
---|
368 | outputEntry.setTime(inputEntry.getTime());
|
---|
369 | outputEntry.setExtra(inputEntry.getExtra());
|
---|
370 | outputEntry.setComment(inputEntry.getComment());
|
---|
371 | outputEntry.setTime(inputEntry.getTime());
|
---|
372 | if (compression) {
|
---|
373 | outputEntry.setMethod(ZipEntry.DEFLATED);
|
---|
374 | //Note, don't need to specify size or crc for compressed files.
|
---|
375 | } else {
|
---|
376 | outputEntry.setMethod(ZipEntry.STORED);
|
---|
377 | outputEntry.setCrc(inputEntry.getCrc());
|
---|
378 | outputEntry.setSize(inputEntry.getSize());
|
---|
379 | }
|
---|
380 | return outputEntry;
|
---|
381 | }
|
---|
382 |
|
---|
383 |
|
---|
384 | /*
|
---|
385 | * Necessary in the case where you add a entry that
|
---|
386 | * is not compressed.
|
---|
387 | */
|
---|
388 | private long calcChecksum(File f) throws IOException {
|
---|
389 | BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
|
---|
390 |
|
---|
391 | return calcChecksum(in);
|
---|
392 | }
|
---|
393 |
|
---|
394 |
|
---|
395 | /*
|
---|
396 | * Necessary in the case where you add a entry that
|
---|
397 | * is not compressed.
|
---|
398 | */
|
---|
399 | private long calcChecksum(InputStream in) throws IOException {
|
---|
400 | CRC32 crc = new CRC32();
|
---|
401 | int len = buffer.length;
|
---|
402 | int count = -1;
|
---|
403 | int haveRead = 0;
|
---|
404 |
|
---|
405 | while ((count = in.read(buffer, 0, len)) > 0) {
|
---|
406 | haveRead += count;
|
---|
407 | crc.update(buffer, 0, count);
|
---|
408 | }
|
---|
409 | in.close();
|
---|
410 | return crc.getValue();
|
---|
411 | }
|
---|
412 |
|
---|
413 |
|
---|
414 | private String outfile = null;
|
---|
415 |
|
---|
416 | private Vector mergefiles = new Vector(10);
|
---|
417 |
|
---|
418 | private Vector addfiles = new Vector(10);
|
---|
419 |
|
---|
420 | private boolean compression = false;
|
---|
421 |
|
---|
422 | byte[] buffer = new byte[8192];
|
---|
423 |
|
---|
424 | }
|
---|
425 |
|
---|
426 |
|
---|