package railo.loader.classloader;


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import railo.loader.util.Util;


/**
 *
 * This class implements a simple class loader 
 * that can be used to load at runtime 
 * classes contained in a JAR file.
 */
public final class RailoClassLoader extends ClassLoader {
    private Hashtable classes = new Hashtable();
    private Hashtable resources = new Hashtable();
    private ClassLoader pcl;
 
  /**
   * Creates a new JarClassLoader that will allow the loading
   * of classes stored in a jar file.
   *
   * @param jarFile   the name of the jar file
   * @param parent parent class loader
 * @throws IOException 
   * @exception IOException   an error happened while reading
   * the contents of the jar file
   */
    public RailoClassLoader(File jarFile, ClassLoader parent) throws IOException {  
    	this(new FileInputStream(jarFile),parent,isSecure(jarFile));
    }
    private static boolean isSecure(File jarFile) {
		if(jarFile.getName().toLowerCase().endsWith(".rc")) return false;
    	return true;
	}
	public RailoClassLoader(InputStream jar, ClassLoader parent, boolean secured) throws IOException {  
        super(parent);
        
        if(secured)
        	throw new IOException("secured core files are not supported");
        
        
        this.pcl=parent;
        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(jar));
  
        try {
            byte[] buffer = new byte[0xffff];
            int bytes_read;
          
            ZipEntry ze;
            byte[] barr;
            while((ze = zis.getNextEntry()) != null) {
                if (!ze.isDirectory()) {
                    ByteArrayOutputStream baos=new ByteArrayOutputStream();
                    while((bytes_read = zis.read(buffer)) != -1)
                        baos.write(buffer, 0, bytes_read);
                    String name = ze.getName().replace('\\', '/');
                    barr=baos.toByteArray();
                    if(name.endsWith(".class")) {
                        String className=name.substring(0,name.length()-6);
                        className=className.replace('/','.');
                        classes.put(className,barr);
                    }
                    resources.put(name, barr);
                    zis.closeEntry();
                    baos.close();
                }
            }
        }
        finally {
            Util.closeEL(zis);
        }   
    }

	/**
     * Looks among the contents of the jar file (cached in memory)
     * and tries to find and define a class, given its name.
     *
     * @param className   the name of the class
     * @return   a Class object representing our class
     * @exception ClassNotFoundException   the jar file did not contain
     * a class named <code>className</code>
     */
    public Class findClass(String className) throws ClassNotFoundException {
        byte[] classBytes = (byte[])classes.get(className);
        if (classBytes == null) throw new ClassNotFoundException("class ["+className+"] not found");
        return defineClass(className, classBytes, 0, classBytes.length);
    }
    
    private Class findClassEL(String className) {
        byte[] classBytes = (byte[])classes.get(className);
        if (classBytes == null) return null;
        return defineClass(className, classBytes, 0, classBytes.length);
    }
     
     
     
     /**
      * Loads the class with the specified name. This method searches for 
      * classes in the same manner as the "loadClass(String, boolean)" 
      * method. It is called by the Java virtual machine to resolve class 
      * references. Calling this method is equivalent to calling 
      * <code>loadClass(name, false)</code>.
      *
      * @param     name the name of the class
      * @return    the resulting <code>Class</code> object
      * @exception ClassNotFoundException if the class was not found
      */
    public Class loadClass(String name) throws ClassNotFoundException   {
        return loadClass(name, false);
    }

     /**
      * Loads the class with the specified name.  The default implementation of
      * this method searches for classes in the following order:<p>
      *
      * <ol>
      * <li> Call {@link #findLoadedClass(String)} to check if the class has
      *      already been loaded. <p>
      * <li> Call the <code>loadClass</code> method on the parent class
      *      loader.  If the parent is <code>null</code> the class loader
      *      built-in to the virtual machine is used, instead. <p>
      * <li> Call the {@link #findClass(String)} method to find the class. <p>
      * </ol>
      *
      * If the class was found using the above steps, and the
      * <code>resolve</code> flag is true, this method will then call the
      * {@link #resolveClass(Class)} method on the resulting class object.
      * <p>
      * From the Java 2 SDK, v1.2, subclasses of ClassLoader are 
      * encouraged to override
      * {@link #findClass(String)}, rather than this method.<p>
      *
      * @param     name the name of the class
      * @param     resolve if <code>true</code> then resolve the class
      * @return   the resulting <code>Class</code> object
      * @exception ClassNotFoundException if the class could not be found
      */
     protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
         // First, check if the class has already been loaded
         Class c = findLoadedClass(name);
         if (c == null) {
             c=findClassEL(name);
             if(c==null) {
                 c = pcl.loadClass(name);
             }
         }
         if (resolve) {
             resolveClass(c);
         }
         return c;
    }
     
    /**
     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
     */
   public InputStream getResourceAsStream(String name) {
       name = name.replace('\\', '/');
       
       byte[] bytes = (byte[])resources.get(name);
       if (bytes == null) return super.getResourceAsStream(name);
       return new ByteArrayInputStream(bytes);
   }
}

