import japa.parser.JavaParser;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.expr.*;
import japa.parser.ast.expr.BinaryExpr.*;
import japa.parser.ast.stmt.ExpressionStmt;
import japa.parser.ast.stmt.ReturnStmt;
import japa.parser.ast.stmt.Statement;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;


class Evaluator{
	private static HashMap<Object, HashMap<String, Variable>> variables = new HashMap<Object, HashMap<String, Variable>>();
	private static HashMap<Class, ArrayList<WeakReference<Object>>> classInstances = new HashMap<Class, ArrayList<WeakReference<Object>>>();
	public static Class classes[];
	public static boolean dynamics = false;
	
	public static void addInstance(Object obj){ //New instance of an EM object has been created. Add Dynamic variables
		if(!dynamics) return;
		
		ArrayList<WeakReference<Object>> list = classInstances.get(obj.getClass());
		if(list == null){
			list = new ArrayList<WeakReference<Object>>();
			classInstances.put(obj.getClass(), list);
		}
		
		list.add(new WeakReference<Object>(obj));
		
		initDynamicVariables(obj);
	}
	private static void initDynamicVariables(Object obj){
		HashMap<String, Variable> vars = variables.get(obj.getClass());
		if(vars == null) return;
		
		for(String key : vars.keySet()){
			createVariable(obj, key);
		}
	}
	private static void addDynamicVariable(Class cls, String name){
		ArrayList<WeakReference<Object>> instances = classInstances.get(cls);
		if(instances == null) return;
		
		for(WeakReference<Object> inst : instances){
			if(inst.get() == null) continue;
			Variable var = createVariable(inst.get(), name);
			var.setClass(inst.get());
		}
	}
	private static void updateDynamicVariable(Class cls, String name, String def){
		ArrayList<WeakReference<Object>> instances = classInstances.get(cls);
		if(instances == null) return;
		
		for(WeakReference<Object> inst : instances){
			if(inst.get() == null) continue;
			updateDependency(inst.get(), name, def);
		}
	}
	
	public static Object[][] getFields(Object obj) throws IllegalArgumentException, IllegalAccessException{
		if(obj.getClass().isArray()){
			return getArray(obj);
		}
		boolean isStatic = false;
		Field fields[] = null;
		if(obj instanceof Class) {
			fields = ((Class) obj).getDeclaredFields();
			isStatic = true;
		}
		else fields = obj.getClass().getDeclaredFields();
		
		HashMap<String, Variable> store = getStorage(obj);
		
		ArrayList<String> placed = new ArrayList<String>();
		ArrayList<String[]> result = new ArrayList<String[]>();
		
		for(int i = 0; i < fields.length; i++){
			Field f = fields[i];
			f.setAccessible(true);
			String name = f.getName();
			String type = f.getType().getName();
			Object val = "N/A";
			if(!isStatic) val = f.get(obj);
			String dependency = null;
			
			if(store.containsKey(name)){
				Variable var = store.get(name);
				if(var.getDefinition() != null){
					dependency = var.getDefinition().toString();
				}
			}
			if(dependency == null && checkIsDependency(obj, name)) {
				dependency = "EXPR";
				Annotation ann = f.getAnnotation(EDEN_DESCRIPTION.class);
				if(ann != null){
					dependency = ((EDEN_DESCRIPTION) ann).definition();
				}
			}
			
			if(val != null && val.getClass().getName().startsWith("em")){
				try {
					Field tmp = val.getClass().getField("item");
					tmp.setAccessible(true);
					type = tmp.getType().getName();
					val = tmp.get(val);
					tmp.setAccessible(false);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
			placed.add(name);
			result.add(new String[]{
				name, type, (val == null ? "null" : val.toString()), (dependency == null ? "" : dependency), "-->"
			});
		}
		
		for(Variable v : store.values()){
			if(placed.contains(v.name)) continue;
			
			result.add(new String[]{
					v.name, v.type, (v.get() == null ? "null" : v.get().toString()), isNull(v.getDefinition(), "").toString(), "-->"
			});
		}
		
		Object results[][] = new Object[result.size()][5];
		for(int i = 0; i < result.size(); i++){
			results[i] = result.get(i);
		}
		
		return results;
	}
	public static Object[][] getMethods(Object obj) throws IllegalArgumentException, IllegalAccessException{
		if(obj.getClass().isArray()){
			return getArray(obj);
		}
		boolean isStatic = false;
		Method methods[] = null;
		if(obj instanceof Class) {
			methods = ((Class) obj).getDeclaredMethods();
			isStatic = true;
		}
		else methods = obj.getClass().getDeclaredMethods();
		Object results[][] = new Object[methods.length][5];
		
		for(int i = 0; i < methods.length; i++){
			Method m = methods[i];
			if(isStatic && !Modifier.isStatic(m.getModifiers())) continue;
			if(m.getName().startsWith("access$")) continue;
			
			String args = "";
			Class types[] = m.getParameterTypes();
			for(int a = 0; a < types.length; a++){
				args += ", " + types[a].getName();
			}
			if(args.length() > 2) args = args.substring(2);
			
			results[i] = new Object[]{
				m.getName(), m.getReturnType().getName(), args, "", "-->"
			};
		}
		
		return results;
	}
	
	public static Object[][] getClasses(Object obj) throws IllegalArgumentException, IllegalAccessException{
		classes = classFiles();
		Object results[][] = new Object[classes.length][5];
		
		for(int i = 0; i < classes.length; i++){
			
			results[i] = new Object[]{
				classes[i].getName(), JaDEN.class.isAssignableFrom(classes[i]) ? "Yes" : "", Shape.class.isAssignableFrom(classes[i]) ? "Yes" : "", "", "-->"
			};
		}
		
		return results;
	}
	
	public static Object[][] getArray(Object arr){
		int length = Array.getLength(arr);
		Object results[][] = new Object[length][5];
		for(int i = 0; i < length; i++){
			Object elem = Array.get(arr, i);
			results[i] = new Object[]{
				i, (elem == null ? "null" : elem.getClass().getName()), (elem == null ? "null" : elem.toString()), "", "-->"
			};
		}
		return results;
	}
	private static boolean checkIsDependency(Object cls, String fieldName){
		try{
			String setMethod = "em_update" + LiveEdit.formatField(fieldName);
			if(cls instanceof Class) ((Class) cls).getDeclaredMethod(setMethod, null);
			else cls.getClass().getDeclaredMethod(setMethod, null);
			return true;
		}catch(Exception me){}
		return false;
	}
	public static Object getFieldValue(Object cls, String name){
		try {
			Field f;
			if(cls instanceof Class) f = ((Class) cls).getDeclaredField(name);
			else f = cls.getClass().getDeclaredField(name);
			
			f.setAccessible(true);
			Object val = f.get(cls);
			if(val != null && val.getClass().getName().startsWith("em")){
				Field tmp = val.getClass().getField("item");
				tmp.setAccessible(true);
				val = tmp.get(val);
				tmp.setAccessible(false);
			}
			return val;
		}catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static Object getArrayValue(Object cls, Integer index){
		return Array.get(cls, index);
	}
	
	public static void evaluateInput(Object cls, String expr){
		List<Statement> stats = (List<Statement>) parseExpression(expr, false);
		evalStatements(stats, cls);
	}
	
	public static void resetVariables(){
		variables = new HashMap<Object, HashMap<String, Variable>>();
	}
	
	public static void main(String args[]){
		//evaluateInput(new JaDEN_App(), "System.out.println(true);");
		Expression exp = (Expression) parseExpression("board.x + posX * size", true);
		HashSet<String> dependsOn = new HashSet<String>();
		getDependents(exp, dependsOn);
		System.out.println(dependsOn);
		classFiles();
	}
	public static void getDependents(Expression e, HashSet<String> dep){
		if(e instanceof UnaryExpr) {
			getDependents(((UnaryExpr) e).getExpr(), dep);
		}else if(e instanceof BinaryExpr) {
			getDependents(((BinaryExpr) e).getLeft(), dep);
			getDependents(((BinaryExpr) e).getRight(), dep);
		}else if(e instanceof NameExpr) {
			dep.add(((NameExpr) e).getName());
		}else if(e instanceof EnclosedExpr) {
			getDependents(((EnclosedExpr) e).getInner(), dep);
		}else if(e instanceof ObjectCreationExpr) {
			List<Expression> args = ((ObjectCreationExpr) e).getArgs();
			for(Expression arg : args) getDependents(arg, dep);
		}else if(e instanceof MethodCallExpr) {
			List<Expression> args = ((MethodCallExpr) e).getArgs();
			for(Expression arg : args) getDependents(arg, dep);
		}else if(e instanceof FieldAccessExpr) {
			Expression scope = ((FieldAccessExpr) e).getScope();
			if(scope != null) getDependents(scope, dep);
			else dep.add(((FieldAccessExpr) e).getField());
		}else if(e instanceof ConditionalExpr) {
			getDependents(((ConditionalExpr) e).getCondition(), dep);
			getDependents(((ConditionalExpr) e).getThenExpr(), dep);
			getDependents(((ConditionalExpr) e).getElseExpr(), dep);
		}else System.out.println(e.getClass().getName());
	}
	
	public static File locateSource(Object obj){
		String name = (obj instanceof Class ? (Class) obj : obj.getClass()).getName();
		File root =  new File(System.getProperty("user.dir"));
		
		return findFile(root, name + ".java");
	}
	
	private static File findFile(File dir, String name){
		if(dir.isFile()){
			if(dir.getName().equals(name)) return dir;
			return null;
		}
		
		for(File f : dir.listFiles()){
			File found = findFile(f, name);
			if(found != null) return found;
		}
		return null;
	}
	
	private static Class[] classFiles(){
		String list = System.getProperty("java.class.path");
		File root =  null;
		if(list.contains(";")) root = new File(list.substring(0, list.indexOf(";")));
		else if(list.contains(":")) root = new File(list.substring(0, list.indexOf(":")));
		else root = new File(list);
		
		ArrayList<Class> classes = new ArrayList<Class>();
		
		for(File f : root.listFiles()){
			if(f.getName().contains("$") || f.getName().startsWith("em")) continue;
			
			if(f.isFile() && f.getName().endsWith(".class")){
				try {
					classes.add(Class.forName(f.getName().substring(0, f.getName().length() - 6)));
				} catch (ClassNotFoundException e) {}
			}
		}
		
		return (Class[]) classes.toArray(new Class[]{});
	}
	
	public static Object parseExpression(String expr, boolean isReturn){
		//Write class evaluation code
		String code = "";
		code += "public class test{\n";
		code += "	public " + (isReturn ? "Object " : "void ") + "getExpression(){\n";
		if(isReturn) code += "		return " + expr + ";\n";
		else code += "		" + expr + "\n";
		code += "	}\n";
		code += "}\n";
		
		//PARSE
		try {
			InputStream stream = new ByteArrayInputStream(code.getBytes());
			CompilationUnit comp = JavaParser.parse(stream);
			TypeDeclaration types = comp.getTypes().get(0);
			MethodDeclaration methods = (MethodDeclaration) types.getMembers().get(0);
			System.out.println(methods.getName());
			Object ret = null;
			if(isReturn) ret = ((ReturnStmt) methods.getBody().getStmts().get(0)).getExpr();
			else ret = methods.getBody().getStmts();
			
			System.out.println("Parsed as: " + ret);
			return ret;
		} catch (Exception e) {
			System.out.println("Unable to evaluate expression");
			e.printStackTrace();
		}
		return null;
	}
	
	public static Variable createVariable(Object who, String name){
		HashMap<String, Variable> store = getStorage(who);
		
		Variable var = new Variable(name, "int", 0);
		store.put(name, var);
		if(who instanceof Class) addDynamicVariable((Class) who, name);
		return var;
	}
	
	public static void setVariable(Object who, String name, Object val){
		try{
			try{
				String setMethod = "set" + LiveEdit.formatField(name);
				Method setV = who.getClass().getDeclaredMethod(setMethod, new Class[]{LiveEdit.getType(val.getClass())});
				setV.setAccessible(true);
				setV.invoke(who, new Object[]{val});
				return;
			}catch(Exception me){
				Field f = who.getClass().getDeclaredField(name);
				f.setAccessible(true);
				f.set((Modifier.isStatic(f.getModifiers()) ? null : who), val);
				return;
			}
		}catch(Exception e){}
		
		HashMap<String, Variable> store = getStorage(who);
		
		Variable var = store.get(name);
		if(var == null){
			var = createVariable(who, name);
		}
		
		var.set(val);
		updatedVariable(who, name);
	}
	
	public static void updateDependency(Object who, String name, String def){ // only for dynamic variables
		HashMap<String, Variable> store = getStorage(who);
		
		Variable var = store.get(name);
		if(var == null){
			var = createVariable(who, name);
			var.setClass(who);
		}
		
		var.setDefinition(def);
		if(who instanceof Class) updateDynamicVariable((Class) who, name, def);
		else{
			var.update(who);
			updatedVariable(who, name);
		}
	}
	
	public static HashMap<String, Variable> getStorage(Object who){
		HashMap<String, Variable> store = variables.get(who);
		if(store == null){
			store = new HashMap<String, Variable>();
			variables.put(who, store);
		}
		return store;
	}
	public static Object evaluateExpression(Expression expr, Object cls){
		return evalExp(expr, cls);
	}
	public static Object evaluateExpression(String expr, Object cls){
		Expression exp  = (Expression) Evaluator.parseExpression(expr, true);
		return evalExp(exp, cls);
	}
	
	private static void evalStatements(List<Statement> statements, Object cls){
		for(Statement stat : statements){
			evalExp(((ExpressionStmt) stat).getExpression(), cls);
		}
	}
	private static Object evalExp(Expression e, Object cls){
		if(e instanceof IntegerLiteralExpr) return Integer.valueOf(((IntegerLiteralExpr) e).getValue());
		else if(e instanceof DoubleLiteralExpr) return Double.valueOf(((DoubleLiteralExpr) e).getValue());
		else if(e instanceof BooleanLiteralExpr) return Boolean.valueOf(((BooleanLiteralExpr) e).getValue());
		//else if(e instanceof CharLiteralExpr) return (char) Integer.valueOf(((CharLiteralExpr) e).getValue()).;
		else if(e instanceof StringLiteralExpr) return ((StringLiteralExpr) e).getValue();
		else if(e instanceof UnaryExpr) return evalUnaryOp((UnaryExpr) e, cls);
		else if(e instanceof BinaryExpr) return evalOp((BinaryExpr) e, cls);
		else if(e instanceof NameExpr) return evalName((NameExpr) e, cls);
		else if(e instanceof FieldAccessExpr) return evalField((FieldAccessExpr) e, cls);
		else if(e instanceof EnclosedExpr) return evalExp(((EnclosedExpr) e).getInner(), cls);
		else if(e instanceof MethodCallExpr) return evalMethodCall((MethodCallExpr) e, cls);
		else if(e instanceof AssignExpr) return evalAssign((AssignExpr) e, cls);
		else if(e instanceof ObjectCreationExpr) return evalNewObject((ObjectCreationExpr) e, cls);
		else if(e instanceof ConditionalExpr) return evalConditional((ConditionalExpr) e, cls);
		else System.out.println(e.getClass().getName());
		
		return null;
	}
	private static Object evalOp(BinaryExpr e, Object cls){
		Object left = evalExp(e.getLeft(), cls);
		Object right = evalExp(e.getRight(), cls);
		Operator op = e.getOperator();
		if(left == null || right == null) return null;
		
		if(left instanceof Double || right instanceof Double) return doubleOp((Double) left, (Double) right, op);
		else if(left instanceof Integer && right instanceof Integer) return integerOp((Integer) left, (Integer) right, op);
		else if(left instanceof String || right instanceof String) return stringOp(left.toString(), right.toString(), op);
		
		return null;
	}
	private static Object evalUnaryOp(UnaryExpr e, Object cls){
		Object value = evalExp(e.getExpr(), cls);
		UnaryExpr.Operator op = e.getOperator();
		if(value == null) return null;
		
		if(op.equals(op.negative)){
			if(value instanceof Double) return - (Double) value;
			else if(value instanceof Integer) return - (Integer) value;
			System.out.println("Cant perform negative unary operation on " + value);
		}else{
			System.out.println("Cant perform unary operation");
		}
		
		
		return null;
	}
	private static Object integerOp(Integer l, Integer r, Operator op){
		if(op.equals(Operator.plus)) return l + r;
		if(op.equals(Operator.minus)) return l - r;
		if(op.equals(Operator.times)) return l * r;
		if(op.equals(Operator.divide)) return l / r;
		if(op.equals(Operator.equals)) return l == r;
		if(op.equals(Operator.less)) return l < r;
		if(op.equals(Operator.lessEquals)) return l <= r;
		if(op.equals(Operator.greater)) return l > r;
		if(op.equals(Operator.greaterEquals)) return l >= r;
		
		return null;
	}
	private static Object doubleOp(Double l, Double r, Operator op){
		if(op.equals(Operator.plus)) return l + r;
		if(op.equals(Operator.minus)) return l - r;
		if(op.equals(Operator.times)) return l * r;
		if(op.equals(Operator.divide)) return l / r;
		if(op.equals(Operator.equals)) return l == r;
		if(op.equals(Operator.less)) return l < r;
		if(op.equals(Operator.lessEquals)) return l <= r;
		if(op.equals(Operator.greater)) return l > r;
		if(op.equals(Operator.greaterEquals)) return l >= r;
		
		return null;
	}
	private static String stringOp(String l, String r, Operator op){
		if(op.equals(Operator.plus)) return l + r;
		else{
			System.out.println("Operator not valid for strings: " + op);
		}
		
		return null;
	}
	
	private static Object evalAssign(AssignExpr e, Object cls){
		Object left = evalExp(e.getTarget(), cls);
		Object right = evalExp(e.getValue(), cls);
		setVariable(cls, e.getTarget().toString(), right);
		
		return right;
	}
	
	private static Object evalField(FieldAccessExpr e, Object cls){
		cls = evalExp(e.getScope(), cls);
		return getFieldValue(cls, e.getField());
	}
	
	private static Object evalName(NameExpr e, Object cls){
		String name = e.getName();
		
		//try get dynamically created variable
		HashMap<String, Variable> store = getStorage(cls);
		Variable var = store.get(name);
		if(var != null) return var.get();
		
		try{//Try get the static class, TODO
			String actual = PackageConverter.convertClass(name);
			return Class.forName(actual);
		}catch(Exception ee){}
		
		try{//Get the defined field in the class
			Field f;
			if(cls instanceof Class) f = ((Class) cls).getDeclaredField(name);
			else f = cls.getClass().getDeclaredField(name);
			f.setAccessible(true);
			return f.get(cls);
		}catch(Exception ee){}
		
		try{//Get the field in the any super class
			Field f = cls.getClass().getField(name);
			f.setAccessible(true);
			return f.get(cls);
		}catch(Exception ee){}
		
		return null;
	}
	
	private static Object evalConditional(ConditionalExpr e, Object cls){
		boolean condition = (Boolean) evalExp(e.getCondition(), cls);
		
		if(condition) return evalExp(e.getThenExpr(), cls);
		else return evalExp(e.getElseExpr(), cls);
	}
	
	private static Object evalMethodCall(MethodCallExpr e, Object cls){
		String name = e.getName();
		Object[] args = evalArgs(e.getArgs(), cls);
		Class[] types = new Class[args.length];
		
		if(e.getScope() != null){
			cls = evalExp(e.getScope(), cls);
		}
		
		for(int i = 0; i < args.length; i++){
			types[i] = LiveEdit.getType(args[i].getClass());
		}
		
		try{//Get the defined field in the class
			Method m = null;
			Method methods[]= null;
			if(cls instanceof Class) methods = ((Class) cls).getDeclaredMethods();
			else methods = cls.getClass().getDeclaredMethods();
			
			for(Method meth : methods){
				if(!meth.getName().equals(name) || meth.getParameterTypes().length != types.length) continue;
				
				System.out.println(name);
				boolean isOk = true;
				for(int i = 0; i < types.length; i++){
					if(!meth.getParameterTypes()[i].isInstance(types[i])) {
						isOk = false;
						break;
					}
				}
				
				if(isOk){
					m = meth;
					break;
				}
			}
			
			m.setAccessible(true);
			return m.invoke((cls instanceof Class ? null : cls), args);
		}catch(Exception ee){}
		
		try{
			Method m = null;
			if(cls instanceof Class) m = ((Class) cls).getMethod(name, types);
			else m = cls.getClass().getMethod(name, types);
			
			m.setAccessible(true);
			try{
				return m.invoke((cls instanceof Class ? null : cls), args);
			}catch(Exception ee){
				ee.printStackTrace();
				return null;
			}
		}catch(Exception ee){}
		
		System.out.println("No method found: " + name + (types.length == 0 ? "[]" : e.getArgs()) + " in " + cls);
		
		return null;
	}
	
	private static Object evalNewObject(ObjectCreationExpr e, Object cls){
		String name = e.getType().getName();
		Object[] args = evalArgs(e.getArgs(), cls);
		Class[] types = new Class[args.length];
		
		if(e.getScope() != null){
			cls = evalExp(e.getScope(), cls);
		}
		
		for(int i = 0; i < args.length; i++){
			types[i] = LiveEdit.getType(args[i].getClass());
		}
		
		try{//Get the defined field in the class
			Class c = Class.forName(name);
			Object inst = c.newInstance();
			Constructor con = c.getConstructor(types);
			
			return con.newInstance(args);
		}catch(Exception ee){}
		
		System.out.println("No class found with contructor: " + name + (types.length == 0 ? "[]" : e.getArgs()));
		
		return null;
	}
	
	private static Object[] evalArgs(List<Expression> args, Object cls){
		if(args == null) return new Object[]{};
		
		Object[] result = new Object[args.size()];
		for(int i = 0; i < args.size(); i++){
			result[i] = evalExp(args.get(i), cls);
		}
		return result;
	}
	
	public static void updatedVariable(Object cls, String name) {
		HashMap<String, Variable> store = variables.get(cls);
		if(store != null){
			for(Variable var : store.values()){
				if(var.dependsOn != null && !var.dependsOn.contains(name)) continue;
				var.update(cls);
				Evaluator.updatedVariable(cls, var.name);
			}
		}
	}
	
	private static Object isNull(Object obj, Object rep){
		if(obj == null) return rep;
		return obj;
	}
	
	public static boolean compileModel(String name, String code){
		String className = name;
		
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
		if(compiler == null) return false;
		
		List<String> optionList = new ArrayList<String>();
		optionList.addAll(Arrays.asList("-d",System.getProperty("user.dir")+"/bin"));

		StringWriter writer = new StringWriter();
		PrintWriter out = new PrintWriter(writer);
		out.println(code);
		out.close();
		JavaFileObject file = new JavaSourceFromString(className, writer.toString());

		Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
		CompilationTask task = compiler.getTask(null, null, diagnostics, optionList, null, compilationUnits);

		boolean success = task.call();
		if(!success){
			System.out.println("Error Building Model");
			return false;
		}
		
		return true;
	}
}

class PackageConverter{
	private static HashMap<String, String> converted = new HashMap<String, String>();
	static{
		converted.put("System", "java.lang.System");
		converted.put("Color", "java.awt.Color");
	}
	
	public static String convertClass(String name){
		String actual = converted.get(name);
		if(actual == null) return name;
		return actual;
	}
}

//Code from: http://stackoverflow.com/questions/3971534/how-to-force-java-to-reload-class-upon-instantiation
class Reloader extends ClassLoader {
    @Override
    public Class<?> loadClass(String s) {
        return findClass(s);
    }

    @Override
    public Class<?> findClass(String s) {
        try {
            byte[] bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException ioe) {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException ignore) { }
            ioe.printStackTrace(System.out);
            return null;
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
}