// LocalServletLoader.java
// $Id: LocalServletLoader.java,v 1.18 1998/06/15 15:43:13 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.servlet;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;

class ServletClassEntry {
  
    long   classStamp   = 0;
    Class  servletClass = null;
    File   classFile    = null;
    boolean systemClass = false;

    public boolean isModified() {
	if (! systemClass)
	    return (classFile.lastModified() > classStamp);
	return false;
    }

    public void update() {
	if (! systemClass)
	    classStamp   = classFile.lastModified();
    }

    public ServletClassEntry(Class servletClass) {
	this.servletClass = servletClass;
	this.systemClass  = true;
    }

    public ServletClassEntry (File classFile, 
			      Class servletClass)
    {
	this.classFile    = classFile;
	this.servletClass = servletClass;
	if (classFile != null)
	    this.classStamp   = classFile.lastModified();
	this.systemClass  =  false;
    }

}


/**
 * LocalServletLoader : 
 * @author Benoit Mahe <bmahe@inria.fr>
 */


public class LocalServletLoader extends ClassLoader {
    private static final boolean debug = false;
    private Hashtable classes = null;
    private int CLASSES_DEFAULT_SIZE = 13;

    ServletWrapper wrapper = null;

    private void trace(String msg) {
	trace(msg,true);
    }

    private void trace(String msg, boolean display) {
	if (display)
	    System.out.println("["+wrapper.getURLPath()+"]: "+msg);
    }

    private String removeSpace(String string) {
	int start = -1;
	int end = string.length();
	boolean spaces = false;
	while (string.charAt(++start) == ' '){ spaces = true; };
	while (string.charAt(--end) == ' '){ spaces = true; };
	if (spaces)
	    return string.substring(start, ++end);
	else
	    return new String(string);
    }

    private String packaged(String name) {
	String cname = removeSpace(name);
	try {
	    if (cname.endsWith(".class")) {
		int idx = cname.lastIndexOf('.');
		if (idx != -1)
		    cname = cname.substring(0,idx);
	    }
	    return cname.replace('.', '/')+".class";
	} catch (Exception ex) {
	    return name;
	}
    }


    /**
     * Given the class name, return its File name.
     * @param name The class to be loaded.
     * @return The File for the class.
     */

    protected String[] classpath = null;

    protected File locateClass(String name) {
	File classfile = null;
	File base = wrapper.getServletDirectory();
	String cname = null;

	cname = packaged(name);
	classfile = new File(base, cname);

	if (classfile != null)
	    if (classfile.exists()) return classfile;
    
	if (classpath == null) {
	    String path_separator = System.getProperty("path.separator");
	    Vector V = new Vector(12);
	    String cp = System.getProperty("java.class.path");
	    StringTokenizer st = new StringTokenizer(cp, path_separator);
	    String path = null;
	    while (st.hasMoreTokens()) {
		path = st.nextToken();
		if ((! path.endsWith(".zip")) && (! path.endsWith(".jar")))
		    V.addElement(path);
	    }
	    classpath = new String[V.size()];
	    V.copyInto(classpath);
	}

	for (int i=0; i<classpath.length ; i++) {
	    classfile = new File(classpath[i],cname);
	    if (classfile.exists())
		return classfile;
	}
	return null;
    }
  

    public URL getResource(String name) {
	URL resource = getSystemResource(name);
	if (resource != null)
	    return resource;
	String url = wrapper.getServletDirectory().getAbsolutePath();
	// Return the location of that resource (name resolved by j.l.Class)
	if ( File.separatorChar != '/' )
	    url = url.replace(File.separatorChar, '/');
	File file = new File(url,name);
	if (! file.exists())
	    return null;
	try {
	    return new URL("file", "localhost", url+"/"+name);
	} catch (MalformedURLException ex) {
	    // Shouldn't fall here
	    return null;
	}
    }
  
    /**
     * Get a resource as a stream.
     * @param name The name of the resource to locate.
     */
  
    public InputStream getResourceAsStream(String name) {
	InputStream in = getSystemResourceAsStream(name);
	if (in != null) 
	    return in;
	URL resource = getResource(name);
	if (resource == null)
	    return null;
	try {
	    URLConnection c = resource.openConnection();
	    return c.getInputStream();
	} catch (IOException ex) {
	    return null;
	}
    }
  
    protected Class loadClassFile(File file) 
	throws ClassNotFoundException
    {
	byte data[] = null;
	if ( file == null )
	    throw new ClassNotFoundException("invalid servlet base");
	trace("located at "+file,debug);
	try {
	    BufferedInputStream in      = 
		new BufferedInputStream( new FileInputStream( file) );
	    ByteArrayOutputStream out   = new ByteArrayOutputStream(512);
	    byte                  buf[] = new byte[512];
	    for (int got = 0 ; (got = in.read(buf)) > 0 ; )
		out.write(buf, 0, got);
	    data = out.toByteArray();
	    in.close();
	} catch (Exception ex) {
	    if (debug) 
		ex.printStackTrace();
	    throw new ClassNotFoundException(file.getAbsolutePath());
	}
	// Define the class:
	return defineClass(data, 0, data.length);
    }

    protected final Class getCachedClass(String name, boolean resolve) 
	throws ClassNotFoundException
    {
	ServletClassEntry entry = (ServletClassEntry)classes.get(name);
	if (entry != null) {
	    if (! entry.isModified()) {
		trace(name+": not modified",debug);
		return entry.servletClass;
	    } else if (! entry.systemClass) {
		entry.servletClass =  loadClassFile(entry.classFile);
		if ( resolve )
		    resolveClass(entry.servletClass);
		entry.update();
		trace(name+": reloaded",debug);
		return entry.servletClass;
	    }
	}
	return null;
    }

    protected void checkPackageAccess(String name) {
	SecurityManager s = System.getSecurityManager();
	if ( s != null ) {
	    int i = name.lastIndexOf('.');
	    if ( i >= 0 )
		s.checkPackageAccess(name.substring(0, i));
	}
    }

    protected final Class getNewClass(File classfile, 
				      String name, 
				      boolean resolve) 
	throws ClassNotFoundException
    {
	Class c = null;
	c       = loadClassFile(classfile);
	if ( resolve )
	    resolveClass(c);
	classes.put(name, new ServletClassEntry(classfile,c));
	trace(name+": loading new class done.",debug);
	return c;
    }

    protected Class loadServletClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	Class c = null;
	trace(name+": loading servlet class",debug);
	// Look for a cached class first:
	c = getCachedClass(name,resolve);
	if (c != null) return c;
	
	checkPackageAccess(name);
	
	File  file    = locateClass(name);
	// Then look for a system class:
	if (file == null) {
	    try {
		if ((c = findSystemClass(name)) != null) {
		    trace(name+": system class",debug);
		    classes.put(name, new ServletClassEntry(c));
		    return c;
		} else 
		    throw new ClassNotFoundException(name);
	    } catch (Exception ex) {
		throw new ClassNotFoundException(name);
	    }
	}
	return getNewClass(file,name,resolve);
    }

    protected Class loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	Class c = null;
	trace(name+": loading class",debug);
	// Look for a cached class first:
	c = getCachedClass(name,resolve);
	if (c != null) return c;

	checkPackageAccess(name);
	
	File  file    = locateClass(name);

	if (file == null) {
	    // Then look for a system class:
	    try {
		if ((c = findSystemClass(name)) != null) {
		    trace(name+": system class",debug);
		    return c;
		}
		else
		    throw new ClassNotFoundException(name);
	    } catch (Exception ex) {
		throw new ClassNotFoundException(name);
	    }
	}
	return getNewClass(file,name,resolve);
    }

    protected LocalServletLoader(ServletWrapper wrapper) {
	super();
	this.wrapper = wrapper;
	this.classes = new Hashtable(CLASSES_DEFAULT_SIZE);
    }

    protected LocalServletLoader(LocalServletLoader loader) {
	super();
	this.wrapper = loader.wrapper;
	int classesSize = loader.classes.size();
	if (classesSize < CLASSES_DEFAULT_SIZE ) 
	    classesSize = CLASSES_DEFAULT_SIZE;
	this.classes = new Hashtable(classesSize);
	this.classpath = loader.classpath;
    }

    public boolean classChanged(String name) {
	ServletClassEntry entry = (ServletClassEntry)classes.get(name);
	if (entry != null) {
	    if (debug) {
		System.out.println("entry    : "+name);
		if (!entry.systemClass) {
		    System.out.println("file     : "+
				       entry.classFile.getAbsolutePath());
		    System.out.println("Stamp    : "+entry.classStamp);
		}
		System.out.println("System   : "+entry.systemClass);
		System.out.println("modified : "+entry.isModified());
	    }
	    return entry.isModified();
	}
	return true;
    }
    
}
