/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/** A utility class for dealing with Jar files.
@author Scott.Stark@jboss.org
@version $Revision: 2787 $
*/
public final class JarUtils
{
/**
* Hide the constructor
*/
private JarUtils()
{
}
/**
* This function will create a Jar archive containing the src
* file/directory. The archive will be written to the specified
* OutputStream.
*
* This is a shortcut for
* jar(out, new File[] { src }, null, null, null);
*
* @param out The output stream to which the generated Jar archive is
* written.
* @param src The file or directory to jar up. Directories will be
* processed recursively.
* @throws IOException
*/
public static void jar(OutputStream out, File src) throws IOException
{
jar(out, new File[] { src }, null, null, null);
}
/**
* This function will create a Jar archive containing the src
* file/directory. The archive will be written to the specified
* OutputStream.
*
* This is a shortcut for
* jar(out, src, null, null, null);
*
* @param out The output stream to which the generated Jar archive is
* written.
* @param src The file or directory to jar up. Directories will be
* processed recursively.
* @throws IOException
*/
public static void jar(OutputStream out, File[] src) throws IOException
{
jar(out, src, null, null, null);
}
/**
* This function will create a Jar archive containing the src
* file/directory. The archive will be written to the specified
* OutputStream. Directories are processed recursively, applying the
* specified filter if it exists.
*
*
This is a shortcut for
* jar(out, src, filter, null, null);
*
* @param out The output stream to which the generated Jar archive is
* written.
* @param src The file or directory to jar up. Directories will be
* processed recursively.
* @param filter The filter to use while processing directories. Only
* those files matching will be included in the jar archive. If
* null, then all files are included.
* @throws IOException
*/
public static void jar(OutputStream out, File[] src, FileFilter filter)
throws IOException
{
jar(out, src, filter, null, null);
}
/**
* This function will create a Jar archive containing the src
* file/directory. The archive will be written to the specified
* OutputStream. Directories are processed recursively, applying the
* specified filter if it exists.
*
* @param out The output stream to which the generated Jar archive is
* written.
* @param src The file or directory to jar up. Directories will be
* processed recursively.
* @param filter The filter to use while processing directories. Only
* those files matching will be included in the jar archive. If
* null, then all files are included.
* @param prefix The name of an arbitrary directory that will precede all
* entries in the jar archive. If null, then no prefix will be
* used.
* @param man The manifest to use for the Jar archive. If null, then no
* manifest will be included.
* @throws IOException
*/
public static void jar(OutputStream out, File[] src, FileFilter filter,
String prefix, Manifest man) throws IOException
{
for (int i = 0; i < src.length; i++)
{
if (!src[i].exists())
{
throw new FileNotFoundException(src.toString());
}
}
JarOutputStream jout;
if (man == null)
{
jout = new JarOutputStream(out);
}
else
{
jout = new JarOutputStream(out, man);
}
if (prefix != null && prefix.length() > 0 && !prefix.equals("/"))
{
// strip leading '/'
if (prefix.charAt(0) == '/')
{
prefix = prefix.substring(1);
}
// ensure trailing '/'
if (prefix.charAt(prefix.length() - 1) != '/')
{
prefix = prefix + "/";
}
}
else
{
prefix = "";
}
JarInfo info = new JarInfo(jout, filter);
for (int i = 0; i < src.length; i++)
{
jar(src[i], prefix, info);
}
jout.close();
}
/**
* This simple convenience class is used by the jar method to reduce the
* number of arguments needed. It holds all non-changing attributes
* needed for the recursive jar method.
*/
private static class JarInfo
{
public JarOutputStream out;
public FileFilter filter;
public byte[] buffer;
public JarInfo(JarOutputStream out, FileFilter filter)
{
this.out = out;
this.filter = filter;
buffer = new byte[1024];
}
}
/**
* This recursive method writes all matching files and directories to
* the jar output stream.
*/
private static void jar(File src, String prefix, JarInfo info)
throws IOException
{
JarOutputStream jout = info.out;
if (src.isDirectory())
{
// create / init the zip entry
prefix = prefix + src.getName() + "/";
ZipEntry entry = new ZipEntry(prefix);
entry.setTime(src.lastModified());
entry.setMethod(JarOutputStream.STORED);
entry.setSize(0L);
entry.setCrc(0L);
jout.putNextEntry(entry);
jout.closeEntry();
// process the sub-directories
File[] files = src.listFiles(info.filter);
for (int i = 0; i < files.length; i++)
{
jar(files[i], prefix, info);
}
}
else if (src.isFile())
{
// get the required info objects
byte[] buffer = info.buffer;
// create / init the zip entry
ZipEntry entry = new ZipEntry(prefix + src.getName());
entry.setTime(src.lastModified());
jout.putNextEntry(entry);
// dump the file
FileInputStream in = new FileInputStream(src);
int len;
while ((len = in.read(buffer, 0, buffer.length)) != -1)
{
jout.write(buffer, 0, len);
}
in.close();
jout.closeEntry();
}
}
public static void unjar(InputStream in, File dest) throws IOException
{
if (!dest.exists())
{
dest.mkdirs();
}
if (!dest.isDirectory())
{
throw new IOException("Destination must be a directory.");
}
JarInputStream jin = new JarInputStream(in);
byte[] buffer = new byte[1024];
ZipEntry entry = jin.getNextEntry();
while (entry != null)
{
String fileName = entry.getName();
if (fileName.charAt(fileName.length() - 1) == '/')
{
fileName = fileName.substring(0, fileName.length() - 1);
}
if (fileName.charAt(0) == '/')
{
fileName = fileName.substring(1);
}
if (File.separatorChar != '/')
{
fileName = fileName.replace('/', File.separatorChar);
}
File file = new File(dest, fileName);
if (entry.isDirectory())
{
// make sure the directory exists
file.mkdirs();
jin.closeEntry();
}
else
{
// make sure the directory exists
File parent = file.getParentFile();
if (parent != null && !parent.exists())
{
parent.mkdirs();
}
// dump the file
OutputStream out = new FileOutputStream(file);
int len = 0;
while ((len = jin.read(buffer, 0, buffer.length)) != -1)
{
out.write(buffer, 0, len);
}
out.flush();
out.close();
jin.closeEntry();
file.setLastModified(entry.getTime());
}
entry = jin.getNextEntry();
}
/* Explicity write out the META-INF/MANIFEST.MF so that any headers such
as the Class-Path are see for the unpackaged jar
*/
Manifest mf = jin.getManifest();
if (mf != null)
{
File file = new File(dest, "META-INF/MANIFEST.MF");
File parent = file.getParentFile();
if( parent.exists() == false )
{
parent.mkdirs();
}
OutputStream out = new FileOutputStream(file);
mf.write(out);
out.flush();
out.close();
}
jin.close();
}
/** Given a URL check if its a jar url(jar:!/archive) and if it is,
extract the archive entry into the given dest directory and return a file
URL to its location. If jarURL is not a jar url then it is simply returned
as the URL for the jar.
@param jarURL the URL to validate and extract the referenced entry if its
a jar protocol URL
@param dest the directory into which the nested jar will be extracted.
@return the file: URL for the jar referenced by the jarURL parameter.
* @throws IOException
*/
public static URL extractNestedJar(URL jarURL, File dest)
throws IOException
{
// This may not be a jar URL so validate the protocol
if( jarURL.getProtocol().equals("jar") == false )
return jarURL;
String destPath = dest.getAbsolutePath();
URLConnection urlConn = jarURL.openConnection();
JarURLConnection jarConn = (JarURLConnection) urlConn;
// Extract the archive to dest/jarName-contents/archive
String parentArchiveName = jarConn.getJarFile().getName();
// Find the longest common prefix between destPath and parentArchiveName
int length = Math.min(destPath.length(), parentArchiveName.length());
int n = 0;
while( n < length )
{
char a = destPath.charAt(n);
char b = parentArchiveName.charAt(n);
if( a != b )
break;
n ++;
}
// Remove any common prefix from parentArchiveName
parentArchiveName = parentArchiveName.substring(n);
File archiveDir = new File(dest, parentArchiveName+"-contents");
if( archiveDir.exists() == false && archiveDir.mkdirs() == false )
throw new IOException("Failed to create contents directory for archive, path="+archiveDir.getAbsolutePath());
String archiveName = jarConn.getEntryName();
File archiveFile = new File(archiveDir, archiveName);
File archiveParentDir = archiveFile.getParentFile();
if( archiveParentDir.exists() == false && archiveParentDir.mkdirs() == false )
throw new IOException("Failed to create parent directory for archive, path="+archiveParentDir.getAbsolutePath());
InputStream archiveIS = jarConn.getInputStream();
FileOutputStream fos = new FileOutputStream(archiveFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[4096];
int read;
while( (read = archiveIS.read(buffer)) > 0 )
{
bos.write(buffer, 0, read);
}
archiveIS.close();
bos.close();
// Return the file url to the extracted jar
return archiveFile.toURL();
}
public static void main(String[] args) throws Exception
{
if (args.length == 0)
{
System.out.println("usage: ");
System.exit(0);
}
if (args[0].equals("x"))
{
BufferedInputStream in = new BufferedInputStream(new FileInputStream(args[1]));
File dest = new File(args[2]);
unjar(in, dest);
}
else if (args[0].equals("c"))
{
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(args[1]));
File[] src = new File[args.length - 2];
for (int i = 0; i < src.length; i++)
{
src[i] = new File(args[2 + i]);
}
jar(out, src);
}
else
{
System.out.println("Need x or c as first argument");
}
}
}