001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: /**
019: * jlink.java links together multiple .jar files Original code by Patrick
020: * Beard. Modifications to work with ANT by Matthew Kuperus Heun.
021: *
022: */package org.apache.tools.ant.taskdefs.optional.jlink;
023:
024: import java.io.BufferedInputStream;
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.util.Enumeration;
031: import java.util.Vector;
032: import java.util.zip.CRC32;
033: import java.util.zip.Deflater;
034: import java.util.zip.ZipEntry;
035: import java.util.zip.ZipException;
036: import java.util.zip.ZipFile;
037: import java.util.zip.ZipOutputStream;
038:
039: // CheckStyle:TypeNameCheck OFF - bc
040: /**
041: * jlink links together multiple .jar files.
042: */
043: public class jlink {
044:
045: private String outfile = null;
046:
047: private Vector mergefiles = new Vector(10);
048:
049: private Vector addfiles = new Vector(10);
050:
051: private boolean compression = false;
052:
053: // CheckStyle:VisibilityModifier OFF - bc
054:
055: byte[] buffer = new byte[8192];
056:
057: // CheckStyle:VisibilityModifier OFF - bc
058:
059: /** The file that will be created by this instance of jlink.
060: * @param outfile the file to create.
061: */
062: public void setOutfile(String outfile) {
063: if (outfile == null) {
064: return;
065: }
066: this .outfile = outfile;
067: }
068:
069: /**
070: * Adds a file to be merged into the output.
071: * @param fileToMerge the file to merge into the output.
072: */
073: public void addMergeFile(String fileToMerge) {
074: if (fileToMerge == null) {
075: return;
076: }
077: mergefiles.addElement(fileToMerge);
078: }
079:
080: /** Adds a file to be added into the output.
081: * @param fileToAdd the file to add to the output.
082: */
083: public void addAddFile(String fileToAdd) {
084: if (fileToAdd == null) {
085: return;
086: }
087: addfiles.addElement(fileToAdd);
088: }
089:
090: /**
091: * Adds several files to be merged into the output.
092: * @param filesToMerge an array of files to merge into the output.
093: */
094: public void addMergeFiles(String[] filesToMerge) {
095: if (filesToMerge == null) {
096: return;
097: }
098: for (int i = 0; i < filesToMerge.length; i++) {
099: addMergeFile(filesToMerge[i]);
100: }
101: }
102:
103: /**
104: * Adds several file to be added into the output.
105: * @param filesToAdd an array of files to add to the output.
106: */
107: public void addAddFiles(String[] filesToAdd) {
108: if (filesToAdd == null) {
109: return;
110: }
111: for (int i = 0; i < filesToAdd.length; i++) {
112: addAddFile(filesToAdd[i]);
113: }
114: }
115:
116: /**
117: * Determines whether output will be compressed.
118: * @param compress if true use compression.
119: */
120: public void setCompression(boolean compress) {
121: this .compression = compress;
122: }
123:
124: /**
125: * Performs the linking of files. Addfiles are added to the output as-is.
126: * For example, a jar file is added to the output as a jar file. However,
127: * mergefiles are first examined for their type. If it is a jar or zip
128: * file, the contents will be extracted from the mergefile and entered
129: * into the output. If a zip or jar file is encountered in a subdirectory
130: * it will be added, not merged. If a directory is encountered, it becomes
131: * the root entry of all the files below it. Thus, you can provide
132: * multiple, disjoint directories, as addfiles: they will all be added in
133: * a rational manner to outfile.
134: * @throws Exception on error.
135: */
136: public void link() throws Exception {
137: ZipOutputStream output = new ZipOutputStream(
138: new FileOutputStream(outfile));
139:
140: if (compression) {
141: output.setMethod(ZipOutputStream.DEFLATED);
142: output.setLevel(Deflater.DEFAULT_COMPRESSION);
143: } else {
144: output.setMethod(ZipOutputStream.STORED);
145: }
146:
147: Enumeration merges = mergefiles.elements();
148:
149: while (merges.hasMoreElements()) {
150: String path = (String) merges.nextElement();
151: File f = new File(path);
152:
153: if (f.getName().endsWith(".jar")
154: || f.getName().endsWith(".zip")) {
155: //Do the merge
156: mergeZipJarContents(output, f);
157: } else {
158: //Add this file to the addfiles Vector and add it
159: //later at the top level of the output file.
160: addAddFile(path);
161: }
162: }
163:
164: Enumeration adds = addfiles.elements();
165:
166: while (adds.hasMoreElements()) {
167: String name = (String) adds.nextElement();
168: File f = new File(name);
169:
170: if (f.isDirectory()) {
171: //System.out.println("in jlink: adding directory contents of " + f.getPath());
172: addDirContents(output, f, f.getName() + '/',
173: compression);
174: } else {
175: addFile(output, f, "", compression);
176: }
177: }
178: if (output != null) {
179: try {
180: output.close();
181: } catch (IOException ioe) {
182: //do nothing
183: }
184: }
185: }
186:
187: /**
188: * The command line entry point for jlink.
189: * @param args an array of arguments
190: */
191: public static void main(String[] args) {
192: // jlink output input1 ... inputN
193: if (args.length < 2) {
194: System.out.println("usage: jlink output input1 ... inputN");
195: System.exit(1);
196: }
197: jlink linker = new jlink();
198:
199: linker.setOutfile(args[0]);
200: // To maintain compatibility with the command-line version,
201: // we will only add files to be merged.
202: for (int i = 1; i < args.length; i++) {
203: linker.addMergeFile(args[i]);
204: }
205: try {
206: linker.link();
207: } catch (Exception ex) {
208: System.err.print(ex.getMessage());
209: }
210: }
211:
212: /*
213: * Actually performs the merging of f into the output.
214: * f should be a zip or jar file.
215: */
216: private void mergeZipJarContents(ZipOutputStream output, File f)
217: throws IOException {
218: //Check to see that the file with name "name" exists.
219: if (!f.exists()) {
220: return;
221: }
222: ZipFile zipf = new ZipFile(f);
223: Enumeration entries = zipf.entries();
224:
225: while (entries.hasMoreElements()) {
226: ZipEntry inputEntry = (ZipEntry) entries.nextElement();
227: //Ignore manifest entries. They're bound to cause conflicts between
228: //files that are being merged. User should supply their own
229: //manifest file when doing the merge.
230: String inputEntryName = inputEntry.getName();
231: int index = inputEntryName.indexOf("META-INF");
232:
233: if (index < 0) {
234: //META-INF not found in the name of the entry. Go ahead and process it.
235: try {
236: output.putNextEntry(processEntry(zipf, inputEntry));
237: } catch (ZipException ex) {
238: //If we get here, it could be because we are trying to put a
239: //directory entry that already exists.
240: //For example, we're trying to write "com", but a previous
241: //entry from another mergefile was called "com".
242: //In that case, just ignore the error and go on to the
243: //next entry.
244: String mess = ex.getMessage();
245:
246: if (mess.indexOf("duplicate") >= 0) {
247: //It was the duplicate entry.
248: continue;
249: } else {
250: // I hate to admit it, but we don't know what happened
251: // here. Throw the Exception.
252: throw ex;
253: }
254: }
255:
256: InputStream in = zipf.getInputStream(inputEntry);
257: int len = buffer.length;
258: int count = -1;
259:
260: while ((count = in.read(buffer, 0, len)) > 0) {
261: output.write(buffer, 0, count);
262: }
263: in.close();
264: output.closeEntry();
265: }
266: }
267: zipf.close();
268: }
269:
270: /*
271: * Adds contents of a directory to the output.
272: */
273: private void addDirContents(ZipOutputStream output, File dir,
274: String prefix, boolean compress) throws IOException {
275: String[] contents = dir.list();
276:
277: for (int i = 0; i < contents.length; ++i) {
278: String name = contents[i];
279: File file = new File(dir, name);
280:
281: if (file.isDirectory()) {
282: addDirContents(output, file, prefix + name + '/',
283: compress);
284: } else {
285: addFile(output, file, prefix, compress);
286: }
287: }
288: }
289:
290: /*
291: * Gets the name of an entry in the file. This is the real name
292: * which for a class is the name of the package with the class
293: * name appended.
294: */
295: private String getEntryName(File file, String prefix) {
296: String name = file.getName();
297:
298: if (!name.endsWith(".class")) {
299: // see if the file is in fact a .class file, and determine its actual name.
300: InputStream input = null;
301: try {
302: input = new FileInputStream(file);
303: String className = ClassNameReader.getClassName(input);
304:
305: if (className != null) {
306: return className.replace('.', '/') + ".class";
307: }
308: } catch (IOException ioe) {
309: //do nothing
310: } finally {
311: if (input != null) {
312: try {
313: input.close();
314: } catch (IOException e) {
315: //do nothing
316: }
317: }
318: }
319: }
320: System.out.println("From " + file.getPath() + " and prefix "
321: + prefix + ", creating entry " + prefix + name);
322: return (prefix + name);
323: }
324:
325: /*
326: * Adds a file to the output stream.
327: */
328: private void addFile(ZipOutputStream output, File file,
329: String prefix, boolean compress) throws IOException {
330: //Make sure file exists
331: if (!file.exists()) {
332: return;
333: }
334: ZipEntry entry = new ZipEntry(getEntryName(file, prefix));
335:
336: entry.setTime(file.lastModified());
337: entry.setSize(file.length());
338: if (!compress) {
339: entry.setCrc(calcChecksum(file));
340: }
341: FileInputStream input = new FileInputStream(file);
342:
343: addToOutputStream(output, input, entry);
344: }
345:
346: /*
347: * A convenience method that several other methods might call.
348: */
349: private void addToOutputStream(ZipOutputStream output,
350: InputStream input, ZipEntry ze) throws IOException {
351: try {
352: output.putNextEntry(ze);
353: } catch (ZipException zipEx) {
354: //This entry already exists. So, go with the first one.
355: input.close();
356: return;
357: }
358:
359: int numBytes = -1;
360:
361: while ((numBytes = input.read(buffer)) > 0) {
362: output.write(buffer, 0, numBytes);
363: }
364: output.closeEntry();
365: input.close();
366: }
367:
368: /*
369: * A method that does the work on a given entry in a mergefile.
370: * The big deal is to set the right parameters in the ZipEntry
371: * on the output stream.
372: */
373: private ZipEntry processEntry(ZipFile zip, ZipEntry inputEntry) {
374: /*
375: First, some notes.
376: On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the
377: ZipInputStream does not work for compressed (deflated) files. Those calls return -1.
378: For uncompressed (stored) files, those calls do work.
379: However, using ZipFile.getEntries() works for both compressed and
380: uncompressed files.
381:
382: Now, from some simple testing I did, it seems that the value of CRC-32 is
383: independent of the compression setting. So, it should be easy to pass this
384: information on to the output entry.
385: */
386: String name = inputEntry.getName();
387:
388: if (!(inputEntry.isDirectory() || name.endsWith(".class"))) {
389: try {
390: InputStream input = zip.getInputStream(zip
391: .getEntry(name));
392: String className = ClassNameReader.getClassName(input);
393:
394: input.close();
395: if (className != null) {
396: name = className.replace('.', '/') + ".class";
397: }
398: } catch (IOException ioe) {
399: //do nothing
400: }
401: }
402: ZipEntry outputEntry = new ZipEntry(name);
403:
404: outputEntry.setTime(inputEntry.getTime());
405: outputEntry.setExtra(inputEntry.getExtra());
406: outputEntry.setComment(inputEntry.getComment());
407: outputEntry.setTime(inputEntry.getTime());
408: if (compression) {
409: outputEntry.setMethod(ZipEntry.DEFLATED);
410: //Note, don't need to specify size or crc for compressed files.
411: } else {
412: outputEntry.setMethod(ZipEntry.STORED);
413: outputEntry.setCrc(inputEntry.getCrc());
414: outputEntry.setSize(inputEntry.getSize());
415: }
416: return outputEntry;
417: }
418:
419: /*
420: * Necessary in the case where you add a entry that
421: * is not compressed.
422: */
423: private long calcChecksum(File f) throws IOException {
424: BufferedInputStream in = new BufferedInputStream(
425: new FileInputStream(f));
426:
427: return calcChecksum(in);
428: }
429:
430: /*
431: * Necessary in the case where you add a entry that
432: * is not compressed.
433: */
434: private long calcChecksum(InputStream in) throws IOException {
435: CRC32 crc = new CRC32();
436: int len = buffer.length;
437: int count = -1;
438: int haveRead = 0;
439:
440: while ((count = in.read(buffer, 0, len)) > 0) {
441: haveRead += count;
442: crc.update(buffer, 0, count);
443: }
444: in.close();
445: return crc.getValue();
446: }
447:
448: }
|