/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/


package org.aspectj.util;

import java.io.File;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
//import java.util.StringTokenizer;

public class Reflection {
    public static final Class<?>[] MAIN_PARM_TYPES = new Class[] {String[].class};    

	private Reflection() {
	}
	
	public static Object invokestaticN(Class<?> class_, String name, Object[] args) {
		return invokeN(class_, name, null, args);
	}

	public static Object invoke(Class<?> class_, Object target, String name, Object arg1, Object arg2) {
		return invokeN(class_, name, target, new Object[] { arg1, arg2 });
	}
	
	public static Object invoke(Class<?> class_, Object target, String name, Object arg1, Object arg2, Object arg3) {
		return invokeN(class_, name, target, new Object[] { arg1, arg2, arg3 });
	}
	
	
	public static Object invokeN(Class<?> class_, String name, Object target, Object[] args) {
		Method meth = getMatchingMethod(class_, name, args);
		try {
			return meth.invoke(target, args);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e.toString());			
		} catch (InvocationTargetException e) {
			Throwable t = e.getTargetException();
			if (t instanceof Error) throw (Error)t;
			if (t instanceof RuntimeException) throw (RuntimeException)t;
			t.printStackTrace();
			throw new RuntimeException(t.toString());
		}
	}


	public static Method getMatchingMethod(Class<?> class_, String name, Object[] args) {
		Method[] meths = class_.getMethods();
		for (int i=0; i < meths.length; i++) {
			Method meth = meths[i];
			if (meth.getName().equals(name) && isCompatible(meth, args)) {
				return meth;
			}
		}
		return null;
	}

	private static boolean isCompatible(Method meth, Object[] args) {
		// ignore methods with overloading other than lengths
		return meth.getParameterTypes().length == args.length;
	}


	

	public static Object getStaticField(Class<?> class_, String name) {
		try {
			return class_.getField(name).get(null);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("unimplemented");
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("unimplemented");
		}
	}

    public static void runMainInSameVM(
            String classpath,
            String className, 
            String[] args) 
            throws SecurityException, NoSuchMethodException, 
            IllegalArgumentException, IllegalAccessException, 
            InvocationTargetException, ClassNotFoundException {
        LangUtil.throwIaxIfNull(className, "class name");
        if (LangUtil.isEmpty(classpath)) {
            Class<?> mainClass = Class.forName(className);
            runMainInSameVM(mainClass, args);
            return;            
        }
        ArrayList<File> dirs = new ArrayList<File>();
        ArrayList<File> libs = new ArrayList<File>();
        ArrayList<URL> urls = new ArrayList<URL>();
        String[] entries = LangUtil.splitClasspath(classpath);
        for (int i = 0; i < entries.length; i++) {
            String entry = entries[i];
            URL url = makeURL(entry);
            if (null != url) {
                urls.add(url);
            }
            File file = new File(entries[i]);
// tolerate bad entries b/c bootclasspath sometimes has them
//            if (!file.canRead()) {
//                throw new IllegalArgumentException("cannot read " + file);
//            }
            if (FileUtil.isZipFile(file)) {
                libs.add(file);
            } else if (file.isDirectory()) {
                dirs.add(file);
            } else {
                // not URL, zip, or dir - unsure what to do
            }
        }
        File[] dirRa = (File[]) dirs.toArray(new File[0]);
        File[] libRa = (File[]) libs.toArray(new File[0]);
        URL[] urlRa = (URL[]) urls.toArray(new URL[0]);
        runMainInSameVM(urlRa, libRa, dirRa, className, args);
    }
        
    public static void runMainInSameVM(
            URL[] urls,
            File[] libs, 
            File[] dirs, 
            String className, 
            String[] args) 
            throws SecurityException, NoSuchMethodException, 
            IllegalArgumentException, IllegalAccessException, 
            InvocationTargetException, ClassNotFoundException {
        LangUtil.throwIaxIfNull(className, "class name");
        LangUtil.throwIaxIfNotAssignable(libs, File.class, "jars");
        LangUtil.throwIaxIfNotAssignable(dirs, File.class, "dirs");
        URL[] libUrls = FileUtil.getFileURLs(libs);
        if (!LangUtil.isEmpty(libUrls)) {
            if (!LangUtil.isEmpty(urls)) {
                URL[] temp = new URL[libUrls.length + urls.length];
                System.arraycopy(urls, 0, temp, 0, urls.length);
                System.arraycopy(urls, 0, temp, libUrls.length, urls.length);
                urls = temp;
            } else {
                urls = libUrls;
            }
        }
        UtilClassLoader loader = new UtilClassLoader(urls, dirs);
        Class<?> targetClass = null;
        try {
            targetClass = loader.loadClass(className);
        } catch (ClassNotFoundException e) {
            String s = "unable to load class " + className
                + " using class loader " + loader; 
            throw new ClassNotFoundException(s);
        }
        Method main = targetClass.getMethod("main", MAIN_PARM_TYPES);
        main.invoke(null, new Object[] { args });
    }
    
    public static void runMainInSameVM(Class<?> mainClass, String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        LangUtil.throwIaxIfNull(mainClass, "main class");
        Method main = mainClass.getMethod("main", MAIN_PARM_TYPES);
        main.invoke(null, new Object[] { args });
    }

    /** @return URL if the input is valid as such */
    private static URL makeURL(String s) {
        try {
            return new URL(s);            
        } catch (Throwable t) {
            return null;
        }
    }

}
