package JavaAgent.agent;

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

/**
 * Class which loads other classes over the net from a given URL.
 * Modified from WWWClassLoader, see copyright notice below.
 * ----------------------------------------------------------------
 * @(#)WWWClassLoader.java	1.38 95/05/12 Jonathan Payne
 *
 * Copyright (c) 1994 Sun Microsystems, Inc. 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. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN 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. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 * -----------------------------------------------------------------
 *
 * <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 0.3 5/21/96 for Java(tm) Language Version 1.0.2
 */

public class NetworkClassLoader extends ClassLoader {

  private static String urlpath[] = 
    { /* "", */ "classes/", "/classes/" };
  private URL source;
  public URL ctx;		    
  Hashtable classes = new Hashtable();   
  Agent parent;

  /**
   * Creates a new NetworkClassLoader for loading classes from the given
   * URL.
   *
   * @param location The URL for the code.
   */

  public NetworkClassLoader(URL url, Agent p){
    ctx = url;
    parent = p;
    try{
      source = new URL(url.getProtocol(),
		       url.getHost(),
		       url.getFile());
    } catch (Exception e){
      parent.addSystemMessage("Error creating NetworkClassLoader.",e);
    }
  }

  /**
   * Load a class from an input stream
   */
  private Class loadClass(InputStream is) {
    byte	bytes[] = new byte[4096];
    byte	buffer[] = new byte[1024];
    int	n;
    int	totalBytes = 0;
    Class c = null;

    try{
      while ((n = is.read(buffer, 0, buffer.length)) >= 0) {
	if (totalBytes + n >= bytes.length) {
	  byte	newbytes[] = new byte[((bytes.length + n) * 3) / 2];
	  
	  System.arraycopy(bytes, 0, newbytes, 0, totalBytes);

	  bytes = newbytes;
	}
	System.arraycopy(buffer, 0, bytes, totalBytes, n);
	totalBytes += n;
      }
      c =  defineClass(bytes, 0, totalBytes);
    } catch (Exception e){
      parent.addSystemMessage("Unable to load class from input stream.",e);
    }
    return c;

  }
  
  private static Hashtable nonlocalPackages = new Hashtable();
  

  /**
   * Check if a package is local. The package name is
   * seperated by /'s.
   * 
   * I will be loading things from identical packages (package Agent), so
   * the idea of a localPackage may not be very useful.
   */
  
  public synchronized static boolean localPackage(String pkg) {
        
    if (nonlocalPackages.get(pkg) != null) {
      return false;
    }
    
    // Check if the package occurs in the classpath
      String str = System.getenv("CLASSPATH");
    for (int i = str.indexOf(':') ; i >= 0 ; str = str.substring(i + 1), i = str.indexOf(':')) {
      if (new File(str.substring(0, i) + "/" + pkg).isDirectory()) {
	return true;
      }
    }
    if ((str.length() > 0) && new File(str + "/" + pkg).isDirectory()) {
      return true;
    }
    nonlocalPackages.put(pkg, pkg);
    return false;
  }
  
  
  /**
   * Load a class from this class loader.
   */

  public Class loadClass(String name) {
    Class c = null;
    try {
      c = loadClass(name, true);
    } catch (ClassNotFoundException e){
      parent.addSystemMessage("Can't load class.", e);
    }
    return c;
  }
  
  /**
   * Load and resolve a class.
   * If the class is not in the AgentClassLoader buffer then first
   * try to get it from the runtime system, if that fails then try to 
   * find it. If it can't be found, return null.
   */
  protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
    Class cl = (Class)classes.get(name);
    if (cl == null) {
      try {
	return Class.forName(name);
      } catch (Exception e) {}
      
      cl = findClass(name);
    }
    
    if ((cl != null) && resolve) {
      try {
	resolveClass(cl);
      } catch (Exception e) {
	throw new ClassNotFoundException(e.toString());
      }
    }
    return cl;
  }
  
  /**
   * This method finds a class. The returned class
   * may be unresolved. This method has to be synchronized
   * to avoid two threads loading the same class at the same time.
   * Must be called with the actual class name.
   */

  private synchronized Class findClass(String name) {
    Class cl = (Class)classes.get(name);
    
    if (cl == null) {
      
      /*
	 
	 System.out.println(Thread.currentThread().getName() + " find class " + name);
	 
	 
	 int i = name.lastIndexOf('.');
	 if ((i >= 0) && localPackage(name.substring(0, i))) {
	 throw new NoClassDefFoundException(name);
	 }
	 
	 
	 skip the first entry if the context is "/" to avoid a
	 duplicate probe
	 
	 int start_index = ctx.file.equals("/") ? 1 : 0;
	 
	 for (int i = start_index; i < urlpath.length; i++) {
	 String cpath = urlpath[i];
	 */
      
      InputStream is = null;
      
      try {
	
	URL url = new URL(ctx, name.replace('.', '/') + ".class");

	try {
	  is = url.openStream();
	  
	  if (is != null) {
	    try {
	      cl = loadClass(is);
	      classes.put(name, cl);
	    } catch (Exception e) {
	      /* There is very little we can do in this case
		 so we print a detailed message (with
		 corrective action) and rethrow the
		 exception. */
	      parent.addSystemMessage("File format exception when reading \"" + url.toExternalForm() + "\".");
	      parent.addSystemMessage("Try recompiling this class file from the source.");
	      throw e;
	    }
	  }
	} catch (IOException e) {
	  parent.addSystemMessage("Error trying to open InputStream to .class file. Try a different URL.", e);

	} finally {
	  if (is != null) {
	    is.close();
	  }
	}
      } catch (Exception e) {
	parent.addSystemMessage("Error creating URL for .class file.",e);
      }
    }
    
    return cl;
  } 
  
}


