/*
* @(#) NetworkClassLoader.java  - Carica una classe dal file system locale o dal network.
*/

package loader;
import java.io.*;
import java.net.*;
import loader.*;

/**
* <br><b>Carica una classe dal file system locale o dal network.</b><br>
* Quando una classe viene caricata utilizzando questo loader,
* esso diviene il loader di default. <br>
* Tutte le classi ad essa associate (che siano superClassi, o classi contenute) verranno
* caricate con questo loader. <br>
* <br><br>
* Quando viene richiesta una classe, essa viene prima cercata nella cache locale
* (implementata in java.lang.ClassLoader), poi nel file system locale,  e se non trovata
* allora viene cercata sul Network.<br>
* Nella cache vengono aggiunte solo le classi provenienti dal Network.<br>
* Se tutte le classi sono presenti in locale non viene stabilita nessuna  connessione con il server.<br>
* <br>
* Esempio di utilizzo:<br>
* <b>
* ClassLoader loader = new NetworkClassLoader(host, port);<br>
* Object main = loader.loadClass("nameClass").newInstance();<br>
* </b>
* @see 	  java.lang.ClassLoader
* @version		1.0 - Gennaio 1998
* @author 		Donato Cappetta  - cappetta@diiie.unisa.it
* @since 		JDK1.1
*/

public class NetworkClassLoader  extends ClassLoader {
      private String hostName = null;
      private int serverPort;
      private Socket socket = null;
      private DataInputStream is = null;
      private DataOutputStream os = null;
      private boolean connected = false;

      /**
      * Costruttore di default<br>
      * C'e' solo per completezza.
      */
      public NetworkClassLoader() {
      		this("localhost", 5050);
      }
      /**
      * Costruttore.<br>
      *
      * @param  hostName Nome del server che "contiene" le classi.
      * @param  serverPort  Numero di porta di connessione.
      */
      public NetworkClassLoader(String hostName, int serverPort) {
      		super();
      		this.hostName = hostName;
      		this.serverPort = serverPort;
      }

       /**
       * Carica una classe.<br>
       * Il metodo viene invocato direttamente dal metodo pubblico loadClass(String name) in
       * java.lang.ClassLoader. <br>
       * Il parametro resolve viene passato automaticamente
       * in modo corretto.<br>
       * @param     name    nome della classe.
       * @param     resolve true o false se la classe va risolta o meno. In genere una classe e da
       * 						risolvere se contiene riferimeni ad altre classi.<br>
       *						Il loader imposta correttamente ed automaticamente questo parametro ad ogni chiamata.<br>
       *
       * @exception ConnectClassServerException  Errore durante la connessione con il ClassServer.
       * @exception JavaPackageException  Tentativo di caricamento di una classe appartenente al package java dal Network.
			* @exception ClassNotFoundException  classe non trovata.
       * @exception ClassFormatError    errore nel convertire la classe da array a  Class.
       * @return Class - la classe richiesta.
       */
      protected Class loadClass(String nameClass, boolean resolve)
							      throws   ConnectClassServerException, JavaPackageException , ClassNotFoundException, ClassFormatError {
      		byte[] classBytes = null;
				Class classClass = null;

	      		System.out.println("Class richiesta: " + nameClass);
	      		//Cerca la classe nella cache delle classi locali
	      		classClass = findLoadedClass(nameClass);
	      		if (classClass == null) {
		      		//System.out.println("Cerco la classe nel File System ... ");
   		      		try {
   		      			classClass = findSystemClass(nameClass);
   		      		} catch(ClassNotFoundException cnfe){
   		      			//classe non trovata nel file system
   		      			classClass = null;
   		      		}
   		     }	//end if() della ricerca in file system

   		    	//se non  trova la classe in locale la cerca sul ClassServer
   		      	if  (classClass == null) {
	   		      	//System.out.println("Cerco la classe sul classServer ... ");
            		try {
            			if (nameClass.startsWith( "java.")) throw new JavaPackageException(nameClass);

            			//si connette al Server delle Classi (classServer)
	            		if (!connected)  connect();
                  		classBytes = loadClassFromServer(nameClass);
                  	} catch (UnknownHostException uhe){
                  			throw new ConnectClassServerException(uhe.toString());
                  	} catch (SocketException se){
                       		disconnect();
                  			throw new ConnectClassServerException(se.toString());
                  	} catch (IOException ioe){
                       		disconnect();
                  			throw new ConnectClassServerException(ioe.toString());
                  	} catch (ClassNotFoundException cnfe){
                  			throw cnfe;
                  	}	//end try-catch

                 	//converte l'array di bytes in classe ed inserice la classe nella cache.
                 	classClass = defineClass(nameClass, classBytes, 0, classBytes.length);
                 	if (classClass == null) throw new ClassFormatError(nameClass);
             	}	//end if della ricerca sul classServer.

          		//se non e' risolta, risolve la classe
             	if (resolve) resolveClass(classClass);
           return classClass;
      }	//end loadClass()


      /**
      * Stabilisce la connessione con il ClassServer.
      *
      * @exception UnknownHostException 	see java.net.UnknownHostException.
      * @exception IOException				see  java.io.IOException.
      *
      */
      protected void connect() throws UnknownHostException, IOException {
      		//System.out.println("Connessione al ClassServer " + hostName + ":" + serverPort);
      		try {
           	socket = new Socket(hostName, serverPort);
        		connected = true;
        		is = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
        		os = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
        	} catch(UnknownHostException uhe){
        		throw uhe;
      		} catch(IOException ioe){
      			throw ioe;
      		}
      }	//end connect()

      /**
      * Chiude la connessione con il ClassServer
      */
      protected void disconnect(){
      		try {
	          	connected = false;
      				os.close(); os = null;
      				is.close(); is = null;
             	socket.close(); socket = null;
      		} catch(IOException ioe){
   	   	} catch (Exception e) {}
      }	//end disconnect()

      /**
      * Legge una  classe dal network e la restuituisce come array di byte.
      *
      * @param     nameClass    nome della classe.
      * @exception ClassNotFoundException 	Classe non trovata.
      * @exception SocketException		see java.net.SocketException.
      * @exception IOException			see java.io.IOException.
	   * @return 		byte[] Array di byte che rappresenta la classe.
      */
		protected byte[] loadClassFromServer(String nameClass)
						throws ClassNotFoundException, SocketException, IOException {
			byte[] classBytes = null;
			int size = 0;
      		// load the class data from the connection
          	try {
           		//invia il nome della classe
           		os.writeUTF(nameClass);
           		os.flush();
           		//legge la dimensione della classe
           		size = is.readInt();
           		//se size == -1 la classe non e' stata trovato
           		if (size == -1) 	throw new ClassNotFoundException(nameClass);
           		classBytes = new byte[size];
           		is.read(classBytes);
          	} catch (SocketException se){
           		throw se;
          	} catch (IOException ioe) {
           		throw ioe;
          	}
          	return classBytes;
       }   //end loadClassFromServer()


      /**
      * Esegue finalize.
      * Metodo presente  per completezza!
      */
      protected void finalize(){
    	  	try {
  				super.finalize();
	  		} catch (Throwable t) {}
      }	//end finalize()
      
      /**
      * Disconnette dal Server, se connesso.
      */
      public void stop(){
           if (connected) disconnect();
      }	//end stop()


}  //end class NetworkClassLoader