Many thanks to Patrick C. Beard <beard@netscape.com> for allowing his original jlink code to be placed under the Apache Software License. Submitted by: Matthew Kuperus Heun <matthew.k.heun@gaerospace.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@267982 13f79535-47bb-0310-9956-ffa450edef68master
@@ -3865,6 +3865,7 @@ it had been located at <code>htdocs/manual/ChangeLog.txt</code>.</p> | |||
<ul> | |||
<li><a href="#cab">Cab</a></li> | |||
<li><a href="#ftp">FTP</a></li> | |||
<li><a href="jlink.html">Jlink</a></li> | |||
<li><a href="junit.html">JUnit</a></li> | |||
<li><a href="#netrexxc">NetRexxC</a></li> | |||
<li><a href="#renameexts">RenameExtensions</a></li> | |||
@@ -0,0 +1,138 @@ | |||
<html> | |||
<head> | |||
</head> | |||
<body> | |||
<h2><a name="jlink">Jlink</a></h2> | |||
<h3><b>Description:</b></h3> | |||
<p>Links entries from sub-builds and libraries.</p> | |||
<p>The jlink task can be used to build jar and zip files, similar to | |||
the <i>jar</i> task. | |||
However, jlink provides options for controlling the way entries from | |||
input files | |||
are added to the output file. Specifically, capabilities for merging | |||
entries from | |||
multiple zip or jar files is available.</p> | |||
<p>If a mergefile is specified directly (eg. at the top level of a | |||
<i>mergefiles</i> | |||
pathelement) <i>and</i> the mergefile ends in ".zip" or | |||
".jar", | |||
entries in the mergefile will be merged into the outfile. A file with | |||
any other extension | |||
will be added to the output file, even if it is specified in the | |||
mergefiles element. | |||
Directories specified in either the mergefiles or addfiles element | |||
are added to the | |||
output file as you would expect: all files in subdirectories are | |||
recursively added to | |||
the output file with appropriate prefixes in the output file | |||
(without merging). | |||
</p> | |||
<p> | |||
In the case where duplicate entries and/or files are found among the | |||
files to be merged or | |||
added, jlink merges or adds the first entry and ignores all subsequent entries. | |||
</p> | |||
<p> | |||
jlink ignores META-INF directories in mergefiles. Users should supply their | |||
own manifest information for the output file. | |||
</p> | |||
<p>It is possible to refine the set of files that are being jlinked. | |||
This can be | |||
done with the <i>includes</i>, <i>includesfile</i>, <i>excludes</i>, | |||
<i>excludesfile</i>, | |||
and <i>defaultexcludes</i> attributes on the <i>addfiles</i> and | |||
<i>mergefiles</i> | |||
nested elements. With the <i>includes</i> or <i>includesfile</i> | |||
attribute you specify the files you want to have included by using patterns. | |||
The <i>exclude</i> or <i>excludesfile</i> attribute is used to specify | |||
the files you want to have excluded. This is also done with patterns. And | |||
finally with the <i>defaultexcludes</i> attribute, you can specify whether you | |||
want to use default exclusions or not. See the section on <a | |||
href="#directorybasedtasks">directory based tasks</a>, on how the | |||
inclusion/exclusion of files works, and how to write patterns. The patterns are | |||
relative to the <i>base</i> directory.</p> | |||
<h3>Parameters:</h3> | |||
<table border="1" cellpadding="2" cellspacing="0"> | |||
<tr> | |||
<td valign="top"><b>Attribute</b></td> | |||
<td valign="top"><b>Description</b></td> | |||
<td align="center" valign="top"><b>Required</b></td> | |||
</tr> | |||
<tr> | |||
<td valign="top">outfile</td> | |||
<td valign="top">the path of the output file.</td> | |||
<td valign="top" align="center">Yes</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">compress</td> | |||
<td valign="top">whether or not the output should be compressed. | |||
<i>true</i>, | |||
<i>yes</i>, or <i>on</i> result in compressed output. | |||
If omitted, output will be uncompressed (inflated).</td> | |||
<td valign="top" align="center">No</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">mergefiles</td> | |||
<td valign="top">files to be merged into the output, if possible.</td> | |||
<td valign="middle" align="middle" rowspan="2">At least one of | |||
mergefiles or addfiles</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">addfiles</td> | |||
<td valign="top">files to be added to the output.</td> | |||
</tr> | |||
</table> | |||
<h3>Examples</h3> | |||
The following will merge the entries in mergefoo.jar and mergebar.jar | |||
into out.jar. | |||
mac.jar and pc.jar will be added as single entries to out.jar. | |||
<pre> | |||
<jlink compress="false" outfile="out.jar"/> | |||
<mergefiles> | |||
<pathelement path="${build.dir}/mergefoo.jar"/> | |||
<pathelement path="${build.dir}/mergebar.jar"/> | |||
</mergefiles> | |||
<addfiles> | |||
<pathelement path="${build.dir}/mac.jar"/> | |||
<pathelement path="${build.dir}/pc.zip"/> | |||
</addfiles> | |||
</jlink> | |||
</pre> | |||
Suppose the file foo.jar contains two entries: bar.class and | |||
barnone/myClass.zip. | |||
Suppose the path for file foo.jar is build/tempbuild/foo.jar. The | |||
following example | |||
will provide the entry tempbuild/foo.jar in the out.jar. | |||
<pre> | |||
<jlink compress="false" outfile="out.jar"/> | |||
<mergefiles> | |||
<pathelement path="build/tempbuild"/> | |||
</mergefiles> | |||
</jlink> | |||
</pre> | |||
However, the next example would result in two top-level entries in out.jar, | |||
namely bar.class and barnone/myClass.zip | |||
<pre> | |||
<jlink compress="false" outfile="out.jar"/> | |||
<mergefiles> | |||
<pathelement path="build/tempbuild/foo.jar"/> | |||
</mergefiles> | |||
</jlink> | |||
</pre> | |||
</body> | |||
</html> |
@@ -60,6 +60,7 @@ ftp=org.apache.tools.ant.taskdefs.optional.FTP | |||
javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC | |||
jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree | |||
starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut | |||
jlink=org.apache.tools.ant.taskdefs.optional.jlink.JlinkTask | |||
# deprecated ant tasks (kept for back compatibility) | |||
javadoc2=org.apache.tools.ant.taskdefs.Javadoc |
@@ -0,0 +1,161 @@ | |||
/* | |||
* 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/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.jlink; | |||
import java.io .*; | |||
/** | |||
* Reads just enough of a class file to determine the class' full name. | |||
* | |||
* <p>Extremely minimal constant pool implementation, mainly to support extracting | |||
* strings from a class file. | |||
* @author <a href="mailto:beard@netscape.com">Patrick C. Beard</a>. | |||
*/ | |||
class ConstantPool extends Object{ | |||
static final | |||
byte UTF8 = 1, UNUSED = 2, INTEGER = 3, FLOAT = 4, LONG = 5, DOUBLE = 6, | |||
CLASS = 7, STRING = 8, FIELDREF = 9, METHODREF = 10, | |||
INTERFACEMETHODREF = 11, NAMEANDTYPE = 12; | |||
byte[] types; | |||
Object[] values; | |||
ConstantPool( DataInput data ) throws IOException { | |||
super(); | |||
int count = data .readUnsignedShort(); | |||
types = new byte [ count ]; | |||
values = new Object [ count ]; | |||
// read in all constant pool entries. | |||
for ( int i = 1; i < count; i++ ) { | |||
byte type = data .readByte(); | |||
types[i] = type; | |||
switch (type) | |||
{ | |||
case UTF8 : | |||
values[i] = data .readUTF(); | |||
break; | |||
case UNUSED : | |||
break; | |||
case INTEGER : | |||
values[i] = new Integer( data .readInt() ); | |||
break; | |||
case FLOAT : | |||
values[i] = new Float( data .readFloat() ); | |||
break; | |||
case LONG : | |||
values[i] = new Long( data .readLong() ); | |||
++i; | |||
break; | |||
case DOUBLE : | |||
values[i] = new Double( data .readDouble() ); | |||
++i; | |||
break; | |||
case CLASS : | |||
case STRING : | |||
values[i] = new Integer( data .readUnsignedShort() ); | |||
break; | |||
case FIELDREF : | |||
case METHODREF : | |||
case INTERFACEMETHODREF : | |||
case NAMEANDTYPE : | |||
values[i] = new Integer( data .readInt() ); | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Provides a quick and dirty way to determine the true name of a class | |||
* given just an InputStream. Reads in just enough to perform this | |||
* minimal task only. | |||
*/ | |||
public class ClassNameReader extends Object{ | |||
public static | |||
String getClassName( InputStream input ) throws IOException { | |||
DataInputStream data = new DataInputStream( input ); | |||
// verify this is a valid class file. | |||
int cookie = data .readInt(); | |||
if ( cookie != 0xCAFEBABE ) { | |||
return null; | |||
} | |||
int version = data .readInt(); | |||
// read the constant pool. | |||
ConstantPool constants = new ConstantPool( data ); | |||
Object[] values = constants .values; | |||
// read access flags and class index. | |||
int accessFlags = data .readUnsignedShort(); | |||
int classIndex = data .readUnsignedShort(); | |||
Integer stringIndex = (Integer) values[classIndex]; | |||
String className = (String) values[stringIndex .intValue()]; | |||
return className; | |||
} | |||
} | |||
@@ -0,0 +1,211 @@ | |||
/* | |||
* 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/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.jlink; | |||
import org.apache.tools.ant .*; | |||
import org.apache.tools.ant.taskdefs.MatchingTask; | |||
import org.apache.tools.ant.types .*; | |||
import java.io.File; | |||
/** | |||
* This class defines objects that can link together various jar and | |||
* zip files. | |||
* | |||
* <p>It is basically a wrapper for the jlink code written originally | |||
* by <a href="mailto:beard@netscape.com">Patrick Beard</a>. The | |||
* classes org.apache.tools.ant.taskdefs.optional.jlink.Jlink and | |||
* org.apache.tools.ant.taskdefs.optional.jlink.ClassNameReader | |||
* support this class.</p> | |||
* | |||
* <p>For example: | |||
* <code> | |||
* <pre> | |||
* <jlink compress="false" outfile="out.jar"/> | |||
* <mergefiles> | |||
* <pathelement path="${build.dir}/mergefoo.jar"/> | |||
* <pathelement path="${build.dir}/mergebar.jar"/> | |||
* </mergefiles> | |||
* <addfiles> | |||
* <pathelement path="${build.dir}/mac.jar"/> | |||
* <pathelement path="${build.dir}/pc.zip"/> | |||
* </addfiles> | |||
* </jlink> | |||
* </pre> | |||
* </code> | |||
* | |||
* @author <a href="mailto:matthew.k.heun@gaerospace.com">Matthew Kuperus Heun</a> */ | |||
public class JlinkTask extends MatchingTask { | |||
/** | |||
* The output file for this run of jlink. Usually a jar or zip file. | |||
*/ | |||
public void setOutfile( File outfile ) { | |||
this.outfile = outfile; | |||
} | |||
/** | |||
* Establishes the object that contains the files to | |||
* be merged into the output. | |||
*/ | |||
public Path createMergefiles() { | |||
if ( this .mergefiles == null ) { | |||
this .mergefiles = new Path(getProject()); | |||
} | |||
return this .mergefiles.createPath(); | |||
} | |||
/** | |||
* Sets the files to be merged into the output. | |||
*/ | |||
public void setMergefiles( Path mergefiles ) { | |||
if ( this .mergefiles == null ) { | |||
this .mergefiles = mergefiles; | |||
} | |||
else { | |||
this .mergefiles .append( mergefiles ); | |||
} | |||
} | |||
/** | |||
* Establishes the object that contains the files to | |||
* be added to the output. | |||
*/ | |||
public Path createAddfiles() { | |||
if ( this .addfiles == null ) { | |||
this .addfiles = new Path(getProject()); | |||
} | |||
return this .addfiles .createPath(); | |||
} | |||
/** | |||
* Sets the files to be added into the output. | |||
*/ | |||
public void setAddfiles( Path addfiles ) { | |||
if ( this .addfiles == null ) { | |||
this .addfiles = addfiles; | |||
} | |||
else { | |||
this .addfiles .append( addfiles ); | |||
} | |||
} | |||
/** | |||
* Defines whether or not the output should be compacted. | |||
*/ | |||
public void setCompress( boolean compress ) { | |||
this .compress = compress; | |||
} | |||
/** | |||
* Does the adding and merging. | |||
*/ | |||
public void execute() throws BuildException { | |||
//Be sure everything has been set. | |||
if ( outfile == null ) { | |||
throw new BuildException( "outfile attribute is required! Please set." ); | |||
} | |||
if (!haveAddFiles() && !haveMergeFiles()) { | |||
throw new BuildException( "addfiles or mergefiles required! Please set." ); | |||
} | |||
log( "linking: " + outfile.getPath() ); | |||
log( "compression: " + compress, Project.MSG_VERBOSE ); | |||
jlink linker = new jlink(); | |||
linker .setOutfile( outfile.getPath() ); | |||
linker .setCompression( compress ); | |||
if (haveMergeFiles()){ | |||
log( "merge files: " + mergefiles .toString(), Project .MSG_VERBOSE ); | |||
linker .addMergeFiles( mergefiles .list() ); | |||
} | |||
if (haveAddFiles()){ | |||
log( "add files: " + addfiles .toString(), Project .MSG_VERBOSE ); | |||
linker .addAddFiles( addfiles .list() ); | |||
} | |||
try { | |||
linker .link(); | |||
} catch( Exception ex ) { | |||
throw new BuildException( ex, location ); | |||
} | |||
} | |||
private boolean haveAddFiles(){ | |||
return haveEntries(addfiles); | |||
} | |||
private boolean haveMergeFiles(){ | |||
return haveEntries(mergefiles); | |||
} | |||
private boolean haveEntries(Path p){ | |||
if (p == null){ | |||
return false; | |||
} | |||
if (p.size() > 0){ | |||
return true; | |||
} | |||
return false; | |||
} | |||
private File outfile = null; | |||
private Path mergefiles = null; | |||
private Path addfiles = null; | |||
private boolean compress = false; | |||
private String ps = System .getProperty( "path.separator" ); | |||
} | |||
@@ -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]; | |||
} | |||