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> | <ul> | ||||
| <li><a href="#cab">Cab</a></li> | <li><a href="#cab">Cab</a></li> | ||||
| <li><a href="#ftp">FTP</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="junit.html">JUnit</a></li> | ||||
| <li><a href="#netrexxc">NetRexxC</a></li> | <li><a href="#netrexxc">NetRexxC</a></li> | ||||
| <li><a href="#renameexts">RenameExtensions</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 | javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC | ||||
| jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree | jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree | ||||
| starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut | 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) | # deprecated ant tasks (kept for back compatibility) | ||||
| javadoc2=org.apache.tools.ant.taskdefs.Javadoc | 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]; | |||||
| } | |||||