/*
 * 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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This is a stand alone application that takes either a javah JNI header file (or String
 * definitions of the method) as input and creates template C code, converting as many
 * variables into native C types as possible, such as arrays. Some object types (such as
 * netlib primitive wrappers, intW and doubleW) are supported. There are a lot of
 * workarounds to accommodate the inconsistent nature of the CBLAS library which is
 * sometimes C-ish and sometimes Fortran-ish and sometimes require extra parameters and
 * sometimes don't.
 * <p>
 * An instance is created for each native method,
 * <p>
 * Note: this could potentially be made a lot more generic, e.g by taking in a class that
 * implements some clean subset of the methods in here. (i.e. factor out the F2J specific
 * stuff).
 * 
 * @author Samuel Halliday
 */
class JNIGenerator {

	/** Pattern to find the declaration in a javah output file */
	private static final Pattern namePattern =
			Pattern.compile("JNIEXPORT.+?\\(");

	// these BLAS functions need an ordering parameter
	private static final Set<String> NEED_CBLAS_ORDERING =
			new HashSet<String>(Arrays.asList("cblas_sgemv", "cblas_sgbmv",
				"cblas_strmv", "cblas_stbmv", "cblas_stpmv", "cblas_strsv",
				"cblas_stbsv", "cblas_stpsv", "cblas_dgemv", "cblas_dgbmv",
				"cblas_dtrmv", "cblas_dtbmv", "cblas_dtpmv", "cblas_dtrsv",
				"cblas_dtbsv", "cblas_dtpsv", "cblas_cgemv", "cblas_cgbmv",
				"cblas_ctrmv", "cblas_ctbmv", "cblas_ctpmv", "cblas_ctrsv",
				"cblas_ctbsv", "cblas_ctpsv", "cblas_zgemv", "cblas_zgbmv",
				"cblas_ztrmv", "cblas_ztbmv", "cblas_ztpmv", "cblas_ztrsv",
				"cblas_ztbsv", "cblas_ztpsv", "cblas_ssymv", "cblas_ssbmv",
				"cblas_sspmv", "cblas_sger", "cblas_ssyr", "cblas_sspr",
				"cblas_ssyr2", "cblas_sspr2", "cblas_dsymv", "cblas_dsbmv",
				"cblas_dspmv", "cblas_dger", "cblas_dsyr", "cblas_dspr",
				"cblas_dsyr2", "cblas_dspr2", "cblas_chemv", "cblas_chbmv",
				"cblas_chpmv", "cblas_cgeru", "cblas_cgerc", "cblas_cher",
				"cblas_chpr", "cblas_cher2", "cblas_chpr2", "cblas_zhemv",
				"cblas_zhbmv", "cblas_zhpmv", "cblas_zgeru", "cblas_zgerc",
				"cblas_zher", "cblas_zhpr", "cblas_zher2", "cblas_zhpr2",
				"cblas_sgemm", "cblas_ssymm", "cblas_ssyrk", "cblas_ssyr2k",
				"cblas_strmm", "cblas_strsm", "cblas_dgemm", "cblas_dsymm",
				"cblas_dsyrk", "cblas_dsyr2k", "cblas_dtrmm", "cblas_dtrsm",
				"cblas_cgemm", "cblas_csymm", "cblas_csyrk", "cblas_csyr2k",
				"cblas_ctrmm", "cblas_ctrsm", "cblas_zgemm", "cblas_zsymm",
				"cblas_zsyrk", "cblas_zsyr2k", "cblas_ztrmm", "cblas_ztrsm",
				"cblas_chemm", "cblas_cherk", "cblas_cher2k", "cblas_zhemm",
				"cblas_zherk", "cblas_zher2k"));

	/** Pattern to find the declaration of signatures in a javah output file */
	private static final Pattern sigPattern =
			Pattern.compile("Signature: \\((.+?)\\)");

	/**
	 * Mapping from signature letters to C types. Created statically below.
	 * 
	 * @see http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp16432
	 */
	private static final Map<String, String> types =
			new HashMap<String, String>();

	/** Use direct access to Java arrays from C land. */
	private static final boolean USE_CRITICAL_ARRAYS = true;

	// some, but not all standard signatures and their C type
	static {
		/* These are if the JNI descriptions of Java types are used */
		types.put("Z", "jboolean");
		types.put("B", "jbyte");
		types.put("C", "jchar");
		types.put("S", "jshort");
		types.put("I", "jint");
		types.put("J", "jlong");
		types.put("F", "jfloat");
		types.put("D", "jdouble");
		types.put("Ljava/lang/String", "jstring");
		types.put("[I", "jintArray");
		types.put("[D", "jdoubleArray");
		types.put("[Z", "jbooleanArray");
		types.put("Lorg/netlib/util/intW", "intW");
		types.put("Lorg/netlib/util/doubleW", "doubleW");

		/* These are if the Java type has been passed in */
		types.put("boolean", "jboolean");
		types.put("byte", "jbyte");
		types.put("char", "jchar");
		types.put("short", "jshort");
		types.put("int", "jint");
		types.put("long", "jlong");
		types.put("float", "jfloat");
		types.put("double", "jdouble");
		types.put("String", "jstring");
		types.put("int[]", "jintArray");
		types.put("double[]", "jdoubleArray");
		types.put("float[]", "jfloatArray");
		types.put("boolean[]", "jbooleanArray");
		types.put("intW", "intW");
		types.put("doubleW", "doubleW");
		types.put("floatW", "floatW");
		types.put("StringW", "StringW");
		types.put("booleanW", "booleanW");
	}

	/**
	 * Take in a javah output file and print the JNI code to stdout.
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		if (args.length < 1)
			throw new IllegalArgumentException("Must pass a filename");
		File file = new File(args[0]);
		if (!file.exists())
			throw new IllegalArgumentException(args[0]
					+ " was not a valid filename");

		String pre = "";
		String post = "";
		if (args.length > 1) {
			// prefix to method calls is arg 2
			pre = args[1];
			if (args.length > 2) {
				// postfix to method calls is arg 3
				post = args[2];
			}
		}

		FileInputStream stream = new FileInputStream(file);
		InputStreamReader streamReader = new InputStreamReader(stream, "UTF-8");
		BufferedReader reader = new BufferedReader(streamReader);
		// matcher doesn't work on readers for some crazy reason, must get String
		StringBuilder builder = new StringBuilder();
		String line;
		while ((line = reader.readLine()) != null) {
			builder.append(line);
		}
		reader.close();
		Matcher matcher =
				Pattern.compile("/\\*[^#]+?JNIEXPORT.+?\\);", Pattern.MULTILINE).matcher(
					builder.toString());
		System.out.println("#include <jni.h>\n\n");
		while (matcher.find()) {
			// go through the file finding matches to the pattern
			JNIGenerator generator =
					new JNIGenerator(matcher.group(), pre, post, false);
			System.out.println(generator.getTemplate());
		}
	}

	/** Set this to true if you're auto-generating CBLAS code (turns on some hacks) */
	private final boolean blasHack;

	/** Stores the JNI C code that cleans up the parameters and propagates changes back */
	private final StringBuilder cleanup = new StringBuilder();

	/** The names of the parameters in C land, after initialisation. */
	private final List<String> cNames = new ArrayList<String>();

	/** Stores the JNI C code that initialises the parameters */
	private final StringBuilder init = new StringBuilder();

	/**
	 * Apologies... this actually stores the start of the JNI C signature rather than the
	 * method name. This should probably be cleaned up but could potentially lead to less
	 * stability in code generated from javah output.
	 */
	private final String name;

	/** The names of the parameters to the method, in order */
	private final List<String> names = new ArrayList<String>();

	/** characters to append to the Java method name to get the C function name */
	private final String post;

	/**
	 * For any workarounds needed after initialisation (e.g. building int arrays out of
	 * boolean arrays)
	 */
	private final StringBuilder postInit = new StringBuilder();

	/** characters to prepend to the Java method name to get the C function name */
	private final String pre;

	// contains a list of signatures objects passed in, in the order
	// presented in the javadocs
	private final List<String> signatures = new ArrayList<String>();

	/**
	 * The constructor to use when creating JNI code from the C header file.
	 * 
	 * @param method
	 *            containing the javadoc portion as well as the declaration
	 * @param post
	 * @param pre
	 */
	public JNIGenerator(String method, String pre, String post, boolean blasHack) {
		assert (method != null) && (pre != null) && (post != null);
		this.pre = pre;
		this.post = post;
		this.blasHack = blasHack;
		// System.out.println(method);
		Matcher sigMatcher = sigPattern.matcher(method);
		sigMatcher.find();
		String signature = sigMatcher.group(1);
		// System.out.println(signature);
		int offset = 0;
		while (offset < signature.length()) {
			if (signature.startsWith(";", offset)) {
				// ignore semicolons, which simply separate java objects
				offset++;
				continue;
			}
			boolean matched = false;
			for (String sig : types.keySet()) {
				if (signature.startsWith(sig, offset)) {
					String type = types.get(sig);
					signatures.add(type);
					// System.out.println(type);
					offset = offset + sig.length();
					matched = true;
					break;
				}
			}
			if (!matched)
				throw new RuntimeException("Type "
						+ signature.substring(offset)
						+ " not implemented yet, sorry!");
		}
		// now the signatures list is created, get the name of the function
		Matcher nameMatcher = namePattern.matcher(method);
		nameMatcher.find();
		name = nameMatcher.group();
		for (int i = 0; i < signatures.size(); i++) {
			names.add("arg" + (i + 1));
		}
	}

	/**
	 * The constructor to use when generating the JNI code directly from the class files.
	 * 
	 * @param pre
	 *            the string to prepend to the C function
	 * @param post
	 *            the string to postpend to the C function
	 * @param methodName
	 *            the name of the method in Java land (with package part)
	 * @param types
	 *            the types of the parameters to the method
	 * @param names
	 *            the names of the parameters to the method
	 * @param returnType
	 *            the Java return type (try to use primitives)
	 * @param blasHack
	 *            set to true if generating CBLAS code
	 */
	public JNIGenerator(String pre, String post, String methodName,
			List<String> types, List<String> names, String returnType,
			boolean blasHack) {
		this.pre = pre;
		this.post = post;
		this.blasHack = blasHack;
		if (!returnType.equals("void")) {
			returnType = "j" + returnType;
		}
		name =
				"JNIEXPORT " + returnType + " JNICALL Java_"
						+ methodName.replace(".", "_") + " (";
		this.names.addAll(names);
		for (String type : types) {
			String sig = JNIGenerator.types.get(type);
			assert sig != null : type + " not supported yet";
			signatures.add(sig);
		}
	}

	public String getTemplate() {
		StringBuilder builder = new StringBuilder();
		builder.append(name);
		builder.append("JNIEnv * env, jobject calling_obj");
		if (signatures.size() > 0)
			builder.append(", ");
		for (int i = 0; i < signatures.size(); i++) {
			String sig = signatures.get(i);
			if ("intW".equals(sig) || "doubleW".equals(sig)
					|| "floatW".equals(sig) || "booleanW".equals(sig)
					|| "StringW".equals(sig)) {
				sig = "jobject";
			}
			builder.append(sig + " " + names.get(i));
			if (i != signatures.size() - 1)
				builder.append(", ");
		}
		builder.append("){\n");

		for (int i = 0; i < signatures.size(); i++) {
			initArgs(i, signatures.get(i));
			cleanupArgs(i, signatures.get(i));
		}

		builder.append(init);

		String returnType =
				name.replaceAll("JNIEXPORT ", "").replaceAll(" JNICALL.*", "");
		if (!"void".equals(returnType)) {
			builder.append("\t" + returnType + " returnValue;\n");
		}

		builder.append(postInit);

		builder.append("\n\t");
		String cName = guessCFunctionName();
		if (!"void".equals(returnType)) {
			builder.append("returnValue = ");
		}
		builder.append(cName + "(");
		if (NEED_CBLAS_ORDERING.contains(cName)) {
			builder.append("F2J_JNI_ORDER, ");
		}

		for (int i = 0; i < signatures.size(); i++) {
			builder.append(parameterise(i));
			if (i != signatures.size() - 1) {
				builder.append(", ");
			}
		}
		// hack for ilaenv in LAPACK
		if (guessCFunctionName().equals("ilaenv_")) {
			builder.append(", " + "(*env)->GetStringLength(env, "
					+ names.get(1) + ")");
			builder.append(", " + "(*env)->GetStringLength(env, "
					+ names.get(2) + ")");
		}

		builder.append(");");
		builder.append("\n\n");

		// if (cleanup.length() > 0) {
		// builder.append("\t// Clean up and propagate changes\n");
		// }
		builder.append(cleanup);

		if (!"void".equals(returnType)) {
			builder.append("\n\treturn returnValue;\n");
		}

		builder.append("}\n");

		if (USE_CRITICAL_ARRAYS)
			return builder.toString().replaceAll("Get\\w+?ArrayElements",
				"GetPrimitiveArrayCritical").replaceAll(
				"Release\\w+?ArrayElements", "ReleasePrimitiveArrayCritical");
		return builder.toString();
	}

	private void cleanupArgs(int arg, String type) {
		String jName = names.get(arg);
		String cName = cNames.get(arg);
		if (type.equals("jstring")) {
			// release UTF-8 array
			cleanup.append("\t(*env)->ReleaseStringUTFChars(env, " + jName
					+ ", " + cName + ");\n");
		} else if (type.equals("jintArray")) {
			// release the array, allows changes to back propagate
			cleanup.append("\t(*env)->ReleaseIntArrayElements(env, " + jName
					+ ", " + cName + ", 0);\n");
		} else if (type.equals("jdoubleArray")) {
			// release the array, allows changes to back propagate
			cleanup.append("\t(*env)->ReleaseDoubleArrayElements(env, " + jName
					+ ", " + cName + ", 0);\n");
		} else if (type.equals("jfloatArray")) {
			// release the array, allows changes to back propagate
			cleanup.append("\t(*env)->ReleaseFloatArrayElements(env, " + jName
					+ ", " + cName + ", 0);\n");
		} else if (type.equals("jbooleanArray")) {
			// release the array, allows changes to back propagate
			// this requires copying the boolean entries back again
			cleanup.append("\tfor (" + cName + "i = 0 ; " + cName + "i < "
					+ cName + "Size ; " + cName + "i++){\n");
			cleanup.append("\t\tif (" + cName + "[" + cName + "i] == 0){\n");
			cleanup.append("\t\t\t" + cName + "Tmp[" + cName
					+ "i] = JNI_FALSE;\n");
			cleanup.append("\t\t} else {\n");
			cleanup.append("\t\t\t" + cName + "Tmp[" + cName
					+ "i] = JNI_TRUE;\n");
			cleanup.append("\t\t}\n\t}\n");
			cleanup.append("\t(*env)->ReleaseBooleanArrayElements(env, "
					+ jName + ", " + cName + "Tmp, 0);\n");
		} else if (type.equals("intW")) {
			cleanup.append("\t(*env)->SetIntField(env, " + jName + ", " + cName
					+ "Id, " + cName + ");\n");
		} else if (type.equals("doubleW")) {
			cleanup.append("\t(*env)->SetDoubleField(env, " + jName + ", "
					+ cName + "Id, " + cName + ");\n");
		} else if (type.equals("floatW")) {
			cleanup.append("\t(*env)->SetFloatField(env, " + jName + ", "
					+ cName + "Id, " + cName + ");\n");
		} else if (type.equals("StringW")) {
			cleanup.append("\tjstring " + cName
					+ "StringNew = (*env)->NewStringUTF(env, " + cName + ");\n");
			cleanup.append("\t(*env)->SetObjectField(env, " + jName + ", "
					+ cName + "Id, " + cName + "StringNew);\n");
		} else if (type.equals("booleanW")) {
			// cast int -> unsigned byte
			cleanup.append("\t(*env)->SetBooleanField(env, " + jName + ", "
					+ cName + "Id, (jboolean)" + cName + ");\n");
		}
	}

	/**
	 * @return a guess of the C land function name, using {@link #pre} {@link #name} and
	 *         {@link #post}
	 */
	private String guessCFunctionName() {
		String guess = name.replaceAll(".*_", "").replaceAll("\\W.*", "");
		return pre + guess + post;
	}

	private void initArgs(int arg, String type) {
		String jName = names.get(arg);
		String cName = "jni_" + jName;

		if (type.equals("jboolean")) {
			// need to deal with booleans, as C uses ints, java uses unsigned bytes
			// (the logical type from f2c)
			init.append("\tlogical " + cName + " = (logical)" + jName + ";\n");
			cNames.add(cName);
		} else if (type.equals("jbyte")) {
			// machine dependent
			cNames.add(jName);
		} else if (type.equals("jchar")) {
			// always an unsigned short
			cNames.add(jName);
		} else if (type.equals("jshort")) {
			// always a short
			cNames.add(jName);
		} else if (type.equals("jint")) {
			// machine dependent
			cNames.add(jName);
		} else if (type.equals("jlong")) {
			// machine dependent
			cNames.add(jName);
		} else if (type.equals("jfloat")) {
			// always a float
			cNames.add(jName);
		} else if (type.equals("jdouble")) {
			// always a double
			cNames.add(jName);
		} else if (type.equals("jstring")) {
			// need to convert into UTF-8 array
			// we also cast to (char *) from (const char *)
			init.append("\tchar * " + cName
					+ " = (char *)(*env)->GetStringUTFChars(env, " + jName
					+ ", JNI_FALSE);\n");
			cNames.add(cName);
		} else if (type.equals("jintArray")) {
			// need to get the pointer to the array
			init.append("\tjint * " + cName
					+ " = (*env)->GetIntArrayElements(env, " + jName
					+ ", JNI_FALSE);\n\tcheck_memory(env, " + cName + ");\n");
			cNames.add(cName);
		} else if (type.equals("jdoubleArray")) {
			// need to get the pointer to the array
			init.append("\tjdouble * " + cName
					+ " = (*env)->GetDoubleArrayElements(env, " + jName
					+ ", JNI_FALSE);\n\tcheck_memory(env, " + cName + ");\n");
			cNames.add(cName);
		} else if (type.equals("jfloatArray")) {
			// need to get the pointer to the array
			init.append("\tjfloat * " + cName
					+ " = (*env)->GetFloatArrayElements(env, " + jName
					+ ", JNI_FALSE);\n\tcheck_memory(env, " + cName + ");\n");
			cNames.add(cName);
		} else if (type.equals("jbooleanArray")) {
			// need to get the pointer to the array and convert to int array
			init.append("\tjboolean * " + cName
					+ "Tmp = (*env)->GetBooleanArrayElements(env, " + jName
					+ ", JNI_FALSE);\n");
			init.append("\tjint " + cName
					+ "Size = (*env)->GetArrayLength(env, " + jName + ");\n");
			// must use the f2c "logical" type here
			init.append("\tlogical " + cName + "[" + cName + "Size];\n");
			init.append("\tint " + cName + "i;\n");
			// copy over boolean bytes to int array
			postInit.append("\tfor (" + cName + "i = 0 ; " + cName + "i < "
					+ cName + "Size ; " + cName + "i++){\n" + "\t\tif ("
					+ cName + "Tmp[" + cName + "i] == JNI_FALSE){\n" + "\t\t\t"
					+ cName + "[" + cName + "i] = 0;\n" + "\t\t} else {\n"
					+ "\t\t\t" + cName + "[" + cName + "i] = 1;"
					+ "\n\t\t}\n\t}\n");
			cNames.add(cName);
		} else if (type.equals("intW")) {
			init.append("\tjclass " + cName
					+ "Class = (*env)->GetObjectClass(env, " + jName + ");\n");
			init.append("\tjfieldID " + cName + "Id = (*env)->GetFieldID(env, "
					+ cName + "Class, \"val\", \"I\");\n");
			init.append("\tjint " + cName + " = (*env)->GetIntField(env, "
					+ jName + ", " + cName + "Id);\n");
			cNames.add(cName);
		} else if (type.equals("doubleW")) {
			init.append("\tjclass " + cName
					+ "Class = (*env)->GetObjectClass(env, " + jName + ");\n");
			init.append("\tjfieldID " + cName + "Id = (*env)->GetFieldID(env, "
					+ cName + "Class, \"val\", \"D\");\n");
			init.append("\tjdouble " + cName
					+ " = (*env)->GetDoubleField(env, " + jName + ", " + cName
					+ "Id);\n");
			cNames.add(cName);
		} else if (type.equals("floatW")) {
			init.append("\tjclass " + cName
					+ "Class = (*env)->GetObjectClass(env, " + jName + ");\n");
			init.append("\tjfieldID " + cName + "Id = (*env)->GetFieldID(env, "
					+ cName + "Class, \"val\", \"D\");\n");
			init.append("\tjfloat " + cName + " = (*env)->GetFloatField(env, "
					+ jName + ", " + cName + "Id);\n");
			cNames.add(cName);
		} else if (type.equals("StringW")) {
			init.append("\tjclass " + cName
					+ "Class = (*env)->GetObjectClass(env, " + jName + ");\n");
			init.append("\tjfieldID " + cName + "Id = (*env)->GetFieldID(env, "
					+ cName + "Class, \"val\", \"Ljava/lang/String;\");\n");
			init.append("\tjstring " + cName
					+ "String = (jstring)((*env)->GetObjectField(env, " + jName
					+ ", " + cName + "Id));\n");
			init.append("\tchar * " + cName
					+ " = (char *)(*env)->GetStringUTFChars(env, " + cName
					+ "String, JNI_FALSE);\n");
			cNames.add(cName);
		} else if (type.equals("booleanW")) {
			init.append("\tjclass " + cName
					+ "Class = (*env)->GetObjectClass(env, " + jName + ");\n");
			init.append("\tjfieldID " + cName + "Id = (*env)->GetFieldID(env, "
					+ cName + "Class, \"val\", \"I\");\n");
			init.append("\tjboolean " + cName
					+ "Boolean = (*env)->GetBooleanField(env, " + jName + ", "
					+ cName + "Id);\n");
			// don't forget C and Java treat 'boolean's differently
			init.append("\tlogical " + cName + " = (logical)" + cName
					+ "Boolean;\n");
			cNames.add(cName);
		} else
			throw new RuntimeException("unknown type " + type);
		assert cNames.size() == arg + 1;
	}

	/**
	 * @param i
	 * @return the version of the variable to send to the C function
	 */
	private String parameterise(int i) {
		// total hack
		if (("".equals(pre) && "_".equals(post))
				|| (blasHack && (guessCFunctionName().equals("cblas_drotg")
						|| guessCFunctionName().equals("cblas_srotg")
						|| ((i != 3) && guessCFunctionName().equals(
							"cblas_srotmg")) || ((i != 3) && guessCFunctionName().equals(
					"cblas_drotmg"))))) {
			// assume we're calling a fortran function and need to reference ints and
			// doubles
			String type = signatures.get(i);
			if ("jint".equals(type) || "jdouble".equals(type)
					|| "jfloat".equals(type) || "jboolean".equals(type)
					|| "intW".equals(type) || "doubleW".equals(type)
					|| "booleanW".equals(type) || "floatW".equals(type))
				return "&" + cNames.get(i);
		}
		// CBLAS needs some wrapper code
		if (blasHack) {
			if (cNames.get(i).startsWith("jni_trans"))
				return "getTrans(" + cNames.get(i) + ")";
			if (cNames.get(i).startsWith("jni_uplo"))
				return "getUpLo(" + cNames.get(i) + ")";
			if (cNames.get(i).startsWith("jni_diag"))
				return "getDiag(" + cNames.get(i) + ")";
			if (cNames.get(i).startsWith("jni_side"))
				return "getSide(" + cNames.get(i) + ")";
		}
		// do the simple thing... no referencing
		return cNames.get(i);
	}
}
