// LocalServletLoader.java
// $Id: LocalServletLoader.java,v 1.14 1998/01/14 10:47:40 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package 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 packaged(String name) {
    String cname = new String(name);
    try {
      if (cname.endsWith(".class")) {
	int idx = cname.lastIndexOf(".");
	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) {
      Vector V = new Vector(12);
      String cp = (String)System.getProperties().get("java.class.path");
      StringTokenizer st = new StringTokenizer(cp, ":");
      String path = null;
      while (st.hasMoreTokens()) {
	path = st.nextToken();
	if (! path.endsWith(".zip"))
	  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;
  }

}
