/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation and others.
 * 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:
 *     Matthew Webster - initial implementation
 *******************************************************************************/
package org.aspectj.testing.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

public class TestServer implements Runnable {

	private static final boolean debug = Boolean.getBoolean("org.aspectj.testing.server.debug");

	private boolean exitOnError = true;
	private File workingDirectory;
	private ClassLoader rootLoader;
	private Map loaders = new HashMap();

	private String mainClass = "UnknownClass";
	private String mainLoader = "UnknownLoader";
	
	public void initialize () throws IOException {
		createRootLoader();
		loadConfiguration();
	}
	
	private void loadConfiguration () throws IOException {
		File file = new File(workingDirectory,"server.properties");
		Properties props = new Properties();
		FileInputStream in = new FileInputStream(file);
		props.load(in);
		in.close();
		
		Enumeration enu = props.propertyNames();
		while (enu.hasMoreElements()) {
			String key = (String)enu.nextElement();
			if (key.startsWith("loader.")) {
				createLoader(props.getProperty(key));
			}
			else if (key.equals("main")) {
				StringTokenizer st = new StringTokenizer(props.getProperty(key),",");
				mainClass = st.nextToken();
				mainLoader = st.nextToken();
			}
		}
	}
	
	private void createLoader (String property) throws IOException {
		ClassLoader parent = rootLoader;

		StringTokenizer st = new StringTokenizer(property,",");
		String name = st.nextToken();
		String classpath = st.nextToken();
		if (st.hasMoreTokens()) {
			String parentName = st.nextToken();
			parent = (ClassLoader)loaders.get(parentName);
			if (parent == null) error("No such loader: " + parentName);
		}

		List urlList = new ArrayList();
		st = new StringTokenizer(classpath,";");
		while (st.hasMoreTokens()) {
			String fileName = st.nextToken();
			File file = new File(workingDirectory,fileName).getCanonicalFile();
			if (!file.exists()) error("Missing or invalid file: " + file.getPath());
			URL url = file.toURL();
			urlList.add(url);
		}
		URL[] urls = new URL[urlList.size()];
		urlList.toArray(urls);
		ClassLoader loader = new URLClassLoader(urls, parent);
		if (debug) System.err.println("? TestServer.createLoader() loader=" + loader + ", name='" + name + "', urls=" + urlList + ", parent=" + parent);

		loaders.put(name,loader);
	}
	
	private void createRootLoader () throws IOException {
		List urlList = new ArrayList();
		
		/* Sandbox */
		URL url = workingDirectory.getCanonicalFile().toURL();
		urlList.add(url);

		/* AspectJ runtime */
		URL[] urls = ((URLClassLoader)getClass().getClassLoader()).getURLs();
		for (int i = 0; i < urls.length; i++) {
			url = urls[i];
			String file = url.getFile();
			if (debug) System.err.println("? TestServer.createRootLoader() " + file);
			if (file.indexOf("runtime") != -1 || file.indexOf("aspectjrt") != -1 || file.indexOf("aspectj5rt") != -1) {
				urlList.add(url);
			}
		}
		
		urls = new URL[urlList.size()];
		urlList.toArray(urls);
		ClassLoader parent = getClass().getClassLoader().getParent();
		rootLoader = new URLClassLoader(urls,parent);
		if (debug) System.err.println("? TestServer.createRootLoader() loader=" + rootLoader + ", urlList=" + urlList + ", parent=" + parent);
	}
	
	public void setExitOntError (boolean b) {
		exitOnError = b;
	}
	
	public void setWorkingDirectory (String name) {
		workingDirectory = new File(name);
		if (!workingDirectory.exists()) error("Missing or invalid working directory: " + workingDirectory.getPath());
	}
	
	public static void main(String[] args) throws Exception {
		System.out.println("Starting ...");
		
		TestServer server = new TestServer();
		server.setWorkingDirectory(args[0]);
		server.initialize();
		
		Thread thread = new Thread(server,"application");
		thread.start();
		thread.join();
		
		System.out.println("Stopping ...");
	}

	public void run() {
		System.out.println("Running " + mainClass);
		runClass(mainClass,(ClassLoader)loaders.get(mainLoader));
	}

	private void runClass (String className, ClassLoader classLoader) {
		try {
			Class clazz = Class.forName(className,false,classLoader);
			invokeMain(clazz,new String[] {});
		}
		catch (ClassNotFoundException ex) {
			ex.printStackTrace();
			error(ex.toString());
		}
	}
	
	public void invokeMain (Class clazz, String[] args)
	{
		Class[] paramTypes = new Class[1];
		paramTypes[0] = args.getClass();
	
		try {
			Method method = clazz.getDeclaredMethod("main",paramTypes);
			Object[] params = new Object[1];
			params[0] = args;
			method.invoke(null,params);
		}
		catch (InvocationTargetException ex) {
			Throwable th = ex.getTargetException();
			th.printStackTrace();
			error(th.toString());
		}
		catch (Throwable th) {
			th.printStackTrace();
			error(th.toString());
		}
	}
	
	private void error (String message) {
		System.out.println(message);
		if (exitOnError) System.exit(0);
	}
}
