Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
uk.ac.starlink.util.SuperJar Maven / Gradle / Ivy
package uk.ac.starlink.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Utility to generate a single jar file containing all the resources
* referenced by a given jar file. As well as the contents of the
* named jar file itself, the contents of any jar files referenced
* in the Class-Path line of that file's Manifest will be included, and
* so on recursively. This can be used to produce a standalone jar
* file for applications which do not require JNI components.
*
* This class has a main method, and is designed to be invoked
* from the command line.
*
* @author Mark Taylor (Starlink)
*/
public class SuperJar {
private final File[] jarFiles_;
private final File[] flatFiles_;
private final File[] jarDeps_;
private final Collection excludeSet_;
private static PrintStream logStrm_ = System.err;
/**
* Constructor.
*
* The entryExcludes
values are full pathnames
* with no leading "/", interpreted as follows:
*
* ending in "/": exclude that directory and all children
* ending in "/*": exclude all flat files, but not subdirectories
* of that directory
* otherwise: exclude the flat file with that exact name
*
*
* @param jarFiles top-level jar files containing files and dependencies
* @param flatFiles files for inclusion at top level of output
* @param jarExcludes names of jar files which may be named as class-path
* dependencies but which should not be included in the result
* @param entryExcludes jar file entries which should be excluded
* from the result
*/
public SuperJar( File[] jarFiles, File[] flatFiles, String[] jarExcludes,
String[] entryExcludes ) throws IOException {
jarFiles_ = jarFiles;
flatFiles_ = flatFiles;
excludeSet_ = new HashSet( Arrays.asList( entryExcludes ) );
excludeSet_.add( "MANIFEST.MF" );
jarDeps_ = getDependencies( jarFiles, jarExcludes );
}
/**
* Writes the data from this object to a single jar file combining the
* contents of all the dependencies.
*
* @param out destination stream
*/
public void writeSingleJar( OutputStream out ) throws IOException {
/* Construct a manifest for the output jar file.
* This contains the main (not per-entry) attributes from the first
* input jar file, minus any class-path entry. */
Manifest inManifest = readManifest( jarFiles_[ 0 ] );
Manifest outManifest = new Manifest();
for ( Map.Entry entry :
inManifest.getMainAttributes().entrySet() ) {
Attributes outAtts = outManifest.getMainAttributes();
if ( ! entry.getKey().equals( Attributes.Name.CLASS_PATH ) ) {
outAtts.put( entry.getKey(), entry.getValue() );
}
}
/* Set up output to single output jar file. */
JarOutputStream jout = new JarOutputStream( out, outManifest );
/* Write flat files. */
writeFlatFiles( jout );
/* Write all items from input jar files. */
for ( int ij = 0; ij < jarDeps_.length; ij++ ) {
JarInputStream jin =
new JarInputStream(
new BufferedInputStream(
new FileInputStream( jarDeps_[ ij ] ) ) );
for ( JarEntry jent; ( jent = jin.getNextJarEntry() ) != null;
jin.closeEntry() ) {
if ( ! excludeEntry( jent ) ) {
jout.putNextEntry( jent );
IOUtils.copy( jin, jout );
jout.closeEntry();
}
}
}
jout.finish();
}
/**
* Writes the data from this object to a zip file containing all the
* top-level and dependency jars as individual jar files in the same
* directory.
*
* @param out destination stream
*/
public void writeZipOfJars( OutputStream out ) throws IOException {
/* All the jar files will go effectively in the same directory
* (at the same level in the zip file). Check that they have
* different names. */
Map tailMap = new HashMap();
for ( int ij = 0; ij < jarDeps_.length; ij++ ) {
File file = jarDeps_[ ij ];
String tail = file.getName();
if ( tailMap.containsKey( tail ) ) {
throw new RuntimeException( "Jar file name clash: "
+ tailMap.get( tail ) + ", " + file );
}
tailMap.put( tail, file );
}
/* Prepare to write to a big zip file. */
ZipOutputStream zout = new ZipOutputStream( out );
/* Write flat files. */
writeFlatFiles( zout );
/* Write each of the jar files. */
for ( int ij = 0; ij < jarDeps_.length; ij++ ) {
File jfile = jarDeps_[ ij ];
String jtail = jfile.getName();
Manifest inManifest = readManifest( jfile );
/* If it's a jar file (has a manifest), doctor the manifest
* to get the classpath right, and then copy entries. */
if ( inManifest != null ) {
String[] cpents = getClassPath( inManifest );
StringBuffer cpbuf = new StringBuffer();
for ( int ic = 0; ic < cpents.length; ic++ ) {
String cptail = new File( cpents[ ic ] ).getName();
if ( tailMap.containsKey( cptail ) ) {
if ( cpbuf.length() > 0 ) {
cpbuf.append( ' ' );
}
cpbuf.append( cptail );
}
}
Attributes atts = inManifest.getMainAttributes();
atts.put( Attributes.Name.CLASS_PATH, cpbuf.toString() );
Manifest outManifest = new Manifest();
outManifest.getMainAttributes().putAll( atts );
zout.putNextEntry( new ZipEntry( jtail ) );
JarOutputStream jout = new JarOutputStream( zout, outManifest );
JarInputStream jin =
new JarInputStream(
new BufferedInputStream(
new FileInputStream( jfile ) ) );
for ( JarEntry jent; ( jent = jin.getNextJarEntry() ) != null;
jin.closeEntry() ) {
if ( ! excludeEntry( jent ) &&
! jent.getName().startsWith( "META-INF" ) ) {
jout.putNextEntry( jent );
IOUtils.copy( jin, jout );
jout.closeEntry();
}
}
jout.finish();
zout.closeEntry();
}
/* Otherwise just copy it as a zip file. */
else {
zout.putNextEntry( new ZipEntry( jtail ) );
ZipOutputStream z2out = new ZipOutputStream( zout );
ZipInputStream zin =
new ZipInputStream(
new BufferedInputStream(
new FileInputStream( jfile ) ) );
for ( ZipEntry zent; ( zent = zin.getNextEntry() ) != null;
zin.closeEntry() ) {
if ( ! excludeEntry( zent ) ) {
z2out.putNextEntry( zent );
IOUtils.copy( zin, z2out );
}
}
z2out.finish();
zout.closeEntry();
}
}
zout.finish();
}
/**
* Determines whether this object has marked a given jar file entry for
* exclusion from the result.
*
* @param entry entry to test
* @return true iff entry
is marked for exclusion
*/
private boolean excludeEntry( ZipEntry entry ) {
if ( entry.isDirectory() ) {
return true;
}
String name = entry.getName();
for ( String exclude : excludeSet_ ) {
if ( exclude.endsWith( "/" ) ) {
if ( name.startsWith( exclude ) ) {
return true;
}
}
else if ( exclude.endsWith( "/*" ) || exclude.equals( "*" ) ) {
String dir = exclude.substring( 0, exclude.length() - 1 );
if ( name.startsWith( dir ) &&
name.indexOf( '/', dir.length() ) == -1 ) {
return true;
}
}
else {
if ( name.equals( exclude ) ) {
return true;
}
}
}
return false;
}
/**
* Outputs this objects designated flat files to a given zip output stream.
*
* @param zout destination archive stream
*/
private void writeFlatFiles( ZipOutputStream zout ) throws IOException {
for ( int i = 0; i < flatFiles_.length; i++ ) {
File file = flatFiles_[ i ];
String ftail = file.getName();
ZipEntry zent = new ZipEntry( ftail );
zout.putNextEntry( zent );
InputStream fin = new FileInputStream( file );
IOUtils.copy( fin, zout );
fin.close();
zout.closeEntry();
}
}
/**
* Returns an array of all the jar files which count as dependencies of
* the given array of jar files. This is the given files themselves,
* as well as any files named in their Class-Path manifest attributes,
* assembled recursively, and excluding duplicates and any that
* have been marked for exclusion.
*
* @param jarfiles input jar files
* @param jarExcludes names of jar files which may be named as class-path
* dependencies but which should not be included in the result
* @return input jarfiles plus recursive dependencies
*/
private static File[] getDependencies( File[] jarfiles,
String[] jarExcludes )
throws IOException {
Collection jfSet = new HashSet();
Collection jarExcludeSet =
new HashSet( Arrays.asList( jarExcludes ) );
for ( int ij = 0; ij < jarfiles.length; ij++ ) {
accumulateDependencies( jarfiles[ ij ], jarExcludeSet, jfSet );
}
File[] jfiles = jfSet.toArray( new File[ 0 ] );
Arrays.sort( jfiles );
return jfiles;
}
/**
* Recursively accumulate dependencies for a given jar file.
*
* @param jfile input jar file
* @param jarExcludeSet collection of Strings naming jar files which
* may be named as class-path dependencies but which should not
* be included in the result
* @param jfSet set of File objects representing the current list of
* known dependencies; this method appends to it
*/
private static void accumulateDependencies( File jfile,
Collection jarExcludeSet,
Collection jfSet )
throws IOException {
if ( jfile.isDirectory() ) {
throw new IllegalArgumentException( jfile + " is a directory, " +
"only jarfiles allowed" );
}
if ( containsFilename( jarExcludeSet, jfile ) ) {
if ( logStrm_ != null ) {
logStrm_.println( " Excluding: " + jfile );
}
}
else if ( containsFile( jfSet, jfile ) ) {
if ( logStrm_ != null ) {
logStrm_.println( " Duplicate: " + jfile );
}
}
else {
if ( logStrm_ != null ) {
logStrm_.println( jfile );
}
jfSet.add( jfile );
String[] cpents = getClassPath( readManifest( jfile ) );
File dir = jfile.getParentFile();
for ( int ie = 0; ie < cpents.length; ie++ ) {
accumulateDependencies( new File( dir, cpents[ ie ] ),
jarExcludeSet, jfSet );
}
}
}
/**
* Reads the manifest from a jar file.
*
* @param jfile jar file
* @return manifest
*/
private static Manifest readManifest( File jfile ) throws IOException {
JarInputStream jin =
new JarInputStream(
new BufferedInputStream( new FileInputStream( jfile ) ) );
Manifest manifest = jin.getManifest();
jin.close();
return manifest;
}
/**
* Gets the declared class-path attribute from a manifest, returning
* the result as an array of strings, one for each entry.
*
* @param manifest manifest
* @return declared class path elements
*/
private static String[] getClassPath( Manifest manifest ) {
if ( manifest == null ) {
return new String[ 0 ];
}
else {
Attributes atts = manifest.getMainAttributes();
String classpath = atts.getValue( Attributes.Name.CLASS_PATH );
return classpath == null || classpath.trim().length() == 0
? new String[ 0 ]
: classpath.trim().split( " +" );
}
}
/**
* Determines whether a given file is named in a collection of filenames.
*
* @param fnameSet collection of Strings, each which may be the
* trailing part of a canonical filename
* @param file file to test
* @return true iff file
is named in fnameSet
*/
private static boolean containsFilename( Collection fnameSet,
File file )
throws IOException {
String cname = file.getCanonicalPath();
for ( String excl : fnameSet ) {
if ( cname.equals( excl ) ||
cname.endsWith( File.separator + excl ) ) {
return true;
}
}
return false;
}
/**
* Determines whether a given file is one of a collection of filenames.
*
* @param fileSet collection of Files, may be relative
* @param file file to test
* @param true iff file
is listed in fileSet
*/
private static boolean containsFile( Collection fileSet, File file )
throws IOException {
String cname = file.getCanonicalPath();
for ( File f : fileSet ) {
if ( f.getCanonicalPath().equals( cname ) ) {
return true;
}
}
return false;
}
/**
* Writes a new jar or zip file based on the contents of an existing
* jar file and the jar files referenced by its manifest.
*
* Usage:
*
* SuperJar [-oj outjar] [-oz outzip]
* [[-xjar jar] -xjar jar ...]
* [[-xentry entry] -xentry entry ...]
* jarfile [jarfile ...]
*
*
* If the -oj flag is given, it supplies the name of
* a monolithic jar file to output.
* -o is a deprecated synonym for -oj .
*
*
If the -oz flag is given, it supplies the name of a zip
* file to output. This zip file will contain all the named and
* referenced jar files in a single flat directory.
*
*
The -xjar * flag may be supplied one or more times to define
* jarfiles which should not be included, even if they are referenced
* in the manifest's Class-Path entry of a jar file which is included.
* The exclude argument thus defined is the name, optionally
* with one or more prepended path elements of the jar file to be
* excluded (e.g. axis.jar or axis/axis.jar would both work).
* -x is a deprecated synonym for -xjar .
*
*
The -xent flag may be supplied one or more times to give
* full paths for jar entries which should not be included in the output.
* These are interpreted as follows:
*
* ending in "/": exclude that directory and all children
* ending in "/*": exclude all flat files, but not subdirectories
* of that directory
* otherwise: exclude the flat file with that exact name
*
*
* The jarfile argument(s) will be combined to form
* the output file, all their contents and those of the jar files
* referenced in their Class-Path manifest entries will be used.
* The manifest of the first one will be used as the manifest of
* the output file (though its Class-Path entry will be empty).
* Zip files can be used as well, they work the same but have no
* manifest.
*
*
Any flat-file arguments will be included as files
* at the top level of the output jar or zip file.
*
* @param args an array of command-line arguments as described above
*/
public static void main( String[] args ) throws IOException {
String usage = "SuperJar [-oj out-jar] [-oz out-zip]\n"
+ " [-xjar jar [-xjar jar] ..]\n"
+ " [-xent entry [-xent entry] ..]\n"
+ " [-file flatfile [-file flatfile] ..]\n"
+ " jarfile [jarfile ..]";
/* Process arguments. */
List arglist = new ArrayList( Arrays.asList( args ) );
List jarlist = new ArrayList();
List flatFileList = new ArrayList();
File outJar = null;
File outZip = null;
List jarExcludeList = new ArrayList();
List entryExcludeList = new ArrayList();
for ( Iterator it = arglist.iterator(); it.hasNext(); ) {
String arg = it.next();
if ( arg.startsWith( "-h" ) ) {
System.err.println( usage );
System.exit( 0 );
}
else if ( arg.equals( "-oj" ) ||
arg.equals( "-o" ) ) {
it.remove();
outJar = new File( it.next() );
it.remove();
}
else if ( arg.equals( "-oz" ) ) {
it.remove();
outZip = new File( it.next() );
it.remove();
}
else if ( arg.equals( "-x" ) ||
arg.equals( "-xjar" ) ) {
it.remove();
jarExcludeList.add( it.next() );
it.remove();
}
else if ( arg.equals( "-xent" ) ) {
it.remove();
entryExcludeList.add( it.next() );
it.remove();
}
else if ( arg.equals( "-file" ) ) {
it.remove();
flatFileList.add( new File( it.next() ) );
it.remove();
}
else {
jarlist.add( new File( arg ) );
it.remove();
}
}
if ( jarlist.size() == 0 ) {
System.err.println( usage );
System.exit( 1 );
}
File[] jarFiles = jarlist.toArray( new File[ 0 ] );
File[] flatFiles = flatFileList.toArray( new File[ 0 ] );
String[] jarExcludes = jarExcludeList.toArray( new String[ 0 ] );
String[] entryExcludes = entryExcludeList.toArray( new String[ 0 ] );
/* Construct the writer. */
SuperJar sj =
new SuperJar( jarFiles, flatFiles, jarExcludes, entryExcludes );
/* Warn if no output. */
if ( outJar == null && outZip == null ) {
System.err.println( "No output requested (use -oj or -oz)" );
}
/* Write a monolithic jar file if required. */
if ( outJar != null ) {
System.err.println( "Writing monolithic jar file: " + outJar );
OutputStream jout =
new BufferedOutputStream( new FileOutputStream( outJar ) );
sj.writeSingleJar( jout );
jout.close();
}
/* Write a monolithic zip file if required. */
if ( outZip != null ) {
System.err.println( "Writing zip of jars: " + outZip );
OutputStream zout =
new BufferedOutputStream( new FileOutputStream( outZip ) );
sj.writeZipOfJars( zout );
zout.close();
}
}
}