|
|
@@ -0,0 +1,420 @@ |
|
|
|
/* |
|
|
|
* The Apache Software License, Version 1.1 |
|
|
|
* |
|
|
|
* Copyright (c) 2000 The Apache Software Foundation. All rights |
|
|
|
* reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions |
|
|
|
* are met: |
|
|
|
* |
|
|
|
* 1. Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* |
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in |
|
|
|
* the documentation and/or other materials provided with the |
|
|
|
* distribution. |
|
|
|
* |
|
|
|
* 3. The end-user documentation included with the redistribution, if |
|
|
|
* any, must include the following acknowlegement: |
|
|
|
* "This product includes software developed by the |
|
|
|
* Apache Software Foundation (http://www.apache.org/)." |
|
|
|
* Alternately, this acknowlegement may appear in the software itself, |
|
|
|
* if and wherever such third-party acknowlegements normally appear. |
|
|
|
* |
|
|
|
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software |
|
|
|
* Foundation" must not be used to endorse or promote products derived |
|
|
|
* from this software without prior written permission. For written |
|
|
|
* permission, please contact apache@apache.org. |
|
|
|
* |
|
|
|
* 5. Products derived from this software may not be called "Apache" |
|
|
|
* nor may "Apache" appear in their names without prior written |
|
|
|
* permission of the Apache Group. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
|
|
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
|
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
|
|
* SUCH DAMAGE. |
|
|
|
* ==================================================================== |
|
|
|
* |
|
|
|
* This software consists of voluntary contributions made by many |
|
|
|
* individuals on behalf of the Apache Software Foundation. For more |
|
|
|
* information on the Apache Software Foundation, please see |
|
|
|
* <http://www.apache.org/>. |
|
|
|
*/ |
|
|
|
|
|
|
|
/** |
|
|
|
* jlink.java |
|
|
|
* links together multiple .jar files |
|
|
|
* |
|
|
|
* Original code by Patrick Beard. Modifications to work |
|
|
|
* with ANT by Matthew Kuperus Heun. |
|
|
|
* |
|
|
|
* @author <a href="mailto:beard@netscape.com>Patrick C. Beard</a>. |
|
|
|
* @author <a href="mailto:matthew.k.heun@gaerospace.com>Matthew Kuperus Heun</a> |
|
|
|
*/ |
|
|
|
package org.apache.tools.ant.taskdefs.optional.jlink; |
|
|
|
|
|
|
|
import java.io .*; |
|
|
|
import java.util.zip .*; |
|
|
|
import java.util .Vector; |
|
|
|
import java.util .Enumeration; |
|
|
|
|
|
|
|
public class jlink extends Object{ |
|
|
|
|
|
|
|
/** |
|
|
|
* The file that will be created by this instance of jlink. |
|
|
|
*/ |
|
|
|
public void setOutfile( String outfile ) { |
|
|
|
if ( outfile == null ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
this .outfile = outfile; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds a file to be merged into the output. |
|
|
|
*/ |
|
|
|
public void addMergeFile( String mergefile ) { |
|
|
|
if ( mergefile == null ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
mergefiles .addElement( mergefile ); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds a file to be added into the output. |
|
|
|
*/ |
|
|
|
public void addAddFile( String addfile ) { |
|
|
|
if ( addfile == null ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
addfiles .addElement( addfile ); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds several files to be merged into the output. |
|
|
|
*/ |
|
|
|
public void addMergeFiles( String[] mergefiles ) { |
|
|
|
if ( mergefiles == null ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
for ( int i = 0; i < mergefiles .length; i++ ) { |
|
|
|
addMergeFile( mergefiles[i] ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds several file to be added into the output. |
|
|
|
*/ |
|
|
|
public void addAddFiles( String[] addfiles ) { |
|
|
|
if ( addfiles == null ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
for ( int i = 0; i < addfiles .length; i++ ) { |
|
|
|
addAddFile( addfiles[i] ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Determines whether output will be compressed. |
|
|
|
*/ |
|
|
|
public void setCompression( boolean compress ) { |
|
|
|
this .compression = compress; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Performs the linking of files. |
|
|
|
* Addfiles are added to the output as-is. For example, a |
|
|
|
* jar file is added to the output as a jar file. |
|
|
|
* However, mergefiles are first examined for their type. |
|
|
|
* If it is a jar or zip file, the contents will be extracted |
|
|
|
* from the mergefile and entered into the output. |
|
|
|
* If a zip or jar file is encountered in a subdirectory |
|
|
|
* it will be added, not merged. |
|
|
|
* If a directory is encountered, it becomes the root |
|
|
|
* entry of all the files below it. Thus, you can |
|
|
|
* provide multiple, disjoint directories, as |
|
|
|
* addfiles: they will all be added in a rational |
|
|
|
* manner to outfile. |
|
|
|
*/ |
|
|
|
public void link() throws Exception { |
|
|
|
ZipOutputStream output = new ZipOutputStream( new FileOutputStream( outfile ) ); |
|
|
|
if ( compression ) { |
|
|
|
output .setMethod( ZipOutputStream .DEFLATED ); |
|
|
|
output .setLevel( Deflater .DEFAULT_COMPRESSION ); |
|
|
|
} else { |
|
|
|
output .setMethod( ZipOutputStream .STORED ); |
|
|
|
} |
|
|
|
Enumeration merges = mergefiles .elements(); |
|
|
|
while ( merges .hasMoreElements() ) { |
|
|
|
String path = (String) merges .nextElement(); |
|
|
|
File f = new File( path ); |
|
|
|
if ( f.getName().endsWith( ".jar" ) || f.getName().endsWith( ".zip" ) ) { |
|
|
|
//Do the merge |
|
|
|
mergeZipJarContents( output, f ); |
|
|
|
} |
|
|
|
else { |
|
|
|
//Add this file to the addfiles Vector and add it later at the top level of the output file. |
|
|
|
addAddFile( path ); |
|
|
|
} |
|
|
|
} |
|
|
|
Enumeration adds = addfiles .elements(); |
|
|
|
while ( adds .hasMoreElements() ) { |
|
|
|
String name = (String) adds .nextElement(); |
|
|
|
File f = new File( name ); |
|
|
|
if ( f .isDirectory() ) { |
|
|
|
//System.out.println("in jlink: adding directory contents of " + f.getPath()); |
|
|
|
addDirContents( output, f, f.getName() + '/', compression ); |
|
|
|
} |
|
|
|
else { |
|
|
|
addFile( output, f, "", compression ); |
|
|
|
} |
|
|
|
} |
|
|
|
if ( output != null ) { |
|
|
|
try { |
|
|
|
output .close(); |
|
|
|
} catch( IOException ioe ) {} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public static void main( String[] args ) { |
|
|
|
// jlink output input1 ... inputN |
|
|
|
if ( args .length < 2 ) { |
|
|
|
System .out .println( "usage: jlink output input1 ... inputN" ); |
|
|
|
System .exit( 1 ); |
|
|
|
} |
|
|
|
jlink linker = new jlink(); |
|
|
|
linker .setOutfile( args[0] ); |
|
|
|
//To maintain compatibility with the command-line version, we will only add files to be merged. |
|
|
|
for ( int i = 1; i < args .length; i++ ) { |
|
|
|
linker .addMergeFile( args[i] ); |
|
|
|
} |
|
|
|
try { |
|
|
|
linker .link(); |
|
|
|
} catch( Exception ex ) { |
|
|
|
System .err .print( ex .getMessage() ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Actually performs the merging of f into the output. |
|
|
|
* f should be a zip or jar file. |
|
|
|
*/ |
|
|
|
private void mergeZipJarContents( ZipOutputStream output, File f ) throws IOException { |
|
|
|
//Check to see that the file with name "name" exists. |
|
|
|
if ( ! f .exists() ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
ZipFile zipf = new ZipFile( f ); |
|
|
|
Enumeration entries = zipf.entries(); |
|
|
|
while (entries.hasMoreElements()){ |
|
|
|
ZipEntry inputEntry = (ZipEntry) entries.nextElement(); |
|
|
|
//Ignore manifest entries. They're bound to cause conflicts between |
|
|
|
//files that are being merged. User should supply their own |
|
|
|
//manifest file when doing the merge. |
|
|
|
String inputEntryName = inputEntry.getName(); |
|
|
|
int index = inputEntryName.indexOf("META-INF"); |
|
|
|
if (index < 0){ |
|
|
|
//META-INF not found in the name of the entry. Go ahead and process it. |
|
|
|
try { |
|
|
|
output.putNextEntry(processEntry(zipf, inputEntry)); |
|
|
|
} catch (ZipException ex){ |
|
|
|
//If we get here, it could be because we are trying to put a |
|
|
|
//directory entry that already exists. |
|
|
|
//For example, we're trying to write "com", but a previous |
|
|
|
//entry from another mergefile was called "com". |
|
|
|
//In that case, just ignore the error and go on to the |
|
|
|
//next entry. |
|
|
|
String mess = ex.getMessage(); |
|
|
|
if (mess.indexOf("duplicate") > 0){ |
|
|
|
//It was the duplicate entry. |
|
|
|
continue; |
|
|
|
} else { |
|
|
|
//I hate to admit it, but we don't know what happened here. Throw the Exception. |
|
|
|
throw ex; |
|
|
|
} |
|
|
|
} |
|
|
|
InputStream in = zipf.getInputStream(inputEntry); |
|
|
|
int len = buffer.length; |
|
|
|
int count = -1; |
|
|
|
while ((count = in.read(buffer, 0, len)) > 0){ |
|
|
|
output.write(buffer, 0, count); |
|
|
|
} |
|
|
|
in.close(); |
|
|
|
output.closeEntry(); |
|
|
|
} |
|
|
|
} |
|
|
|
zipf .close(); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Adds contents of a directory to the output. |
|
|
|
*/ |
|
|
|
private void addDirContents( ZipOutputStream output, File dir, String prefix, boolean compress ) throws IOException { |
|
|
|
String[] contents = dir .list(); |
|
|
|
for ( int i = 0; i < contents .length; ++i ) { |
|
|
|
String name = contents[i]; |
|
|
|
File file = new File( dir, name ); |
|
|
|
if ( file .isDirectory() ) { |
|
|
|
addDirContents( output, file, prefix + name + '/', compress ); |
|
|
|
} |
|
|
|
else { |
|
|
|
addFile( output, file, prefix, compress ); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Gets the name of an entry in the file. This is the real name |
|
|
|
* which for a class is the name of the package with the class |
|
|
|
* name appended. |
|
|
|
*/ |
|
|
|
private String getEntryName( File file, String prefix ) { |
|
|
|
String name = file .getName(); |
|
|
|
if ( ! name .endsWith( ".class" ) ) { |
|
|
|
// see if the file is in fact a .class file, and determine its actual name. |
|
|
|
try { |
|
|
|
InputStream input = new FileInputStream( file ); |
|
|
|
String className = ClassNameReader .getClassName( input ); |
|
|
|
input .close(); |
|
|
|
if ( className != null ) { |
|
|
|
return className .replace( '.', '/' ) + ".class"; |
|
|
|
} |
|
|
|
} catch( IOException ioe ) {} |
|
|
|
} |
|
|
|
System.out.println("From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix+name); |
|
|
|
return (prefix + name); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Adds a file to the output stream. |
|
|
|
*/ |
|
|
|
private void addFile( ZipOutputStream output, File file, String prefix, boolean compress) throws IOException { |
|
|
|
//Make sure file exists |
|
|
|
long checksum = 0; |
|
|
|
if ( ! file .exists() ) { |
|
|
|
return ; |
|
|
|
} |
|
|
|
ZipEntry entry = new ZipEntry( getEntryName( file, prefix ) ); |
|
|
|
entry .setTime( file .lastModified() ); |
|
|
|
entry .setSize( file .length() ); |
|
|
|
if (! compress){ |
|
|
|
entry.setCrc(calcChecksum(file)); |
|
|
|
} |
|
|
|
FileInputStream input = new FileInputStream( file ); |
|
|
|
addToOutputStream(output, input, entry); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* A convenience method that several other methods might call. |
|
|
|
*/ |
|
|
|
private void addToOutputStream(ZipOutputStream output, InputStream input, ZipEntry ze) throws IOException{ |
|
|
|
try { |
|
|
|
output.putNextEntry(ze); |
|
|
|
} catch (ZipException zipEx) { |
|
|
|
//This entry already exists. So, go with the first one. |
|
|
|
input.close(); |
|
|
|
return; |
|
|
|
} |
|
|
|
int numBytes = -1; |
|
|
|
while((numBytes = input.read(buffer)) > 0){ |
|
|
|
output.write(buffer, 0, numBytes); |
|
|
|
} |
|
|
|
output.closeEntry(); |
|
|
|
input.close(); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* A method that does the work on a given entry in a mergefile. |
|
|
|
* The big deal is to set the right parameters in the ZipEntry |
|
|
|
* on the output stream. |
|
|
|
*/ |
|
|
|
private ZipEntry processEntry( ZipFile zip, ZipEntry inputEntry ) throws IOException{ |
|
|
|
/* |
|
|
|
First, some notes. |
|
|
|
On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the |
|
|
|
ZipInputStream does not work for compressed (deflated) files. Those calls return -1. |
|
|
|
For uncompressed (stored) files, those calls do work. |
|
|
|
However, using ZipFile.getEntries() works for both compressed and |
|
|
|
uncompressed files. |
|
|
|
|
|
|
|
Now, from some simple testing I did, it seems that the value of CRC-32 is |
|
|
|
independent of the compression setting. So, it should be easy to pass this |
|
|
|
information on to the output entry. |
|
|
|
*/ |
|
|
|
String name = inputEntry .getName(); |
|
|
|
if ( ! (inputEntry .isDirectory() || name .endsWith( ".class" )) ) { |
|
|
|
try { |
|
|
|
InputStream input = zip.getInputStream( zip .getEntry( name ) ); |
|
|
|
String className = ClassNameReader .getClassName( input ); |
|
|
|
input .close(); |
|
|
|
if ( className != null ) { |
|
|
|
name = className .replace( '.', '/' ) + ".class"; |
|
|
|
} |
|
|
|
} catch( IOException ioe ) {} |
|
|
|
} |
|
|
|
ZipEntry outputEntry = new ZipEntry( name ); |
|
|
|
outputEntry.setTime(inputEntry .getTime() ); |
|
|
|
outputEntry.setExtra(inputEntry.getExtra()); |
|
|
|
outputEntry.setComment(inputEntry.getComment()); |
|
|
|
outputEntry.setTime(inputEntry.getTime()); |
|
|
|
if (compression){ |
|
|
|
outputEntry.setMethod(ZipEntry.DEFLATED); |
|
|
|
//Note, don't need to specify size or crc for compressed files. |
|
|
|
} else { |
|
|
|
outputEntry.setMethod(ZipEntry.STORED); |
|
|
|
outputEntry.setCrc(inputEntry.getCrc()); |
|
|
|
outputEntry.setSize(inputEntry.getSize()); |
|
|
|
} |
|
|
|
return outputEntry; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Necessary in the case where you add a entry that |
|
|
|
* is not compressed. |
|
|
|
*/ |
|
|
|
private long calcChecksum(File f) throws IOException { |
|
|
|
BufferedInputStream in = new BufferedInputStream(new FileInputStream(f)); |
|
|
|
return calcChecksum(in, f.length()); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Necessary in the case where you add a entry that |
|
|
|
* is not compressed. |
|
|
|
*/ |
|
|
|
private long calcChecksum(InputStream in, long size) throws IOException{ |
|
|
|
CRC32 crc = new CRC32(); |
|
|
|
int len = buffer.length; |
|
|
|
int count = -1; |
|
|
|
int haveRead = 0; |
|
|
|
while((count=in.read(buffer, 0, len)) > 0){ |
|
|
|
haveRead += count; |
|
|
|
crc.update(buffer, 0, count); |
|
|
|
} |
|
|
|
in.close(); |
|
|
|
return crc.getValue(); |
|
|
|
} |
|
|
|
|
|
|
|
private String outfile = null; |
|
|
|
|
|
|
|
private Vector mergefiles = new Vector( 10 ); |
|
|
|
|
|
|
|
private Vector addfiles = new Vector( 10 ); |
|
|
|
|
|
|
|
private boolean compression = false; |
|
|
|
|
|
|
|
byte[] buffer = new byte[8192]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|