JarUtils.java Source code

Java tutorial

Introduction

Here is the source code for JarUtils.java

Source

/*
  * 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() {
    }

    /**
     * <P>This function will create a Jar archive containing the src
     * file/directory.  The archive will be written to the specified
     * OutputStream.</P>
     *
     * <P>This is a shortcut for<br>
     * <code>jar(out, new File[] { src }, null, null, null);</code></P>
     *
     * @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);
    }

    /**
     * <P>This function will create a Jar archive containing the src
     * file/directory.  The archive will be written to the specified
     * OutputStream.</P>
     *
     * <P>This is a shortcut for<br>
     * <code>jar(out, src, null, null, null);</code></P>
     *
     * @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);
    }

    /**
     * <P>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.
     *
     * <P>This is a shortcut for<br>
     * <code>jar(out, src, filter, null, null);</code></P>
     *
     * @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);
    }

    /**
     * <P>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:<url>!/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: <x or c> <jar-archive> <files...>");
            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");
        }
    }
}