//  JARHandler.java
//
//  Modified by Vijayram Arthanari on Wed Oct 15 2003.
//  Modified to load and run the Task-Thread
//  from the jar file specified
//  Removed the code using SOAP Connection to fetch the jar file
//
//  (Originally) GridConnectionClassLoader.java
//
//  Created by Anthony Karre on Wed Dec 11 2002.
//  Copyright (c) 2002 Perficient. All rights reserved, except for Ken McCrary stuff.
//
//  Structure of this class was kick-started by published Ken McCrary examples:
//  Here is his copyright notice:
//
/* Copyright (c) 1999 Ken McCrary, All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies.
*
* KEN MCCRARY MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
* SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. KEN MCCRARY
* SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT
* OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*/

import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.zip.* ;
import java.util.jar.Manifest;
import java.util.jar.JarInputStream ;
import java.util.jar.JarFile ;
import java.util.jar.JarEntry ;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.Hashtable ;
import java.io.* ;
import java.net.URL ;

public class JARHandler extends ClassLoader {

  // Provide normal constructor.
  
  JARHandler() {
    super();
    init();
  }
  
  // Provide delegation constructor.

  JARHandler(ClassLoader parent) {
    super(parent);
    init();
  }

  JARHandler(String filename){
	super();
	jarfilename=filename;
	init();
  }

  // ***********************************************************************
  // Initialize the ClassLoader by using Web services (SOAP) to fetch our JAR.
  // This jar file will contain the classes that compose our grid task.
  // **************************************************************************
  
  private void init() {
  
    byte[] localjarbytes = null ;
    FileInputStream tempfis = null ;
    JarInputStream jis = null ;
      
    try {

      // Save the filename for later reference.
      
      //jarfilename = new String(tempfile.getCanonicalPath()) ;
      tempfile = new File(jarfilename) ;
      
      // Try to get a manifest, if one exists.  This will
      // let us know what the main task thread class is.
      // The main task thread is the primary work unit
      // that we will be executing in our grid.
        
      tempfis = new FileInputStream(tempfile) ;
      jis = new JarInputStream(tempfis) ;
            
      manifest = jis.getManifest() ;
      Attributes attr = manifest.getMainAttributes();
            
      if (attr != null) {
        taskthread = attr.getValue("Task-Thread") ;
      }
        
      // Now scan all of the entries in the file and
      // get the size of each resource/class.  This is the
      // only way to get the true size when the JAR is compressed.
        
      ZipFile zipfile = new ZipFile(tempfile);
      enum = zipfile.entries() ;
                            
      while (enum.hasMoreElements()) {          
        ZipEntry ze = (ZipEntry) enum.nextElement() ;
        htSizes.put(ze.getName(), new Integer((int)ze.getSize())) ;
      }

    } // Try.
      
    catch (Exception e) {
      System.out.println("Class Loader: " + e.toString() + " : " + e.getMessage()) ;
    }
    finally {
      if ( tempfis != null ) {
        try {
          tempfis.close();
        }
        catch (Exception e){}
      }
      if ( jis != null ) {
        try {
          jis.close();
        }
        catch (Exception e){}
      }
        
    } // Finally.
        
  }
  /**
   *  This is the method where the task of classloading
   *  is delegated to our custom loader.
   *
   * @param  name the name of the class
   * @return the resulting Class object
   * @exception ClassNotFoundException if the class could not be found
   */
  
  protected Class findClass(String name) throws ClassNotFoundException {
  
    FileInputStream fis = null ;
    ZipInputStream zis = null ;
  
    try {
    
      String path = name.replace('.', '/') + ".class" ;
      fis = new FileInputStream(tempfile) ;
      zis = new ZipInputStream(fis) ;
      ZipEntry ze = null ;
      boolean classfound = false ;
      
      while ((ze = zis.getNextEntry()) != null) {
      
        if (path.equals(ze.getName())) {
        
          classfound = true ;
        
          // Get the size of the classfile so we know how many bytes
          // to read.  If the size appears to be -1, then the
          // jar file is compressed, and we need to consult our
          // hashtable to get the uncompressed size.
          
          int size = (int) ze.getSize() ;
          
          if (size == -1) {
            size = ((Integer) htSizes.get(path)).intValue() ;
          }
          
          // Now that we know the actual size of the class,
          // fetch the data.
          
          byte[] classBytes = new byte[size] ;
          int rb = 0 ;
          int chunk = 0 ;
          
          while ((size - rb) > 0) {
            chunk = zis.read(classBytes, rb, size - rb) ;
            if (chunk == -1) {break ;}
            rb += chunk ;
          }
          
          // Now that we have our class data, define the class.
          
          System.out.println ("Class Loader: Found class " + name) ;
          definePackage(name);
          return defineClass(name, classBytes, 0, classBytes.length);

        }
        
      }
      
      if (!classfound) {
        // We apparently failed to locate the class within the jar file,
        // so indicate the problem with an exception.
        
        throw new ClassNotFoundException(name) ;
      }
      
    }
    catch (Exception e) {
      // We could not find the class, so indicate the problem with an exception.
      throw new ClassNotFoundException(name);
    }
    finally {
      if ( fis != null ) {
        try {
          fis.close();
        }
        catch (Exception e){}
      }
      if ( zis != null ) {
        try {
          zis.close();
        }
        catch (Exception e){}
      }
    }
    
    return null ;
  }
  /**
   *  Identify where to load a resource from.
   *  Normally the resources will be extracted from a JAR
   *  and reside in a filesystem somewhere.  Since
   *  we are loading our classes directly from a jar file,
   *  they won't really be available via URL.
   *
   *  We'll always return a null to indicate
   *  that the resource can't be found, i.e., unavailable.
   *  You could also dump all of the resources into a
   *  local/temp filesystem and "find" them there.
   *
   *  @param name the resource name
   *  @return URL for resource or null if not found
   */
  
  protected URL findResource(String name) {
    return null;
  }

  /**
   *  Used for identifying resources from multiple URLS.
   *  We will return a null for the same reasons as above.
   *
   *  @param name the resource name
   *  @return Enumeration of one URL
   */
  
  protected Enumeration findResources(final String name) throws IOException {
    return null ;  
  }

  /**
   *  Minimal package definition
   *
   */
  
  private void definePackage(String className) {
  
    // Extract the package name from the class name.
    
    String pkgName = className;
    int index = className.lastIndexOf('.');
    
    if (index != -1) {
      pkgName =  className.substring(0, index);
    }

    // Preconditions -- need a manifest and the package
    // is not previously defined.
    
    if ( null == manifest ||
      getPackage(pkgName) != null) {
        return;
    }

    String specTitle,
           specVersion,
           specVendor,
           implTitle,
           implVersion,
           implVendor;

    // Look up the versioning information.
    // This should really look for a named attribute.
    
    Attributes attr = manifest.getMainAttributes();

    if (attr != null) {
      specTitle   = attr.getValue(Name.SPECIFICATION_TITLE);
      specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
      specVendor  = attr.getValue(Name.SPECIFICATION_VENDOR);
      implTitle   = attr.getValue(Name.IMPLEMENTATION_TITLE);
      implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
      implVendor  = attr.getValue(Name.IMPLEMENTATION_VENDOR);

      definePackage(pkgName,
                    specTitle,
                    specVersion,
                    specVendor,
                    implTitle,
                    implVersion,
                    implVendor,
                    null); // No sealing for simplicity.
    }
  }

  //  The getTaskThreadName is a getter for the taskthread field.
  
  public String getTaskThreadName() {
    return this.taskthread ;
  }
  
  //  getResourceEntries returns an enumeration of the keys
  //  in the hashtable.  The keys are the names of the JAR
  //  entries we picked up earlier.  This is useful for
  //  printing the list of JAR resources.
  
  public Enumeration getResourceEntries() {
    return htSizes.keys() ;
  }
  
  private Manifest manifest = null ;
  private String taskthread = null ;
  private String jarfilename=null;
  private  File tempfile = null ;
  private Enumeration enum = null ;
  private Hashtable htSizes = new Hashtable() ;
}
