package de.farafin.snEADy.inOut;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import de.farafin.snEADy.communication.I_Constants;

/**
 * Laedt JAVA-Klassen aus einen anzugebenden Verzeichnis.
 * Stammt aus den Sun JAVA-Tutorien.
 * @see Utils#loadHamster(java.lang.String, algds.c2002.Logable, boolean)
 * @author roland, lars (nur nachbearbeitung, Stammt aus den Sun JAVA-Tutorien)
 * 
 * @version $Revision: 1.6 $
 */
public final class C_FileClassLoader extends ClassLoader implements I_Constants
{
	/**
	 * Comment for <code>root</code>
	 */
	private String root;

	/**
	 * @param rootDir
	 */
	public C_FileClassLoader(String rootDir)
	{
		if(rootDir == null) throw new IllegalArgumentException("Null root directory");
		root = rootDir.replace('.', File.separatorChar);
	}

	/**
	 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
	 */
	public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
	{
		//if(DEBUG) System.out.println("Loader: Lade " + name);

		Class c;
		try
		{
			// findLoadedClass is not static, thats why the method is not static
			c = findLoadedClass(name);

			if(c != null)
			{
				//if(DEBUG) System.out.println("DEBUG C_FileClassLoader.loadClass: class Loaded!");
				return c;
			}
			
			// roland: class wurde bereits geladen
			//if(DEBUG) if(c != null) System.out.println("Loader: " + name + " bereits da.");

			// roland: wenn class noch nicht geladen wurde, versuche class aus
			// file zu laden

			if(c == null)
			{
				try
				{
					c = findSystemClass(name);
					//if(DEBUG) if(c != null) System.out.println("Loader: " + name + " als Systemklasse geladen.");
				}
				catch(Exception e)
				{
					//if(DEBUG) System.out.println(e);
				}
			}

			if(c != null)
			{
				//if(DEBUG) System.out.println("DEBUG C_FileClassLoader.loadClass: class Loaded!");
				return c;
			}
			
			if(c == null)
			{
				// Convert class name argument to filename
				// Convert package names into subdirectories
				String filename = name.replace('.', File.separatorChar) + ".class";

				try
				{
					// Roland: enth?llt die bytes 1:1 aus der .class-datei
					byte data[] = loadClassData(filename);

					// roland: interpretiere bytes beginnend von anfang bis ende als class mit dem namen name
					c = defineClass(name, data, 0, data.length);

					//if(DEBUG) System.out.println("Loader: " + name + " selbst geladen.");
				}
				catch(IOException e)
				{
					// tritt auf, wenn die Datei nicht gefunden wurde
					//if(DEBUG) System.out.println(e);
				}
				catch(NoClassDefFoundError e)
				{
					// tritt auf, wenn die Datei gefunden wurde,
					// aber eine Klasse enthaelt, die nicht dem Dateinamen
					// enspricht (Windows findet Dateien
					// auch bei falscher Gross-Klein-Schreibung)
					//if(DEBUG) System.out.println(e);
				}
			}

			// Since all support classes of loaded class use same class loader
			// must check subclass cache of classes for things like Object
		

			if(c == null)
			{
				//if(DEBUG) System.out.println("Loader: Class " + name + " not found.");
				throw new ClassNotFoundException(name);
			}
			// Roland: Linkt die geladene Class in das laufende projekt
			if(resolve) resolveClass(c);

		}
		catch(Throwable ex)
		{
			if(DEBUG) System.out.println(ex);
			throw new ClassNotFoundException(name);
		}

		//if(DEBUG) System.out.println("Loader: Class " + name + " successfully loaded.");
		return c;
	}

	/**
	 * @param filename
	 *            the filename of the class that should be loaded
	 * @return the compleate file as byte array
	 * @throws IOException
	 */
	private byte[] loadClassData(String filename) throws IOException
	{

		// Create a file object relative to directory provided
		File f = new File(root, filename);
		//if(DEBUG) System.out.println("Try to load class file " + f);

		// Get size of class file
		int size = (int) f.length();

		// Reserve space to read
		byte buff[] = new byte[size];

		// Get stream to read from
		FileInputStream fis = new FileInputStream(f);
		DataInputStream dis = new DataInputStream(fis);

		// Read in data
		dis.readFully(buff);

		// close stream
		dis.close();

		// return data
		return buff;
	}
}