Jar Class

package javaxt.io;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;

//Imports for dealing with ZIP files
import java.net.URI;
import java.util.zip.*;

//******************************************************************************
//**  Jar Class
//******************************************************************************
/**
 *   Used to find entries in a jar file associated with a given class or
 *   package.
 *
 *   The original motivation behind this class was to support a requirement to
 *   extract and update config files stored in Java packages. For console apps,
 *   the config file is stored in the jar (zip) file. For web apps, chances are
 *   that the package has been un-zipped and the config file is laying around
 *   on disk. This class was designed to support both use cases.
 *
 ******************************************************************************/

public class Jar {

    private java.io.File file;
    private java.lang.Package Package;


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using an instance of another class.
   */
    public Jar(java.lang.Object object){
        this(object.getClass());
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using a Class.
   */
    public Jar(java.lang.Class Class) {
        this.Package = Class.getPackage();
        String path = Class.getName();
        if (path!=null) path = path.replace((CharSequence)".",(CharSequence)"/") + ".class";
        java.util.ArrayList<java.net.URL> urls = getResource(path, Class.getClassLoader());
        if (urls.size()==1){
            file = getFile(urls.get(0));
        }
        /*
        else if (urls.size()>1){ //Should never happen!

            for (java.net.URL url : urls){

                java.net.JarURLConnection urlcon = (java.net.JarURLConnection) (url.openConnection());
                java.util.jar.JarFile jar = urlcon.getJarFile();
                java.util.Enumeration<java.util.jar.JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    String entry = entries.nextElement().getName();
                    //System.out.println(entry);
                }
            }
        }
        */
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using a Package. This method is
   *  somewhat unreliable if there are multiple jar files that contain the
   *  same package name. Suggest using the getJars() method instead.
   */
    public Jar(java.lang.Package Package) {
        this.Package = Package;
        String path = Package.getName().replace((CharSequence)".",(CharSequence)"/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (java.net.URL url : getResource(path, classLoader)){
            file = getFile(url);
            if (file!=null) break;
        }
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using a path to a jar file or
   *  directory.
   */
    public Jar(java.io.File file){
        this.file = file;
    }


  //**************************************************************************
  //** getJars
  //**************************************************************************
  /** Returns an array of files or directories associated with a given Package.
   *  This method should be used instead of new Jar(java.lang.Package).
   */
    public static Jar[] getJars(java.lang.Package Package){
        return getJars(Package.getName());
    }

    public static Jar[] getJars(String packageName){
        java.util.ArrayList<Jar> jars = new java.util.ArrayList<Jar>();
        String path = packageName.replace((CharSequence)".",(CharSequence)"/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (java.net.URL url : getResource(path, classLoader)){
            java.io.File file = getFile(url);
            if (file!=null) jars.add(new Jar(file));
        }
        return jars.toArray(new Jar[jars.size()]);
    }

  //**************************************************************************
  //** getFile
  //**************************************************************************
  /** Returns a java.io.File representation of the jar file or directory where
   *  the jar file has been extracted.
   */
    public java.io.File getFile(){
        return file;
    }


  //**************************************************************************
  //** getManifest
  //**************************************************************************
  /**  Returns the Manifest file found in the "META-INF" directory. The
   *   Manifest file contains metadata for the jar file including version
   *   numbers, vendor name, etc. You can loop through properties in the
   *   Manifest like this:
   <pre>
    java.io.File file = new java.io.File("/Drivers/h2/h2-1.3.162.jar");
    java.util.jar.JarFile jar = new javaxt.io.Jar(file);
    java.util.jar.Manifest manifest = jar.getManifest();

    System.out.println("\r\nMain Attributes:\r\n--------------------------");
    printAttributes(manifest.getMainAttributes());


    System.out.println("\r\nOther Attributes:\r\n--------------------------");
    java.util.Map<String, java.util.jar.Attributes> entries = manifest.getEntries();
    java.util.Iterator<String> it = entries.keySet().iterator();
    while (it.hasNext()){
        String key = it.next();
        printAttributes(entries.get(key));
        System.out.println();
    }

    jar.close();

    private static void printAttributes(java.util.jar.Attributes attributes){
        java.util.Iterator it = attributes.keySet().iterator();
        while (it.hasNext()){
            java.util.jar.Attributes.Name key = (java.util.jar.Attributes.Name) it.next();
            Object value = attributes.get(key);
            System.out.println(key + ":  " + value);
        }
    }
   </pre>
   */
    public java.util.jar.Manifest getManifest(){
        try{
            Entry entry = this.getEntry("META-INF", "MANIFEST.MF");
            if (entry!=null) {
                ByteArrayInputStream is = new ByteArrayInputStream(entry.getBytes());
                java.util.jar.Manifest manifest = new java.util.jar.Manifest(is);
                is.close();
                return manifest;
            }
        }
        catch(Exception e){
        }
        return null;
    }


  //**************************************************************************
  //** getVersion
  //**************************************************************************
  /** Returns the version number of the jar file, if available. Two different
   *  strategies are used to find the version number. First strategy is to
   *  parse the jar file manifest and return the value of the
   *  "Implementation-Version" or "Bundle-Version", whichever is found first.
   *  If no version information is found in the manifest, an attempt is made
   *  to parse the file name. Returns a null is no version information is
   *  available.
   */
    public String getVersion(){

        java.util.jar.Attributes attributes = getManifest().getMainAttributes();
        if (attributes!=null){
            java.util.Iterator it = attributes.keySet().iterator();
            while (it.hasNext()){
                java.util.jar.Attributes.Name key = (java.util.jar.Attributes.Name) it.next();
                String keyword = key.toString();
                if (keyword.equals("Implementation-Version") || keyword.equals("Bundle-Version")){
                    return (String) attributes.get(key);
                }
            }
        }

        String fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
        if (fileName.contains(".")){
            String majorVersion = fileName.substring(0, fileName.indexOf("."));
            int delimiter = majorVersion.lastIndexOf("-");
            if (majorVersion.indexOf("_")>delimiter) delimiter = majorVersion.indexOf("_");
            majorVersion = majorVersion.substring(delimiter+1, fileName.indexOf("."));
            String minorVersion = fileName.substring(fileName.indexOf("."));
            return majorVersion + minorVersion;
        }

        return null;
    }


  //**************************************************************************
  //** getEntries
  //**************************************************************************
  /**  Used to return a list of all the entries found in the jar file.
   */
    public Entry[] getEntries(){
        java.util.ArrayList<Entry> entries = new java.util.ArrayList<Entry>();
        try{

            if (file.isDirectory()){
                Directory dir = new Directory(file);
                java.util.List items = dir.getChildren(true);
                for (int i=0; i<items.size(); i++){
                     Object item = items.get(i);
                     if (item instanceof File){
                         entries.add(new Entry(((File) item).toFile()));
                     }
                }
            }
            else{
                ZipInputStream in = new ZipInputStream(new FileInputStream(file));
                ZipEntry zipEntry = null;
                while((zipEntry = in.getNextEntry())!=null){
                    entries.add(new Entry(zipEntry));
                }
                in.close();
            }
        }
        catch(Exception e){
        }
        return entries.toArray(new Entry[entries.size()]);
    }


  //**************************************************************************
  //** getEntry
  //**************************************************************************
  /**  Used to retrieve a single entry from the jar file. */

    public Entry getEntry(String Entry){
        return getEntry(Package.getName(),Entry);
    }


  //**************************************************************************
  //** getEntry
  //**************************************************************************
  /**  Used to retrieve a single entry from the jar file.
   *  @param Package Name of the package or directory in the jar file
   *  (e.g. "javaxt.io"). Null values and zero length strings default to the
   *  the root directory.
   *  @param Entry Name of the class/file found in the given package
   *  (e.g. "Jar.class").
   */
    public Entry getEntry(String Package, String Entry){

        ZipInputStream in = null;
        try{

            if (file.isDirectory()){
                return new Entry(new java.io.File(file, Entry));
            }
            else{

              //Update package name and entry
                if (Package!=null){
                    Package = Package.trim();
                    if (Package.length()==0) Package = null;
                }
                if (Package!=null){
                    if (Package.contains(".")) Package = Package.replace(".","/");
                    Entry = Package + "/" + Entry;
                }


              //Find entry in the jar file
                in = new ZipInputStream(new FileInputStream(file));
                ZipEntry zipEntry = null;
                while((zipEntry = in.getNextEntry())!=null){
                    if (zipEntry.getName().equalsIgnoreCase(Entry)){
                        //System.out.println(zipEntry.getName() + " <--");
                        Entry entry = new Entry(zipEntry);
                        in.close();
                        return entry;
                    }
                }
                in.close();
            }
        }
        catch(Exception e){
            if (in!=null){
                try{ in.close(); }
                catch(Exception ex){}
            }
            //e.printStackTrace();
        }

        return null;
    }


  //**************************************************************************
  //** getEntries
  //**************************************************************************
  /**  Used to retrieve a single entry from the jar file. */

    public Entry getEntry(java.lang.Class Class) {
        String ClassName = Class.getName();
        String PackageName = Class.getPackage().getName();
        ClassName = ClassName.substring(PackageName.length()+1);
        return getEntry(PackageName,ClassName+".class");
    }


  //**************************************************************************
  //** getClasses
  //**************************************************************************
  /** Returns all the classes in the jar file. Returns an empty array if the
   *  jar file has not been loaded or if there are no classes in the file.
   */
    public Class[] getClasses(){
        java.util.ArrayList<Class> classes = new java.util.ArrayList<Class>();
        java.net.URLClassLoader child = null;
        for (Jar.Entry entry : getEntries()){
            String name = entry.getName();
            if (name.endsWith(".class")){
                name = name.substring(0, name.length()-6).replace("/", ".");
                try{
                    classes.add(Class.forName(name));
                }
                catch(Exception e){
                    try{
                        if (child==null){
                            child = new java.net.URLClassLoader(
                                new java.net.URL[] {file.toURI().toURL()},
                                this.getClass().getClassLoader()
                            );
                        }
                        classes.add(Class.forName(name, true, child));
                    }
                    catch(Exception e2){
                        //e2.printStackTrace();
                    }
                }
            }
        }
        return classes.toArray(new Class[classes.size()]);
    }


  //**************************************************************************
  //** toString
  //**************************************************************************
  /**  Returns the path to the jar file. */

    public String toString(){
        return file.toString();
    }


  //**************************************************************************
  //** getResource
  //**************************************************************************
  /** Returns the URL associated with a given path in the jar file.
   *  @param cl ClassLoader used to find the given path/resource. If the
   *  ClassLoader is null or fails to find the requested path, an alternate
   *  ClassLoader is used (e.g. ClassLoader's Parent, ContextClassLoader,
   *  SystemClassLoader).
   */
    private static java.util.ArrayList<java.net.URL> getResource(String path, ClassLoader cl){

        java.util.ArrayList<java.net.URL> arr = new java.util.ArrayList<java.net.URL>();
        try{

            int x = 0;
            while (true){

                if (cl==null){
                    cl = Thread.currentThread().getContextClassLoader();
                    if (cl==null){
                        cl = java.lang.ClassLoader.getSystemClassLoader();
                        if (cl==null){
                            break; //Throw Exception?
                        }
                    }
                }


                java.util.Enumeration<java.net.URL> en = cl.getResources(path);
                if (en.hasMoreElements()){
                    while (en.hasMoreElements()){
                        java.net.URL url = en.nextElement();
                        arr.add(url);
                    }

                    break;
                }
                else{
                    if (cl.getParent()!=null && cl.getParent().equals(cl)){
                        break;
                    }
                    cl = cl.getParent();
                }



                x++;
                if (x==50) break;
            }

            /*
            String debug = "";
            debug += "Path: " + path + "\r\n";
            debug += "ClassLoader: " + cl + "\r\n";
            for (java.net.URL url : arr){
                debug += "Resource: " + url + "\r\n";
            }
            System.out.println(debug);
            */

        }
        catch(Exception e){
            //e.printStackTrace();
        }

        return arr;
    }


    private static java.io.File getFile(java.net.URL url){
        if (url!=null)
        try{
            java.net.URI uri = new java.net.URI(url.toString().replace((CharSequence)" ",(CharSequence)"%20"));
            if (uri.getPath()==null){
                String path = uri.toString();
                if (path.startsWith("jar:file:")){

                  //Update Path and Define Zipped File
                    path = path.substring(path.indexOf("file:/"));
                    path = path.substring(0,path.toLowerCase().indexOf(".jar")+4);

                    if (path.startsWith("file://")){ //UNC Path
                        path = "C:/" + path.substring(path.indexOf("file:/")+7);
                        path = "/" + new URI(path).getPath();
                    }
                    else{
                        path = new URI(path).getPath();
                    }
                    return new java.io.File(path);
                }
                else if (path.startsWith("jar:http")){
                    path = path.substring(path.indexOf("http"));
                    path = path.substring(0,path.toLowerCase().indexOf(".jar")+4);

                }
            }
            else{
                return new java.io.File(uri);
            }
        }
        catch(Exception e){
            //e.printStackTrace();
        }

        return null;
    }


  //**************************************************************************
  //** JAR Entry Class
  //**************************************************************************
  /** Used to represent an entry in a jar/war file. The jar file might be
   *  zipped or unpacked by a web server.
   */
    public class Entry{
        private ZipEntry zipEntry = null;
        private java.io.File fileEntry = null;

      /** Constructor for zipped jar files. */
        private Entry(ZipEntry zipEntry){
            this.zipEntry = zipEntry;
        }

      /** Constructor for unzipped jar files. */
        private Entry(java.io.File fileEntry){
            this.fileEntry = fileEntry;
        }

        public String getName(){
            if (fileEntry==null) return zipEntry.getName();
            else return fileEntry.getName();
        }

        public java.util.Date getDate(){
            if (fileEntry==null) return new java.util.Date(zipEntry.getTime());
            else return new java.util.Date(fileEntry.lastModified());
        }


      /** Returns a long value representing a cyclic redundancy check
       * (CRC-32 checksum) of the uncompressed entry data, or -1 if not known.
       */
        public long checksum(){
            if (fileEntry==null) return zipEntry.getCrc();
            else return new javaxt.io.File(fileEntry).checksum();
        }


        public java.io.File getFile(){
            return fileEntry;
        }

        public long getSize(){
            if (fileEntry==null){
                return zipEntry.getSize();
            }
            else{
                return fileEntry.length();
            }
        }


        public byte[] getBytes(){
            ZipFile zip = null;
            try{

                if (fileEntry==null){
                    zip = new ZipFile(file);
                    java.io.DataInputStream is = new java.io.DataInputStream(zip.getInputStream(zipEntry));

                    int bufferSize = 1024;
                    ByteArrayOutputStream bas = new ByteArrayOutputStream();
                    byte[] b = new byte[bufferSize];
                    int x=0;
                    while((x=is.read(b,0,bufferSize))>-1) {
                        bas.write(b,0,x);
                    }
                    bas.close();

                    zip.close();
                    return bas.toByteArray();
                }
                else{
                    return new javaxt.io.File(fileEntry).getBytes().toByteArray();
                }
            }
            catch(Exception e){
                if (zip!=null){
                    try{ zip.close(); }
                    catch(Exception ex){}
                }
                return null;
            }
        }


      /** Used to extract the zip entry to a file. */
        public void extractFile(java.io.File destination){
            try{
                if (fileEntry==null){
                    destination.getParentFile().mkdirs();
                    FileOutputStream out = new FileOutputStream(destination);
                    ZipInputStream in = new ZipInputStream(new FileInputStream(file));
                    ZipEntry zipEntry = null;
                    while((zipEntry = in.getNextEntry())!=null){
                        if (zipEntry.getName().equals(this.zipEntry.getName())){

                            byte[] buf = new byte[1024];
                            int len;
                            while ((len = in.read(buf)) > 0) {
                                out.write(buf, 0, len);
                            }
                            break;
                        }
                    }
                    in.close();
                    out.close();
                }
                else{

                  //Simply copy the file to the destination
                    if (destination.isFile()){
                        new File(fileEntry).copyTo(new File(destination),false);
                    }
                    else{
                        new File(fileEntry).copyTo(new Directory(destination),false);
                    }
                }
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }


        public void setText(String text){
            try{
                if (fileEntry==null){
                    ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
                    ByteArrayInputStream byteInput = new ByteArrayInputStream(text.getBytes());
                    ZipOutputStream zipOutput = new ZipOutputStream(byteOutput);
                    ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file));


                    ZipEntry zipEntry = null;
                    while((zipEntry = zipInput.getNextEntry())!=null){

                        if (zipEntry.getName().equals(this.zipEntry.getName())){

                          //Write Updated Config File
                            zipOutput.putNextEntry(new ZipEntry(this.zipEntry.getName()));
                            byte[] buf = new byte[1024];
                            int len;
                            while ((len = byteInput.read(buf)) > 0) {
                                zipOutput.write(buf, 0, len);
                            }
                            byteInput.close();
                        }
                        else{
                            zipOutput.putNextEntry(zipEntry);
                            byte[] buf = new byte[1024];
                            int len;
                            while ((len = zipInput.read(buf)) > 0) {
                                zipOutput.write(buf, 0, len);
                            }
                        }

                        zipInput.closeEntry();
                        zipOutput.closeEntry();
                    }

                    zipInput.close();
                    zipOutput.close();


                    FileOutputStream fos = new FileOutputStream(file);
                    fos.write(byteOutput.toByteArray());
                    fos.close();

                    byteOutput.close();

                }
                else{
                    new File(fileEntry).write(text);
                }
            }
            catch(Exception e){
            }
        }

      /** Used to extract the contents to a string. */
        public String getText(){
            return getText("UTF-8");
        }

      /** Used to extract the contents to a string. Returns null if the
       *  extraction failed.
       *  @param charsetName Name of the character encoding used to read the
       *  file. Examples include UTF-8 and ISO-8859-1
       */
        public String getText(String charsetName){
            try{
                if (fileEntry==null){
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    ZipInputStream in = new ZipInputStream(new FileInputStream(file));
                    ZipEntry zipEntry = null;
                    while((zipEntry = in.getNextEntry())!=null){
                        if (zipEntry.getName().equals(this.zipEntry.getName())){

                            byte[] buf = new byte[1024];
                            int len;
                            while ((len = in.read(buf)) > 0) {
                                out.write(buf, 0, len);
                            }
                            break;
                        }
                    }
                    in.close();
                    return out.toString(charsetName);
                }
                else{
                    return new File(fileEntry).getText(charsetName);
                }
            }
            catch(Exception e){
                return null;
            }

        }

        public String toString(){
            return getName();
        }
    }
}