/*
 * Copyright ThinkTank Maths Limited 2006, 2007
 *
 * This file is free software: you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this file.
 * If not, see <http://www.gnu.org/licenses/>.
 * 
 * For the avoidance of doubt, source code generated by this program is not considered
 * a derivative work.
 */
package org.netlib.generate;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.netlib.util.StringW;
import org.netlib.util.booleanW;
import org.netlib.util.doubleW;
import org.netlib.util.floatW;
import org.netlib.util.intW;

import java.net.MalformedURLException;

/**
 * Due to the depressing number of LAPACK routines, it is much more efficient to
 * auto-generate the Java code for the wrapper and corresponding Java and JNI
 * implementations.
 * <p>
 * Warning: this code is very monolithic and horrible. It was written in a hurry for a
 * one-off run. It is probably very hard to understand and not at all elegant. Efforts
 * have been best spent on making sure the output code is elegant as it is the stuff that
 * actually matters.
 * 
 * @author Samuel Halliday
 */
class JavaGenerator {

	class Doublet<A, B> {
		public A a;
		public B b;

		public Doublet(A a, B b) {
			this.a = a;
			this.b = b;
		}

		@Override
		public String toString() {
			return a + " " + b;
		}
	}

	interface IClassFilter {
		/**
		 * @param className
		 * @return true if the class with the given name is acceptable.
		 */
		public boolean isValid(String className);
	}

	private static final Map<Type, String> classDefs =
			new HashMap<Type, String>();

	static {
		/*
		 * The complete(ish) list of parameter types for F2J methods. Typically an array
		 * is followed by an int specifying the offset.
		 */
		classDefs.put(String.class, "String");
		classDefs.put(Integer.TYPE, "int");
		classDefs.put(Double.TYPE, "double");
		classDefs.put(Boolean.TYPE, "boolean");
		classDefs.put(Float.TYPE, "float");
		classDefs.put(intW.class, "intW");
		classDefs.put(doubleW.class, "doubleW");
		classDefs.put(booleanW.class, "booleanW");
		classDefs.put(floatW.class, "floatW");
		classDefs.put(StringW.class, "StringW");
		classDefs.put(int[].class, "int[]");
		classDefs.put(double[].class, "double[]");
		classDefs.put(boolean[].class, "boolean[]");
		classDefs.put(float[].class, "float[]");
	}

	public static void main(String[] args) throws Exception {
		// create the BLAS wrapper
		JavaGenerator blas =
				new JavaGenerator("org.netlib.blas", "BLAS",
					"lib/f2j/jlapack-0.8-javadoc.zip");
		writeToFile(blas.getAbstractWrapper(), "src/org/netlib/blas/BLAS.java");
		writeToFile(blas.getJavaWrapper(), "src/org/netlib/blas/JBLAS.java");
		writeToFile(blas.getJNIWrapper(), "src/org/netlib/blas/NativeBLAS.java");
		writeToFile(blas.getJNIC(), "jni/org_netlib_blas_NativeBLAS.c");

		// create the LAPACK wrapper
		JavaGenerator lapack =
				new JavaGenerator("org.netlib.lapack", "LAPACK",
					"lib/f2j/jlapack-0.8-javadoc.zip");
		writeToFile(lapack.getAbstractWrapper(),
			"src/org/netlib/lapack/LAPACK.java");
		writeToFile(lapack.getJavaWrapper(),
			"src/org/netlib/lapack/JLAPACK.java");
		writeToFile(lapack.getJNIWrapper(),
			"src/org/netlib/lapack/NativeLAPACK.java");
		writeToFile(lapack.getJNIC(), "jni/org_netlib_lapack_NativeLAPACK.c");

		// create the ARPACK wrapper
		// TODO: add the ARPACK javadocs here
		JavaGenerator arpack =
				new JavaGenerator("org.netlib.arpack", "ARPACK", "");
		writeToFile(arpack.getAbstractWrapper(),
			"src/org/netlib/arpack/ARPACK.java");
		writeToFile(arpack.getJavaWrapper(),
			"src/org/netlib/arpack/JARPACK.java");
		writeToFile(arpack.getJNIWrapper(),
			"src/org/netlib/arpack/NativeARPACK.java");
		writeToFile(arpack.getJNIC(), "jni/org_netlib_arpack_NativeARPACK.c");
	}

	static void writeToFile(String string, String filename) throws IOException {
		FileOutputStream out = new FileOutputStream(filename);
		OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
		writer.write(string);
		writer.close();
	}

	/**
	 * The generated files are licenced under the BSD licence, the same as the netlib
	 * sources.
	 */
	private final String COPYRIGHT =
			"/*\n * Copyright 2003-2007 Keith Seymour.\n"
					+ " * Copyright 1992-2007 The University of Tennessee. All rights reserved.\n"
					+ " * \n"
					+ " * Redistribution and use in source and binary forms, with or without\n"
					+ " * modification, are permitted provided that the following conditions are\n"
					+ " * met:\n"
					+ " * \n"
					+ " * - Redistributions of source code must retain the above copyright\n"
					+ " *   notice, this list of conditions and the following disclaimer.\n"
					+ " * \n"
					+ " * - Redistributions in binary form must reproduce the above copyright\n"
					+ " *   notice, this list of conditions and the following disclaimer listed\n"
					+ " *   in this license in the documentation and/or other materials\n"
					+ " *   provided with the distribution.\n"
					+ " * \n"
					+ " * - Neither the name of the copyright holders nor the names of its\n"
					+ " *   contributors may be used to endorse or promote products derived from\n"
					+ " *   this software without specific prior written permission.\n"
					+ " * \n"
					+ " * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
					+ " * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
					+ " * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n"
					+ " * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"
					+ " * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n"
					+ " * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n"
					+ " * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
					+ " * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
					+ " * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
					+ " * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
					+ " * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
					+ " * \n" + " * This file was auto-generated by the "
					+ JavaGenerator.class.getCanonicalName()
					+ "\n * program, a part of netlib-java.\n * \n"
					+ " * @see http://code.google.com/p/netlib-java/\n" + " */\n";

	private final String javadocs;

	private final String javaWrapper;

	private final String jniC;

	private final String jniWrapper;

	private final String pkg;

	private String post;

	private String pre;

	private final String topWrapper;

	private final String wrapperName;

	/**
	 * Some F2J methods take parameters that I don't know how to deal with in the JNI, so
	 * we'll simply make sure the wrapper always sends them to the F2J implementation.
	 */
	private final Set<String> dontUseJNI = new HashSet<String>();

	/**
	 * @param packageName
	 * @param wrapperName
	 * @param javadocs
	 * @throws IOException
	 */
	JavaGenerator(String packageName, String wrapperName, String javadocs)
			throws IOException {
		pkg = packageName;
		this.javadocs = javadocs;
		this.wrapperName = wrapperName;
		// hack for fortran/C names. BLAS is different to the other netlib libs
		if (wrapperName.equals("BLAS")) {
			pre = "cblas_";
			post = "";
		} else {
			pre = "";
			post = "_";
		}

		List<Class<?>> classes = getClasses(pkg, new IClassFilter() {
			public boolean isValid(String className) {
				assert className != null;
				assert className.startsWith(pkg);
				String shortName = className.substring(pkg.length() + 1);
				if (shortName.toUpperCase().equals(shortName))
					// all caps mean the "convenience" F2J libs
					// or the wrapper class we are trying to create!
					return false;
				if (shortName.startsWith("Native"))
					// these are the JNI classes
					return false;
				if (shortName.contains("$"))
					// inner classes
					return false;
				if (shortName.endsWith("Test"))
					// test cases
					return false;
				return true;
			}
		});

		List<Method> methods = new ArrayList<Method>();
		for (Class<?> clazz : classes) {
			Method[] ms = clazz.getDeclaredMethods();
			for (Method m : ms) {
				// F2J methods have the same name as their containing class
				String name = m.getName();
				String className = clazz.getSimpleName();
				if (!name.equals(className.toLowerCase()))
					continue;
				methods.add(m);
				break;
			}
		}
		StringBuilder topWrapper = new StringBuilder();
		StringBuilder javaWrapper = new StringBuilder();
		StringBuilder jniWrapper = new StringBuilder();
		StringBuilder jniC = new StringBuilder();
		topWrapper.append(COPYRIGHT);
		javaWrapper.append(COPYRIGHT);
		jniWrapper.append(COPYRIGHT);
		jniC.append(COPYRIGHT);

		topWrapper.append("package " + pkg + ";\n\n");
		topWrapper.append("import java.util.logging.Logger;\n");
		topWrapper.append("import org.netlib.util.StringW;\n");
		topWrapper.append("import org.netlib.util.booleanW;\n");
		topWrapper.append("import org.netlib.util.doubleW;\n");
		topWrapper.append("import org.netlib.util.floatW;\n");
		topWrapper.append("import org.netlib.util.intW;\n\n");
		topWrapper.append("/**\n");
		topWrapper.append(" * "
				+ wrapperName
				+ " provider which will attempt to access a native implementation\n");
		topWrapper.append(" * and falling back to use F2J if none is available.\n *\n");
		topWrapper.append(" * @see http://sourceforge.net/projects/f2j\n");
		topWrapper.append(" * @see http://www.netlib.org/"
				+ wrapperName.toLowerCase() + "/\n");
		topWrapper.append(" * @author Samuel Halliday\n");
		topWrapper.append(" */\n");
		topWrapper.append("public abstract class " + wrapperName + " {\n\n");
		topWrapper.append(topWrapperLoader(wrapperName));

		javaWrapper.append("package " + pkg + ";\n\n");
		javaWrapper.append("import java.util.logging.Logger;\n");
		javaWrapper.append("import org.netlib.util.StringW;\n");
		javaWrapper.append("import org.netlib.util.booleanW;\n");
		javaWrapper.append("import org.netlib.util.doubleW;\n");
		javaWrapper.append("import org.netlib.util.floatW;\n");
		javaWrapper.append("import org.netlib.util.intW;\n\n");
		javaWrapper.append("/**\n");
		javaWrapper.append(" * " + wrapperName
				+ " provider implementation which uses F2J.\n *\n");
		javaWrapper.append(" * @see http://sourceforge.net/projects/f2j\n");
		javaWrapper.append(" * @author Samuel Halliday\n");
		javaWrapper.append(" */\n");
		javaWrapper.append("final class J" + wrapperName + " extends "
				+ wrapperName + " {\n\n");
		javaWrapper.append("\tstatic final " + wrapperName
				+ " INSTANCE = new J" + wrapperName + "();\n\n");
		javaWrapper.append("\tprivate J" + wrapperName + "() {\n");
		javaWrapper.append("\t}\n\n");

		jniWrapper.append("package " + pkg + ";\n\n");
		jniWrapper.append("import java.util.logging.Logger;\n");
		jniWrapper.append("import org.netlib.util.StringW;\n");
		jniWrapper.append("import org.netlib.util.booleanW;\n");
		jniWrapper.append("import org.netlib.util.doubleW;\n");
		jniWrapper.append("import org.netlib.util.floatW;\n");
		jniWrapper.append("import org.netlib.util.intW;\n");
		jniWrapper.append("import org.netlib.utils.JNIMethods;\n\n");
		jniWrapper.append("/**\n");
		jniWrapper.append(" * "
				+ wrapperName
				+ " provider implementation which uses the Java Native Interface to access\n");
		jniWrapper.append(" * system netlib libraries.\n *\n");
		jniWrapper.append(" * @see http://www.netlib.org/\n");
		jniWrapper.append(" * @author Samuel Halliday\n");
		jniWrapper.append(" */\n");
		jniWrapper.append("final class Native" + wrapperName + " extends "
				+ wrapperName + " {\n\n");
		jniWrapper.append(jniWrapperLoader(wrapperName));

		jniC.append("\n#include \"f2j_jni.h\"\n");
		jniC.append("#include \"" + pkg.toLowerCase().replace(".", "_")
				+ "_Native" + wrapperName + ".h\"\n\n");

		for (Method method : methods) {
			System.out.println("Generating " + method.getName());
			Type[] params = method.getGenericParameterTypes();
			for (Type param : params) {
				if (classDefs.containsKey(param))
					continue;

				System.err.println(method.getName() + " has a " + param
						+ " parameter, so we'll not generate a JNI");
				dontUseJNI.add(method.getName());
			}

			String[] wrapper = createWrapper(method);
			topWrapper.append(wrapper[0]);
			javaWrapper.append(wrapper[1]);
			jniWrapper.append(wrapper[2]);
			jniC.append(wrapper[3]);
		}
		topWrapper.append("}\n");
		javaWrapper.append("}\n");
		jniWrapper.append("}\n");

		// System.out.print(topWrapper);
		this.topWrapper = topWrapper.toString();
		this.javaWrapper = javaWrapper.toString();
		this.jniWrapper = jniWrapper.toString();
		this.jniC = jniC.toString();
	}

	/**
	 * @return the Java source code for the top level abstract class for the package
	 */
	public String getAbstractWrapper() {
		return topWrapper;
	}

	/**
	 * @return the Java source code for the F2J delegate class
	 */
	public String getJavaWrapper() {
		return javaWrapper;
	}

	/**
	 * @return the C source code for the JNI code
	 */
	public String getJNIC() {
		return jniC;
	}

	/**
	 * @return the Java source code for the JNI delegate class
	 */
	public String getJNIWrapper() {
		return jniWrapper;
	}

	/**
	 * @param s
	 * @return a capitalised version of the input
	 */
	private String capitalize(String s) {
		if (s.length() == 0)
			return s;
		return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
	}

	/**
	 * @param method
	 * @param typeAndName
	 * @return
	 */
	private String createJavaWrapper(Method method,
			List<Doublet<String, String>> typeAndName) {
		// no need for javadocs
		StringBuilder builder = new StringBuilder();
		builder.append("\t@Override\n");
		builder.append("\tpublic "
				+ method.getReturnType().getName().toLowerCase() + " "
				+ method.getName() + "(");
		for (int i = 0; i < typeAndName.size(); i++) {
			Doublet<String, String> tm = typeAndName.get(i);
			String type = tm.a;
			String name = tm.b;
			builder.append(type + " " + name);
			if (i != typeAndName.size() - 1)
				builder.append(", ");
		}
		builder.append(") {\n\t\t");
		if (!method.getReturnType().equals(Void.TYPE))
			builder.append("return ");

		String n = method.getName();
		builder.append(pkg + "." + capitalize(n) + "." + n + "(");
		for (int i = 0; i < typeAndName.size(); i++) {
			Doublet<String, String> tm = typeAndName.get(i);
			String type = tm.a;
			String name = tm.b;
			builder.append(name);
			if (type.contains("[]")) {
				// offset is always zero
				builder.append(", 0");
			}
			if (i != typeAndName.size() - 1)
				builder.append(", ");
		}
		builder.append(");\n\t}\n\n");
		return builder.toString();
	}

	/**
	 * @param method
	 * @param typeAndName
	 * @return
	 */
	private String createJNICode(Method method,
			List<Doublet<String, String>> typeAndName) {
		List<String> names = new ArrayList<String>();
		List<String> types = new ArrayList<String>();
		for (Doublet<String, String> tn : typeAndName) {
			types.add(tn.a);
			names.add(tn.b);
		}
		String name = pkg + ".Native" + wrapperName + "." + method.getName();
		String rtn = method.getReturnType().getName().toLowerCase();
		// cblas hack
		String pre = this.pre;
		String post = this.post;
		if (wrapperName.equals("BLAS") && method.getName().equals("lsame")) {
			pre = "";
			post = "_";
		}

		JNIGenerator jni =
				new JNIGenerator(pre, post, name, types, names, rtn,
					wrapperName.equals("BLAS") ? true : false);
		return jni.getTemplate() + "\n";
	}

	/**
	 * @param method
	 * @param typeAndName
	 * @return
	 */
	private String createJNIWrapper(Method method,
			List<Doublet<String, String>> typeAndName) {
		// no need for javadocs
		StringBuilder builder = new StringBuilder();
		builder.append("\t@Override\n");
		builder.append("\tpublic native "
				+ method.getReturnType().getName().toLowerCase() + " "
				+ method.getName() + "(");
		for (int i = 0; i < typeAndName.size(); i++) {
			Doublet<String, String> tm = typeAndName.get(i);
			String type = tm.a;
			String name = tm.b;
			builder.append(type + " " + name);
			if (i != typeAndName.size() - 1)
				builder.append(", ");
		}
		builder.append(");\n\n");
		return builder.toString();
	}

	/**
	 * @param javadocs
	 * @param typeAndName
	 * @return
	 */
	private String createTopJavaDocs(String javadocs,
			List<Doublet<String, String>> typeAndName) {
		StringBuilder builder = new StringBuilder();
		builder.append("\t/**\n");
		builder.append(javadocs);
		for (int i = 0; i < typeAndName.size(); i++) {
			builder.append("\t * @param " + typeAndName.get(i).b + "\n");
		}
		builder.append("\t */\n");
		return builder.toString();
	}

	/**
	 * @param method
	 * @param typeAndName
	 * @param javadocs
	 * @return
	 */
	private String createTopWrapper(Method method,
			List<Doublet<String, String>> typeAndName, String javadocs) {
		StringBuilder builder = new StringBuilder();
		builder.append(createTopJavaDocs(javadocs, typeAndName));
		builder.append("\tpublic abstract "
				+ method.getReturnType().toString().toLowerCase() + " "
				+ method.getName() + "(");
		for (int i = 0; i < typeAndName.size(); i++) {
			builder.append(typeAndName.get(i).a + " " + typeAndName.get(i).b);
			if (i != typeAndName.size() - 1)
				builder.append(", ");
		}
		builder.append(");\n\n");
		return builder.toString();
	}

	/**
	 * A list of methods not defined in headers on OS X Leopard or Ubuntu Gutsy. These are
	 * most likely subroutines that should not be a part of the API, but we leave them in
	 * anyway.
	 */
	static final Set<String> notSupportedByJNI =
			new HashSet<String>(Arrays.asList(
			/* These are BLAS/LAPACK not defined on OS X Leopard */
			"disnan", "dlacn2", "dlag2s", "dlahr2", "dlaisnan", "dlaneg",
				"dlaqr0", "dlaqr1", "dlaqr2", "dlaqr3", "dlaqr4", "dlaqr5",
				"dlarra", "dlarrc", "dlarrd", "dlarrj", "dlarrk", "dlarrr",
				"dlazq3", "dlazq4", "dsgesv", "dstemr", "ilaver", "iparmq",
				"sisnan", "slag2d", "slahr2", "slaisnan", "slaneg", "slaqr0",
				"slaqr1", "slaqr2", "slaqr3", "slaqr4", "slaqr5", "slarra",
				"slarrc", "slarrj", "slarrr", "sstemr", "slacn2", "slarrd",
				"slarrk", "slazq3", "slazq4",
				/* These are BLAS not defined on Ubuntu Gutsy (LAPACK 3.0) */
				"lsame",
				/*
				 * These are LAPACK and not defined in LAPACK 3.0
				 * http://www.netlib.org/clapack/clapack.h
				 */
				"dlangb", "dlange", "dlangt", "dlanhs", "dlansb", "dlansp",
				"dlanst", "dlansy", "dlantb", "dlantp", "dlantr", "dlapy2",
				"dlapy3", "lsamen", "slangb", "slange", "slangt", "slanhs",
				"slansb", "slansp", "slanst", "slansy", "slantb", "slantp",
				"slantr", "slapy2", "slapy3", "dlamc3", "dsecnd", "second",
				"slamch", "slamc3",
				/*
				 * The following is not defined in clapack.h bet really should be!
				 * So we're leaving it in and we'll suffer the compile time warning.
				 */
				// "dlamch",
				/* these are not defined in the ARPACK headers */
				"dmout", "dvout", "icnteq", "icopy", "iset", "iswap", "ivout",
				"smout", "svout", "dgetv0", "dlaqrb", "dnaitr", "dnapps",
				"dnaup2", "dnconv", "dneigh", "dngets", "dsaitr", "dsapps",
				"dsaup2", "dsconv", "dseigt", "dsesrt", "dsgets", "dsortc",
				"dsortr", "dstatn", "dstats", "dstqrb", "sgetv0", "slaqrb",
				"snaitr", "snapps", "snaup2", "snconv", "sneigh", "sngets",
				"ssaitr", "ssapps", "ssaup2", "ssconv", "sseigt", "ssesrt",
				"ssgets", "ssortc", "ssortr", "sstatn", "sstats", "sstqrb"));

	/** A list of methods where F2J has a different signature than in OS X Leopard */
	static final List<String> incompatibleWithJni =
			Arrays.asList("dlar1v", "dlarrb", "dlarre", "dlarrf", "dlarrv",
				"slar1v", "slarrb", "slarre", "slarrf", "slarrv");

	/**
	 * @param method
	 * @return the 4D array of wrappers. The first is the abstract portion, the second is
	 *         the F2J wrapper, the third is the JNI wrapper and the forth is the C JNI
	 *         code.
	 * @throws IOException
	 */
	private String[] createWrapper(Method method) throws IOException {
		String name = method.getName();
		String[] parts = new String[4];

		Map<String, String[]> fromDocs = getParameterNames(method);
		String javadocs = fromDocs.keySet().iterator().next();
		String[] names = fromDocs.values().iterator().next();
		Class<?>[] paramTypes = method.getParameterTypes();
		List<Doublet<String, String>> typeAndName =
				typeAndName(paramTypes, names);
		// some exceptional methods
		if (notSupportedByJNI.contains(name)
				|| incompatibleWithJni.contains(name)
				|| dontUseJNI.contains(name)) {
			String javaWrapper = createJavaWrapper(method, typeAndName);
			String javaDocs = createTopJavaDocs(javadocs, typeAndName);
			parts[0] = javaWrapper.replace("@Override\n", javaDocs);
			System.err.println("Forcing F2J for " + name);
			parts[1] = "";
			parts[2] = "";
			parts[3] = "";
			return parts;
		}
		parts[0] = createTopWrapper(method, typeAndName, javadocs);
		parts[1] = createJavaWrapper(method, typeAndName);
		parts[2] = createJNIWrapper(method, typeAndName);
		parts[3] = createJNICode(method, typeAndName);
		return parts;
	}

	/**
	 * This convenience method will examine the classpath and find any classes which are
	 * in the requested package. A filter can be specified to exclude results.
	 * 
	 * @param packageName
	 * @param filter
	 * @return all classes in a given package
	 * @see http://forum.java.sun.com/thread.jspa?threadID=757391&messageID=4326850
	 */
	private List<Class<?>> getClasses(String packageName, IClassFilter filter)
		throws MalformedURLException{
		String packagePath = packageName.replace('.', '/');
//		ArrayList<URL> classpath = new ArrayList<URL>();
//		String[] classpathString = System.getProperty("java.class.path").split(":");
//		for (int i = 0 ; i < classpathString.length ; i++){
//			if (classpathString[i] == null)
//				continue;
//			try {
//				URL url = new URL("file:" + classpathString[i]);
//				classpath.add(url);
//			} catch (MalformedURLException ex) {
//				Logger.getLogger(JavaGenerator.class.getName()).
//					log(Level.SEVERE, classpathString[i] + " " + ex.getMessage());
//			}
//		}
		URL url1 = new URL("file:///usr/share/java/junit-3.8.2.jar");
		URL url2 = new URL("file:///usr/share/java/f2jutil-0.8.1.jar");
		URL url3 = new URL("file:///usr/share/java/jlapack-blas-0.8.jar");
		URL url4 = new URL("file:///usr/share/java/jlapack-lapack-0.8.jar");
		URL url5 = new URL("file:///usr/share/java/jlapack-xerbla-0.8.jar");

		URL [] classpath = { url1, url2, url3, url4, url5 };
		List<Class<?>> result = new ArrayList<Class<?>>();
		System.out.println(Arrays.toString(classpath));
		for (URL url : classpath) {
			File file;
			try {
				file = new File(url.toURI());
			} catch (URISyntaxException e1) {
				continue;
			}
			if (file.getPath().endsWith(".jar")) {
				// class path is a jar file
				JarFile jarFile;
				try {
					jarFile = new JarFile(file);
				} catch (IOException e) {
					continue;
				}
				for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
					String entryName = (entries.nextElement()).getName();
					if (entryName.matches(packagePath + "/\\w*\\.class")) {
						// get only class files in package dir
						ClassLoader classLoader =
								new URLClassLoader(new URL[] { url });
						String className =
								entryName.replace('/', '.').substring(0,
									entryName.lastIndexOf('.'));
						if (!filter.isValid(className))
							continue;
						Class<?> clazz;
						try {
							clazz = classLoader.loadClass(className);
						} catch (ClassNotFoundException e) {
							continue;
						}
						result.add(clazz);
					}
				}
			} else {
				// class path is a directory
				File packageDirectory =
						new File(file.getPath() + "/" + packagePath);
				for (File f : packageDirectory.listFiles()) {
					if (f.getPath().endsWith(".class")) {
						String className =
								packageName
										+ "."
										+ f.getName().substring(0,
											f.getName().lastIndexOf('.'));
						if (!filter.isValid(className))
							continue;
						ClassLoader classLoader =
								new URLClassLoader(new URL[] { url });
						Class<?> clazz;
						try {
							clazz = classLoader.loadClass(className);
						} catch (ClassNotFoundException e) {
							continue;
						}
						result.add(clazz);
					}
				}
			}
		}
		return result;
	}

	/**
	 * Note that we cannot use a library like Paranamer here because the information is
	 * not in the bytecode. We look in the Javadocs!
	 * 
	 * @param method
	 * @return a singleton map from the javadoc description to the parameter names of the
	 *         method, in order. (Yes, I know this is a hack)
	 * @throws IOException
	 */
	private Map<String, String[]> getParameterNames(Method method)
			throws IOException {
		ZipInputStream in;
		try {
			in = new ZipInputStream(new FileInputStream(javadocs));
		} catch (FileNotFoundException e) {
			// no javadocs available, just return arg1 ... argN
			int size = method.getGenericParameterTypes().length;
			String[] names = new String[size];
			for (int i = 0; i < size; i++) {
				names[i] = "arg" + (i + 1);
			}
			String docs =
					"\t * No documentation was available when generating this method.\n\t * \n";
			return Collections.singletonMap(docs, names);
		}
		ZipEntry entry;
		// deal with the capitalisation of LAPACK classes... clashes there
		String m = method.getName();
		String p =
				pkg.replace(".", "/") + "/" + m.substring(0, 1).toUpperCase()
						+ m.substring(1) + ".html";
		try {
			while ((entry = in.getNextEntry()) != null) {
				String name = entry.getName();
				if (name.endsWith(p)) {
					ByteArrayOutputStream out = new ByteArrayOutputStream();
					// Transfer bytes from the ZIP file to the output stream
					byte[] buf = new byte[1024];
					int len;
					while ((len = in.read(buf)) > 0) {
						out.write(buf, 0, len);
					}
					String javadoc = new String(out.toByteArray());
					out.close();
					String[] names = parseParameterNames(method, javadoc);
					String documentation =
							parseJavadocDescription(method, javadoc);
					return Collections.singletonMap(documentation, names);
				}
			}
			throw new RuntimeException("getParameterNames failed");
		} finally {
			in.close();
		}
	}

	private String jniWrapperLoader(String wrapperName) {
		StringBuilder b = new StringBuilder();
		b.append("\t// singleton\n");
		b.append("\tprotected static final Native" + wrapperName
				+ " INSTANCE = new Native" + wrapperName + "();\n\n");
		b.append("\t// indicates if the JNI loaded OK. If this is false, calls to the native\n");
		b.append("\t// methods will fail with UnsatisfiedLinkError\n");
		b.append("\tprotected final boolean isLoaded;\n\n");
		b.append("\tprivate Native" + wrapperName + "() {\n");
		b.append("\t\tString libname = JNIMethods.getPortableLibraryName(\"jni"
				+ wrapperName.toLowerCase() + "\");\n");
		b.append("\t\ttry {\n");
		b.append("\t\t\tSystem.loadLibrary(libname);\n");
		b.append("\t\t} catch (UnsatisfiedLinkError e) {\n");
		b.append("\t\t\tisLoaded = false;\n");
		b.append("\t\t\treturn;\n");
		b.append("\t\t}\n");
		b.append("\t\tisLoaded = true;\n");
		b.append("\t}\n\n");
		return b.toString();
	}

	/**
	 * @param method
	 * @param javadoc
	 * @return
	 */
	private String parseJavadocDescription(Method method, String javadoc) {
		Pattern pattern =
				Pattern.compile("seymour@cs.utk.edu</a> with any questions.\n<p>");
		Matcher matcher = pattern.matcher(javadoc);
		boolean matched = matcher.find();
		assert matched;
		int start = matcher.end();
		int end = javadoc.indexOf("</pre>", start);
		javadoc =
				javadoc.substring(start, end).replaceAll("\n \\*", "\n").replaceAll(
					"\n c", "\n");
		return "<pre>" + javadoc + "</pre>\n";
	}

	/**
	 * @param method
	 * @param javadoc
	 * @return
	 */
	private String[] parseParameterNames(Method method, String javadoc) {
		int n = method.getParameterTypes().length;
		String[] names = new String[n];
		// this is the worst regex code I've ever written... I'm being lazy
		int begin = javadoc.indexOf("METHOD SUMMARY");
		Pattern pattern =
				Pattern.compile("\\Q>" + method.getName() + "</A></B>(\\E");
		Matcher matcher = pattern.matcher(javadoc);
		boolean matched = matcher.find(begin);
		assert matched;
		// this begin is the real beginning of our search
		begin = matcher.end();
		// we need to cap the region to look at
		pattern = Pattern.compile("\\Q)</CODE>\\E");
		matcher = pattern.matcher(javadoc);
		matched = matcher.find(begin);
		assert matched;
		int end = matcher.start();
		pattern = Pattern.compile("&nbsp;([^,]*)(,|$)");
		matcher = pattern.matcher(javadoc);
		matcher.region(begin, end);
		int cnt = 0;
		while (matcher.find()) {
			String name = matcher.group(1);
			names[cnt] = name;
			cnt++;
		}
		assert cnt == n;
		return names;
		// int n = method.getParameterTypes().length;
		// String[] names = new String[n];
		// for (int i = 0; i < n; i++) {
		// names[i] = "arg" + (i + 1);
		// }
		// return names;
	}

	private String topWrapperLoader(String wrapperName) {
		// use static initialisation
		StringBuilder builder = new StringBuilder();
		builder.append("\tstatic private final " + wrapperName + " current;\n");
		builder.append("\tstatic {\n");
		builder.append("\t\tLogger logger = Logger.getLogger(\"org.netlib." + wrapperName.toLowerCase() + "\");\n");
		builder.append("\t\tif (Native" + wrapperName + ".INSTANCE.isLoaded) {\n");
		builder.append("\t\t\tcurrent = Native" + wrapperName + ".INSTANCE;\n");
		builder.append("\t\t\tlogger.config(\"Using JNI for " + wrapperName + "\");\n");
		builder.append("\t\t} else {\n");
		builder.append("\t\t\tcurrent = J" + wrapperName + ".INSTANCE;\n");		
		builder.append("\t\t\tlogger.config(\"Using F2J as JNI failed for " + wrapperName + "\");\n");
		builder.append("\t\t}\n");

		if ("LAPACK".equals(wrapperName)) {
			// workaround bug 5
			builder.append("\t\tcurrent.slamch(\"E\");\n");
			builder.append("\t\tcurrent.dlamch(\"E\");\n");
		}

		builder.append("\t}\n\n");
		builder.append("\tpublic static " + wrapperName + " getInstance() {\n");
		builder.append("\t\treturn current;\n");
		builder.append("\t}\n\n");

// // leading dimension helper methods
// builder.append("\t/**\n");
// builder.append("\t * <code>max(1, M)</code> provided as a convenience for 'leading
// dimension' calculations\n\t * \n");
// builder.append("\t * @param n\n");
// builder.append("\t */\n");
// builder.append("\tstatic public int ld(int n) {\n");
// builder.append("\t\treturn Math.max(1, n);\n");
// builder.append("\t}\n");
// builder.append("\t/**\n");
// builder.append("\t * <code>max(1, max(M, N))</code> provided as a convenience for
// 'leading dimension' calculations\n\t * \n");
// builder.append("\t * @param m\n");
// builder.append("\t * @param n\n");
// builder.append("\t */\n");
// builder.append("\tstatic public int ld(int m, int n) {\n");
// builder.append("\t\treturn Math.max(1, Math.max(m, n));\n");
// builder.append("\t}\n\n");

		return builder.toString();

		// the following is an alternative using lazy initialisation
// StringBuilder builder = new StringBuilder();
// builder.append("\tstatic private volatile " + wrapperName
// + " current = null;\n\n");
// builder.append("\tprivate static final Object currentLock = new Object();\n\n");
// builder.append("\tpublic static final " + wrapperName
// + " getInstance() {\n");
// builder.append("\t\tsynchronized (currentLock) {\n");
// builder.append("\t\t\t// synchronised lazy initialisation\n");
// builder.append("\t\t\tif (current == null) {\n");
// builder.append("\t\t\t\tLogger logger = Logger.getLogger(\"org.netlib\");\n");
// builder.append("\t\t\t\t// test the JNI implementation\n");
// builder.append("\t\t\t\tif (Native" + wrapperName
// + ".INSTANCE.isLoaded) {\n");
// builder.append("\t\t\t\t\tcurrent = Native" + wrapperName + ".INSTANCE;\n");
// builder.append("\t\t\t\t\tlogger.info(\"Using JNI for " + wrapperName + "\");\n");
// builder.append("\t\t\t\t} else {\n");
// builder.append("\t\t\t\t\t// otherwise use F2J\n");
// builder.append("\t\t\t\t\tcurrent = J" + wrapperName + ".INSTANCE;\n");
// builder.append("\t\t\t\t\tlogger.info(\"Using F2J as JNI failed for "
// + wrapperName + "\");\n");
// builder.append("\t\t\t\t}\n");
// builder.append("\t\t\t}\n");
// builder.append("\t\t\treturn current;\n");
// builder.append("\t\t}\n");
// builder.append("\t}\n");
// return builder.toString();
	}

	/**
	 * @param paramTypes
	 * @param names
	 * @return
	 */
	private List<Doublet<String, String>> typeAndName(Class<?>[] paramTypes,
			String[] names) {
		assert names.length == paramTypes.length;
		List<Doublet<String, String>> l =
				new ArrayList<Doublet<String, String>>();
		Class<?> lastType = Void.TYPE;
		for (int i = 0; i < names.length; i++) {
			Class<?> type = paramTypes[i];
			if (type.equals(Integer.TYPE)
					&& (lastType.equals(double[].class)
							|| lastType.equals(int[].class)
							|| lastType.equals(boolean[].class) || lastType.equals(float[].class))) {
				lastType = type;
				continue;
			}
			String t = classDefs.get(type);
			if (t == null)
				t = "Object";
			l.add(new Doublet<String, String>(t, names[i]));
			lastType = type;
		}
		return l;
	}
}
