All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jace.proxy.ProxyGenerator Maven / Gradle / Ivy

There is a newer version: 1.2.22
Show newest version
package org.jace.proxy;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.*;
import java.util.*;
import org.jace.metaclass.*;
import org.jace.parser.ClassFile;
import org.jace.parser.field.ClassField;
import org.jace.parser.field.FieldAccessFlag;
import org.jace.parser.field.FieldAccessFlagSet;
import org.jace.parser.method.ClassMethod;
import org.jace.parser.method.MethodAccessFlag;
import org.jace.parser.method.MethodAccessFlagSet;
import org.jace.util.CKeyword;
import org.jace.util.DelimitedCollection;
import org.jace.util.Util;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Generates a C++ proxy class for use with the Jace library.
 *
 * @author Toby Reyelts
 * @author Gili Tzabari
 */
public class ProxyGenerator
{
	/**
	 * Type of method invocation.
	 */
	@SuppressWarnings("PublicInnerClass")
	public enum InvokeStyle
	{
		NORMAL,
		VIRTUAL
	}

	/**
	 * Indicates the member accessibility that proxies should expose.
	 */
	@SuppressWarnings("PublicInnerClass")
	public enum AccessibilityType
	{
		//
		// WARNING: Do not change the element order! shouldBeSkipped() expects the elements to be ordered in
		// increasing visibility.
		//
		/**
		 * Generate public fields and methods.
		 */
		PUBLIC,
		/**
		 * Generate public and protected fields and methods.
		 */
		PROTECTED,
		/**
		 * Generate public, protected and package-private fields and methods.
		 */
		PACKAGE,
		/**
		 * Generate public, protected, package-private and private fields and methods.
		 */
		PRIVATE
	}
	private final static String newLine = System.getProperty("line.separator");
	private final ClassFile classFile;
	private final ClassPath classPath;
	private final AccessibilityType accessibility;
	private final MetaClassFilter dependencyFilter;
	private final boolean exportSymbols;
	private final Logger log = LoggerFactory.getLogger(ProxyGenerator.class);
	/**
	 * The names of macros that may conflict with generated code.
	 */
	private Collection reservedFields = Lists.newArrayList("BIG_ENDIAN", "LITTLE_ENDIAN",
		"UNDERFLOW", "OVERFLOW", "minor", "TRUE", "FALSE", "EOF");

	/**
	 * Creates a new ProxyGenerator.
	 *
	 * @param builder an instance of ProxyGenerator.Builder
	 */
	@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
	private ProxyGenerator(Builder builder)
	{
		assert (builder != null);
		this.classFile = builder.classFile;
		this.accessibility = builder.accessibility;
		this.dependencyFilter = builder.dependencyFilter;
		this.exportSymbols = builder.exportSymbols;
		this.classPath = builder.classPath;
	}

	/**
	 * Generates the proxy header.
	 *
	 * @param output the output writer
	 * @throws IOException if an I/O exception occurs while writing the header file
	 */
	public void generateHeader(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();

		output.write(metaClass.beginGuard() + newLine);
		output.write(newLine);

		includeStandardHeaders(output, false);
		includeDependentHeaders(output);
		makeForwardDeclarations(output);
		output.write(newLine);

		generateClassDeclaration(output);
		output.write(newLine);

		output.write(metaClass.endGuard() + newLine);
		output.write(newLine);
	}

	/**
	 * Generates the proxy source code.
	 *
	 * @param output the output writer
	 * @throws IOException if an I/O exception occurs while writing the source file
	 */
	public void generateSource(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();

		output.write(metaClass.include() + newLine);
		output.write(newLine);

		includeStandardSourceHeaders(output);
		output.write(newLine);

		includeDependentSourceHeaders(output);
		output.write(newLine);

		generateClassDefinition(output);
		output.write(newLine);
	}

	/**
	 * Generates the source-code includes.
	 *
	 * @param output the output writer
	 * @throws IOException if an I/O exception occurs while writing
	 */
	public void includeStandardSourceHeaders(Writer output) throws IOException
	{
		Util.generateComment(output, "Standard Jace headers needed to implement this class.");

		output.write("#include \"jace/JArguments.h\"" + newLine);
		output.write("#include \"jace/JMethod.h\"" + newLine);
		output.write("#include \"jace/JField.h\"" + newLine);
		output.write("#include \"jace/JClassImpl.h\"" + newLine);
		String className = classFile.getClassName().asIdentifier();
		if (className.equals("java.lang.String"))
			output.write("#include \"jace/proxy/java/lang/Integer.h\"" + newLine);

		output.write("#include \"jace/BoostWarningOff.h\"" + newLine);
		output.write("#include " + newLine);
		output.write("#include \"jace/BoostWarningOn.h\"" + newLine);
	}

	/**
	 * Generates #include statements for any headers the class depends on.
	 *
	 * @param output the output writer
	 * @throws IOException if an I/O exception occurs while writing
	 */
	private void includeDependentSourceHeaders(Writer output) throws IOException
	{
		Util.generateComment(output, "Headers for the classes that this class uses.");

		for (MetaClass dependentMetaClass: getForwardDeclarations())
		{
			// Skip includes for classes that aren't part of our dependency list
			if (!dependencyFilter.accept(dependentMetaClass))
				continue;
			output.write(dependentMetaClass.include() + newLine);
		}
	}

	/**
	 * Generates the C++ class definition.
	 *
	 * @param output the output writer
	 * @throws IOException if an I/O exception occurs while writing
	 */
	private void generateClassDefinition(Writer output) throws IOException
	{
		beginNamespace(output);
		output.write(newLine);

		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();

		Util.generateComment(output,
			"The Jace C++ proxy class source for " + classFile.getClassName() + "." + newLine
			+ "Please do not edit this source, as any changes you make will be overwritten."
			+ newLine + "For more information, please refer to the Jace Developer's Guide.");

		output.write(getInitializerValue(false) + newLine);
		generateMethodDefinitions(output, false);
		generateFieldDefinitions(output, false);
		generateJaceDefinitions(output, false);
		output.write(newLine);

		endNamespace(output);
		output.write(newLine);

		// Define (if necessary) the ElementProxy specializations
		output.write("BEGIN_NAMESPACE(jace)" + newLine);
		output.write(newLine);
		output.write("#ifndef PUT_TSDS_IN_HEADER" + newLine);
		printElementProxyTsd(output, metaClass);
		output.write("#endif" + newLine);

		// Define (if necessary) the ElementProxy specializations
		output.write("#ifndef PUT_TSDS_IN_HEADER" + newLine);
		printFieldProxyTsd(output, metaClass);
		output.write("#endif" + newLine);
		output.write(newLine);
		output.write("END_NAMESPACE(jace)" + newLine);
	}

	/**
	 * Returns true if the class method should be skipped.
	 *
	 * @param method the class method
	 * @return true if the class method should be skipped
	 */
	private boolean shouldBeSkipped(ClassMethod method)
	{
		MethodAccessFlagSet flagSet = method.getAccessFlags();

		// skip the method if it is a BRIDGE method
		if ((flagSet.getValue() & 0x0040) != 0)
			return true;

		// if the method is public, we always generate it
		if (flagSet.contains(MethodAccessFlag.PUBLIC))
			return false;

		if (flagSet.contains(MethodAccessFlag.PROTECTED))
			return accessibility.compareTo(AccessibilityType.PROTECTED) < 0;

		if (flagSet.contains(MethodAccessFlag.PRIVATE))
			return accessibility.compareTo(AccessibilityType.PRIVATE) < 0;

		// method is package-private
		return accessibility.compareTo(AccessibilityType.PACKAGE) < 0;
	}

	/**
	 * Returns true if the class field should be skipped.
	 *
	 * @param field the class field
	 * @return true if the class field should be skipped
	 */
	private boolean shouldBeSkipped(ClassField field)
	{
		FieldAccessFlagSet flagSet = field.getAccessFlags();

		// if the field is public, we always generate it
		if (flagSet.contains(FieldAccessFlag.PUBLIC))
			return false;


		if (flagSet.contains(FieldAccessFlag.PROTECTED))
			return accessibility.compareTo(AccessibilityType.PROTECTED) < 0;

		if (flagSet.contains(FieldAccessFlag.PRIVATE))
			return accessibility.compareTo(AccessibilityType.PRIVATE) < 0;

		// method is package-private
		return accessibility.compareTo(AccessibilityType.PACKAGE) < 0;
	}

	/**
	 * Generate the method definitions.
	 *
	 * @param output the output writer
	 * @param forPeer true if the methods are being generated for a peer, false for a proxy
	 * @throws IOException if an error occurs while writing
	 */
	public void generateMethodDefinitions(Writer output, boolean forPeer) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		String className = metaClass.getSimpleName();

		// go through all of the methods
		for (ClassMethod method: classFile.getMethods())
		{
			if (shouldBeSkipped(method))
				continue;

			if (!isPartOfDependencies(method))
				continue;

			MetaClass returnType = MetaClassFactory.getMetaClass(method.getReturnType()).proxy();
			String methodName = method.getName();

			// If this is a static initializer, then we don't need to declare it
			if (methodName.equals(""))
				continue;

			boolean isConstructor = methodName.equals("");
			MethodAccessFlagSet accessFlagSet = method.getAccessFlags();

			// skip the methods that we don't need to be generating for a Peer
			if (forPeer)
			{
				if (isConstructor || methodName.equals("jaceUserStaticInit") || methodName.equals(
					"jaceUserClose") || methodName.equals("jaceUserFinalize") || methodName.equals(
					"jaceSetNativeHandle") || methodName.equals("jaceGetNativeHandle") || methodName.equals(
					"jaceCreateInstance") || methodName.equals("jaceDestroyInstance") || methodName.equals(
					"jaceDispose") || accessFlagSet.contains(MethodAccessFlag.NATIVE))
				{
					continue;
				}
			}

			// If this is a constructor, there is no return-type
			if (isConstructor)
				output.write(className + " " + className + "::Factory::create");
			else
			{
				// handle clashes between C++ keywords and java identifiers
				methodName = CKeyword.adjust(methodName);

				if (returnType.getSimpleName().equals("JVoid"))
					output.write("void");
				else
					output.write("::" + returnType.getFullyQualifiedName("::"));
				output.write(" " + className + "::" + methodName);
			}
			output.write("(");

			List parameterTypes = method.getParameterTypes();
			DelimitedCollection parameterList =
																		new DelimitedCollection(parameterTypes);
			DelimitedCollection.Stringifier sf = new DelimitedCollection.Stringifier()
			{
				private int current = 0;

				@Override
				public String toString(TypeName typeName)
				{
					MetaClass mc = MetaClassFactory.getMetaClass(typeName).proxy();
					String result = "::" + mc.getFullyQualifiedName("::") + " p" + current;
					++current;
					return result;
				}
			};
			output.write(parameterList.toString(sf, ", ") + ")");

			// If this is a constructor, we need to handle it differently from other methods
			if (isConstructor)
			{
				output.write("{" + newLine);

				// initialize any arguments we have
				output.write("  JArguments arguments;" + newLine);

				if (parameterTypes.size() > 0)
				{
					output.write("  arguments");
					for (int i = 0; i < parameterTypes.size(); ++i)
						output.write(" << p" + i);
					output.write(";" + newLine);
				}

				// set the jni object for this c++ object to the result of the call to newObject
				output.write("  jobject localRef = newObject(" + className
										 + "::staticGetJavaJniClass(), arguments);" + newLine);
				output.write("  " + className + " result = " + className + "(localRef);" + newLine);
				output.write("  JNIEnv* env = attach();" + newLine);
				output.write("  deleteLocalRef(env, localRef);" + newLine);
				output.write("  return result;" + newLine);
			}
			else
			{
//        if (!accessFlagSet.contains(MethodAccessFlag.STATIC))
//          output.write("const " + newLine);
				output.write(newLine);
				output.write("{" + newLine);

				// Initialize any arguments we have
				output.write("  JArguments arguments;" + newLine);

				if (parameterTypes.size() > 0)
				{
					output.write("  arguments");
					for (int i = 0; i < parameterTypes.size(); ++i)
					{
						output.write(" << p" + i);
					}
					output.write(";" + newLine);
				}

				// If this is a non-void method call we need to return the result of the method call
				output.write("  ");

				if (!returnType.getSimpleName().equals("JVoid"))
					output.write("return ");

				output.write("JMethod< ");
				output.write("::" + returnType.getFullyQualifiedName("::"));
				output.write(" >");
				output.write("(\"" + methodName + "\").invoke(");

				// If this method is static, we need to provide the class info, otherwise we provide a reference to itself.
				if (method.getAccessFlags().contains(MethodAccessFlag.STATIC))
					output.write("staticGetJavaJniClass()");
				else
					output.write("*this");
				output.write(", arguments);" + newLine);
			}
			output.write("}" + newLine);
			output.write(newLine);
		}

		if (!forPeer)
		{
			Util.generateComment(output, "Creates a new null reference." + newLine + newLine
																	 + "All subclasses of JObject should provide this constructor"
																	 + newLine
																	 + "for their own subclasses.");
			output.write(className + "::" + className + "()" + newLine);
			output.write("{}" + newLine);
			output.write(newLine);

			output.write(className + "::" + className + "(jvalue value) " + getInitializerName() + newLine);
			output.write("{" + newLine);
			output.write("  setJavaJniValue(value);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write(className + "::" + className + "(jobject object) " + getInitializerName()
									 + newLine);
			output.write("{" + newLine);
			output.write("  setJavaJniObject(object);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write(className + "::" + className + "(const " + className + "& object) "
									 + getInitializerName()
									 + newLine);
			output.write("{" + newLine);
			output.write("  setJavaJniObject(object);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);
		}

		// Now define the special "one-off" methods that we add to classes like,
		// Object, String, and Throwable to provide better C++ and Java integration.
		String fullyQualifiedName = classFile.getClassName().asIdentifier();
		if (fullyQualifiedName.equals("java.lang.Object"))
		{
			Util.generateComment(output,
				"Provide the standard \"System.out.println()\" semantics for ostreams.");
			output.write("std::ostream& operator<<(std::ostream& out, Object& object)" + newLine);
			output.write("{" + newLine);
			output.write("  out << object.toString();" + newLine);
			output.write("  return out;" + newLine);
			output.write("}" + newLine);
		}
		else if (fullyQualifiedName.equals("java.lang.String"))
		{
			output.write("String::String(const char* str)" + newLine);
			output.write("{" + newLine);
			output.write("  jstring strRef = createString(str);" + newLine);
			output.write("  setJavaJniObject(strRef);" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  deleteLocalRef(env, strRef);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String::String(const std::string& str)" + newLine);
			output.write("{" + newLine);
			output.write("  jstring strRef = createString(str);" + newLine);
			output.write("  setJavaJniObject(strRef);" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  deleteLocalRef(env, strRef);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String::String(const std::wstring& str)" + newLine);
			output.write("{" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  size_t nativeLength = str.size();" + newLine);
			output.write("  if (nativeLength > static_cast(::jace::proxy::java::lang::Integer::MAX_VALUE()))"
									 + newLine);
			output.write("  {" + newLine);
			output.write("    throw JNIException(std::string(\"String::String(const std::wstring& str) - "
									 + "str.size() (\") +" + newLine);
			output.write("      jace::toString(nativeLength) + \") > Integer.MAX_VALUE.\");" + newLine);
			output.write("  }" + newLine);
			output.write("  jsize length = jsize(str.size());" + newLine);
			output.write("  jstring strRef = env->NewString(reinterpret_cast(str.c_str()), length);"
									 + newLine);
			output.write("  setJavaJniObject(strRef);" + newLine);
			output.write("  deleteLocalRef(env, strRef);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String& String::operator=(const String& str)" + newLine);
			output.write("{" + newLine);
			output.write("  setJavaJniObject(str);" + newLine);
			output.write("  return *this;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String::operator std::string() const" + newLine);
			output.write("{" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  jstring thisString = static_cast(static_cast(*this));"
									 + newLine);
			output.write("  jclass cls = getJavaJniClass().getClass();" + newLine);
			output.write("  jmethodID getBytes = env->GetMethodID(cls, \"getBytes\", \"()[B\");" + newLine);
			output.write("  jbyteArray array = static_cast(env->CallObjectMethod(thisString, getBytes));"
									 + newLine);
			output.write(newLine);
			output.write("  if (!array)" + newLine);
			output.write("  {" + newLine);
			output.write("    env->ExceptionDescribe();" + newLine);
			output.write("    env->ExceptionClear();" + newLine);
			output.write("    throw JNIException(\"String::operator std::string()- Unable to get the contents of the java String.\");"
									 + newLine);
			output.write("  }" + newLine);
			output.write(newLine);
			output.write("  int arraySize = env->GetArrayLength(array);" + newLine);
			output.write("  jbyte* byteArray = env->GetByteArrayElements(array, 0);" + newLine);
			output.write(newLine);
			output.write("  if (!byteArray)" + newLine);
			output.write("  {" + newLine);
			output.write("    env->ExceptionDescribe();" + newLine);
			output.write("    env->ExceptionClear();" + newLine);
			output.write("    throw JNIException(\"String::operator std::string() - Unable to get the "
									 + "contents of the java String.\");" + newLine);
			output.write("  }" + newLine);
			output.write(newLine);
			output.write("  std::string str((char*) byteArray, (char*) byteArray + arraySize);" + newLine);
			output.write("  env->ReleaseByteArrayElements(array, byteArray, JNI_ABORT);" + newLine);
			output.write("  deleteLocalRef(env, array);" + newLine);
			output.write("  return str;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String::operator std::wstring() const" + newLine);
			output.write("{" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  jstring thisString = static_cast(static_cast(*this));"
									 + newLine);
			output.write("  const jchar* buffer = env->GetStringChars(thisString, 0);" + newLine);
			output.write("  if (!buffer)" + newLine);
			output.write("  {" + newLine);
			output.write("    env->ExceptionDescribe();" + newLine);
			output.write("    env->ExceptionClear();" + newLine);
			output.write("    throw JNIException(\"String::operator std::wstring() - Unable to get the "
									 + "contents of the java String.\");" + newLine);
			output.write("  }" + newLine);
			output.write("  std::wstring result = reinterpret_cast(buffer);" + newLine);
			output.write("  env->ReleaseStringChars(thisString, buffer);" + newLine);
			output.write("  return result;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			Util.generateComment(output,
				"Creates a new jstring from a std::string using the platform's default charset.");
			output.write("jstring String::createString(const std::string& str)" + newLine);
			output.write("{" + newLine);
			output.write("  JNIEnv* env = attach();" + newLine);
			output.write("  size_t nativeLength = str.size();" + newLine);
			output.write("  if (nativeLength > static_cast(::jace::proxy::java::lang::Integer::MAX_VALUE()))"
									 + newLine);
			output.write("  {" + newLine);
			output.write("    throw JNIException(std::string(\"String::String(const std::string& str) - "
									 + "str.size() (\") +" + newLine);
			output.write("      jace::toString(nativeLength) + \") > Integer.MAX_VALUE.\");" + newLine);
			output.write("  }" + newLine);
			output.write("  jsize bufLen = jsize(nativeLength);" + newLine);
			output.write("  jbyteArray jbuf = env->NewByteArray(bufLen);" + newLine + newLine);
			output.write("  if (!jbuf)" + newLine);
			output.write("  {" + newLine);
			output.write("    env->ExceptionDescribe();" + newLine);
			output.write("    env->ExceptionClear();" + newLine);
			output.write("    throw JNIException(\"String::createString - Unable to allocate a new java String.\");"
									 + newLine);
			output.write("  }" + newLine);
			output.write(newLine);
			output.write("  env->SetByteArrayRegion(jbuf, 0, bufLen, (jbyte*) str.c_str());" + newLine);
			output.write("  jclass cls = getJavaJniClass().getClass();" + newLine);
			output.write("  jmethodID init = env->GetMethodID(cls, \"\", \"([BII)V\");" + newLine);
			output.write("  jstring jstr = static_cast(env->NewObject(cls, init, jbuf, 0, bufLen)); "
									 + newLine);
			output.write(newLine);
			output.write("  if (!jstr)" + newLine);
			output.write("  {" + newLine);
			output.write("    env->ExceptionDescribe();" + newLine);
			output.write("    env->ExceptionClear();" + newLine);
			output.write("    throw JNIException(\"String::createString - Unable to allocate a new java String.\");"
									 + newLine);
			output.write("  }" + newLine);
			output.write(newLine);
			output.write("  deleteLocalRef(env, jbuf);" + newLine);
			output.write("  return jstr;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("std::ostream& operator<<(std::ostream& stream, const String& str)" + newLine);
			output.write("{" + newLine);
			output.write("  return stream << (std::string) str;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("std::string operator+(const std::string& stdStr, const String& jStr)" + newLine);
			output.write("{" + newLine);
			output.write("  return stdStr + (std::string) jStr;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("std::string operator+(const String& jStr, const std::string& stdStr)" + newLine);
			output.write("{" + newLine);
			output.write("  return (std::string) jStr + stdStr;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("String String::operator+(String str)" + newLine);
			output.write("{" + newLine);
			output.write("  return (std::string) *this + (std::string) str;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("bool operator==(const std::string& stdStr, const String& str)" + newLine);
			output.write("{" + newLine);
			output.write("  return (std::string) str == stdStr;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write("bool operator==(const String& str, const std::string& stdStr)" + newLine);
			output.write("{" + newLine);
			output.write("  return (std::string) str == stdStr;" + newLine);
			output.write("}" + newLine);
			output.write(newLine);
		}
		else if (fullyQualifiedName.equals("java.lang.Throwable"))
		{
			output.write("Throwable::~Throwable() throw ()" + newLine);
			output.write("{}" + newLine);
			output.write(newLine);

			output.write("const char* Throwable::what() const throw()" + newLine);
			output.write("{" + newLine);
			output.write("  // JACE really isn't const correct like it should be, yet." + newLine);
			output.write("  // For now, the easiest way to get around this is to cast constness away."
									 + newLine);
			output.write("  Throwable* t = const_cast(this);" + newLine);
			output.write(newLine);
			output.write("  /* Get the string contents of this exception." + newLine);
			output.write("   */" + newLine);
			output.write("  t->msg = t->toString();" + newLine);
			output.write(newLine);
			output.write("  /* Return a handle to the msg." + newLine);
			output.write("   */" + newLine);
			output.write("  return t->msg.c_str();" + newLine);
			output.write("}" + newLine);
			output.write(newLine);
		}
	}

	/**
	 * Same as generateFieldDefinitions(output, false).
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 * @see generateFieldDefinitions(Writer, boolean)
	 */
	public void generateFieldDefinitions(Writer output) throws IOException
	{
		generateFieldDefinitions(output, false);
	}

	/**
	 * Generate the field definitions.
	 *
	 * @param output the output writer
	 * @param forPeer true if the fields are being generated for a peer, false for a proxy
	 * @throws IOException if an error occurs while writing
	 */
	public void generateFieldDefinitions(Writer output, boolean forPeer) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		String className = metaClass.getSimpleName();

		Collection methodNames = Lists.newArrayListWithCapacity(classFile.getMethods().size());
		for (ClassMethod method: classFile.getMethods())
		{
			if (shouldBeSkipped(method))
				continue;
			methodNames.add(CKeyword.adjust(method.getName()));
		}

		for (ClassField field: classFile.getFields())
		{
			if (shouldBeSkipped(field))
				continue;
			MetaClass mc = MetaClassFactory.getMetaClass(field.getDescriptor()).proxy();

			// Don't generate the field if it's type is not part of our dependency list
			if (!dependencyFilter.accept(mc))
				continue;

			String name = field.getName();
			if (forPeer && name.equals("jaceNativeHandle"))
				continue;

			// handle clashes between C++ keywords and java identifiers
			name = CKeyword.adjust(name);

			// handle clashes with method names by prefixing an underscore to the field name
			if (methodNames.contains(name))
				name = "_" + name;

			// handle clashes with "reserved fields"
			if (reservedFields.contains(name))
				name += "_Jace";

			String fieldType = "::jace::JField< " + "::" + mc.getFullyQualifiedName("::") + " >";
			String proxyType = "::jace::JFieldProxy< " + "::" + mc.getFullyQualifiedName("::") + " >";
			FieldAccessFlagSet accessFlagSet = field.getAccessFlags();

			Util.generateComment(output, accessFlagSet.getName() + " " + name);

			String modifiers = "";

			// can't call non-const methods on const fields. There's probably a better way of going about doing this.
			//
			//if (accessFlagSet.contains(FieldAccessFlag.FINAL))
			//  modifiers = modifiers + "const ";

			output.write(modifiers + proxyType + " " + className + "::" + name + "()" + newLine);
			output.write("{" + newLine);
			output.write("  return " + fieldType + "(\"" + name + "\").get(");

			// if this field is static, we need to provide the class info, otherwise we provide a reference to itself
			if (accessFlagSet.contains(FieldAccessFlag.STATIC))
				output.write("staticGetJavaJniClass()");
			else
				output.write("*this");

			output.write(");" + newLine);
			output.write("}" + newLine);
			output.write(newLine);
		}
	}

	/**
	 * Same as generateJaceDefinitions(output, false).
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 * @see generateJaceDefinitions(Writer, boolean).
	 */
	public void generateJaceDefinitions(Writer output) throws IOException
	{
		generateJaceDefinitions(output, false);
	}

	/**
	 * Generate the jace-specific methods.
	 *
	 * @param output the output writer
	 * @param forPeer true if the fields are being generated for a peer, false for a proxy
	 * @throws IOException if an error occurs while writing
	 */
	public void generateJaceDefinitions(Writer output, boolean forPeer) throws IOException
	{
		MetaClass classMetaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		String className = classMetaClass.getSimpleName();

		Util.generateComment(output, "The following methods are required to integrate this class"
																 + newLine
																 + "with the Jace framework.");

		if (forPeer)
		{
			output.write(className + "::" + className + "(jobject jPeer) " + getInitializerName()
									 + newLine);
			output.write("{" + newLine);
			output.write("  setJavaJniObject(jPeer);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write(className + "::" + className + "(const " + className + "& jPeer) "
									 + getInitializerName() + newLine);
			output.write("{" + newLine);
			output.write("  // The default copy-constructor causes JObject::setJavaJniValue()" + newLine);
			output.write("  // to get invoked multiple times (once per superclass). Instead" + newLine);
			output.write("  // we invoke each superclass' default constructor and initialize" + newLine);
			output.write("  // JObject once." + newLine);
			output.write("  setJavaJniValue(jPeer);" + newLine);
			output.write("}" + newLine);
			output.write(newLine);

			output.write(className + "::~" + className + "() throw ()" + newLine);
			output.write("{}" + newLine);
			output.write(newLine);
		}

		output.write("static boost::mutex javaClassMutex;" + newLine);
		output.write("const JClass& " + className
								 + "::staticGetJavaJniClass() throw (::jace::JNIException)" + newLine);
		output.write("{" + newLine);
		output.write("  static boost::shared_ptr result;" + newLine);
		output.write("  boost::mutex::scoped_lock lock(javaClassMutex);" + newLine);
		output.write("  if (result == 0)" + newLine);
		output.write("    result = boost::shared_ptr(new JClassImpl(\"" + classFile.
			getClassName() + "\"));"
								 + newLine);
		output.write("  return *result;" + newLine);
		output.write("}" + newLine);
		output.write(newLine);

		output.write("const JClass& " + className
								 + "::getJavaJniClass() const throw (::jace::JNIException)" + newLine);
		output.write("{" + newLine);
		output.write("  return " + className + "::staticGetJavaJniClass();" + newLine);
		output.write("}" + newLine);

		if (forPeer)
		{
			output.write(newLine);
			output.write("::" + classMetaClass.getFullyQualifiedName("::") + " " + className
									 + "::getJaceProxy()" + newLine);
			output.write("{" + newLine);
			output.write("  return ::" + classMetaClass.getFullyQualifiedName("::")
									 + "(static_cast(static_cast(*this)));"
									 + newLine);
			output.write("}" + newLine);
		}

		try
		{
			if (!forPeer && isException(classFile.getClassName()))
			{
				output.write(newLine);
				output.write("JEnlister< " + className + " > " + className + "::enlister;" + newLine);
			}
		}
		catch (ClassNotFoundException e)
		{
			throw new IOException(e);
		}
	}

	/**
	 * Indicates if a class is an exception.
	 *
	 * @param className the class name
	 * @return true if the class is an exception
	 * @throws ClassNotFoundException if the class was not found
	 * @throws IOException if an error occurs while trying to locate a class file
	 */
	private boolean isException(TypeName className) throws ClassNotFoundException, IOException
	{
		ClassFile c = new ClassFile(classPath.openClass(className));
		while (true)
		{
			if (c.getClassName().asIdentifier().equals("java.lang.Throwable"))
				return true;
			TypeName superName = c.getSuperClassName();
			if (superName == null)
				return false;
			c = new ClassFile(classPath.openClass(superName));
		}
	}

	/**
	 * Indicates if a class is an enum.
	 *
	 * @param className the class name
	 * @return true if the class is an exception
	 * @throws ClassNotFoundException if the class was not found
	 * @throws IOException if an error occurs while trying to locate the class file
	 */
	private boolean isEnum(TypeName className) throws ClassNotFoundException, IOException
	{
		ClassFile c = new ClassFile(classPath.openClass(className));
		if (c.getSuperClassName() == null)
			return false;
		return c.getSuperClassName().asIdentifier().equals("java.lang.Enum");
	}

	/**
	 * Returns the superclass of a class.
	 *
	 * @param className the class name
	 * @return null if className is object, otherwise the superclass
	 * @throws ClassNotFoundException if the class was not found
	 * @throws IOException if an error occurs while trying to locate a class file
	 */
	private TypeName getSuperclass(TypeName className) throws ClassNotFoundException, IOException
	{
		ClassFile c = new ClassFile(classPath.openClass(className));
		return c.getSuperClassName();
	}

	/**
	 * Same as getInitializerValue(false).
	 *
	 * @return the initializer list
	 * @see defineInitializerValue(boolean)
	 */
	public String getInitializerValue()
	{
		return getInitializerValue(false);
	}

	/**
	 * Returns the initializer list for the current class.
	 *
	 * For example, ": Object(), OutputStream()"
	 *
	 * This could probably be placed in MetaClass. It might not hurt to
	 * put the initializers in some sort of array instead of a formatted
	 * String.
	 *
	 * This method used to include interfaces in the initializer list,
	 * but now that interfaces have a default constructor that does what
	 * we want, they are no longer included.
	 *
	 * @param forPeer true if the methods are being generated for a peer, false for a proxy
	 * @return the initializer list
	 */
	public String getInitializerValue(boolean forPeer)
	{
		TypeName objectName = TypeNameFactory.fromPath("java/lang/Object");
		TypeName superName = classFile.getSuperClassName();

		String initializerName = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy().
			getSimpleName() + "_INITIALIZER";

		StringBuilder definition = new StringBuilder(16 + initializerName.length());
		if (classFile.getClassName().equals(objectName))
		{
			definition.append("#define ");
			definition.append(initializerName);
			definition.append(newLine);
		}
		else if (superName.equals(objectName))
		{
			definition.append("#define ");
			definition.append(initializerName);
			if (forPeer)
				definition.append(" : ::jace::Peer(jPeer)");
			definition.append(newLine);
		}
		else
		{
			Collection constructors = Lists.newArrayListWithCapacity(2);
			constructors.add("::" + MetaClassFactory.getMetaClass(superName).proxy().getFullyQualifiedName(
				"::") + "()");
			if (forPeer)
				constructors.add("::jace::Peer(jPeer)");
			DelimitedCollection delimited = new DelimitedCollection(constructors);

			definition.append("#define ").append(initializerName);
			if (!constructors.isEmpty())
				definition.append(" : ").append(delimited.toString(", "));
			definition.append(newLine);
		}
		return definition.toString();
	}

	/**
	 * Returns the name of the initializer list.
	 *
	 * For example, "java/lang/Cloneable_INITIALIZER"
	 *
	 * @return the name of the intializer list
	 */
	private String getInitializerName()
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		return metaClass.getSimpleName() + "_INITIALIZER";
	}

	/**
	 * Generate the class file declaration.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	private void generateClassDeclaration(Writer output) throws IOException
	{
		beginNamespace(output);
		output.write(newLine);

		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();

		Util.generateComment(output,
			"The Jace C++ proxy class for " + classFile.getClassName().asIdentifier() + "."
			+ newLine + "Please do not edit this class, as any changes you make will be "
			+ "overwritten." + newLine
			+ "For more information, please refer to the Jace Developer's Guide.");

		output.write("class " + metaClass.getSimpleName() + ": public ");

		// Write out the super classes.
		TypeName superName = classFile.getSuperClassName();
		if (superName == null)
			output.write("virtual JObject");
		else
		{
			// if we are derived directly from java.lang.Object, we need to derive from it virtually
			if (superName.asIdentifier().equals("java.lang.Object"))
				output.write("virtual ");
			MetaClass superMetaClass = MetaClassFactory.getMetaClass(superName).proxy();
			output.write("::" + superMetaClass.getFullyQualifiedName("::"));

			// We add in special support for java.lang.Throwable, because we want it to derive from std::exception.
			if (classFile.getClassName().asIdentifier().equals("java.lang.Throwable"))
				output.write(", public std::exception");

			// now, add all of the interfaces that are implemented
			for (TypeName i: classFile.getInterfaces())
			{
				MetaClass interfaceClass = MetaClassFactory.getMetaClass(i).proxy();
				output.write(", public virtual ");
				output.write("::" + interfaceClass.getFullyQualifiedName("::"));
			}
		}
		output.write(newLine);

		output.write("{" + newLine);
		StringWriter contents = new StringWriter();
		contents.append("public:" + newLine);

		generateEnumDeclarations(contents);
		generateMethodDeclarations(contents);
		generateFieldDeclarations(contents, false);
		output.write(Util.indent(contents.toString(), 2));
		contents = new StringWriter();
		output.write("private:" + newLine);
		generateJaceDeclarations(contents);
		output.write(Util.indent(contents.toString(), 2));

		output.write("};" + newLine);
		output.write(newLine);

		endNamespace(output);
		output.write(newLine);

		// declare (and if necessary define) the ElementProxy specialization
		String fullName = "::" + metaClass.getFullyQualifiedName("::");

		output.write("BEGIN_NAMESPACE(jace)" + newLine);
		output.write(newLine);
		output.write("#ifndef PUT_TSDS_IN_HEADER" + newLine);
		output.write("  template <> ElementProxy< " + fullName
								 + " >::ElementProxy(jarray array, jvalue element, int index);" + newLine);
		output.write("  template <> ElementProxy< " + fullName
								 + " >::ElementProxy(const jace::ElementProxy< " + fullName
								 + " >& proxy);" + newLine);
		output.write("#else" + newLine);
		printElementProxyTsd(output, metaClass);
		output.write("#endif" + newLine);
		output.write(newLine);

		// declare (and if necessary define) the FieldProxy specialization
		output.write("#ifndef PUT_TSDS_IN_HEADER" + newLine);
		output.write("  template <> JFieldProxy< " + fullName
								 + " >::JFieldProxy(jfieldID _fieldID, jvalue value, jobject _parent);" + newLine);
		output.write("  template <> JFieldProxy< " + fullName
								 + " >::JFieldProxy(jfieldID _fieldID, jvalue value, jclass _parentClass);"
								 + newLine);
		output.write("  template <> JFieldProxy< " + fullName
								 + " >::JFieldProxy(const ::jace::JFieldProxy< " + fullName
								 + " >& object);" + newLine);
		output.write("#else" + newLine);
		printFieldProxyTsd(output, metaClass);
		output.write("#endif" + newLine);
		output.write(newLine);
		output.write("END_NAMESPACE(jace)" + newLine);
	}

	/**
	 * Generates the ElementProxy template specialization declaration.
	 *
	 * @param output the output writer
	 * @param mc the class meta-data
	 * @throws IOException if an error occurs while writing
	 */
	private void printElementProxyTsd(Writer output, MetaClass mc) throws IOException
	{
		String name = "::" + mc.getFullyQualifiedName("::");

		// normal constructor
		output.write("  template <> inline ElementProxy< " + name
								 + " >::ElementProxy(jarray array, jvalue element, int _index): " + newLine);

		if (mc.getFullyQualifiedName("/").equals(JaceConstants.getProxyPackage().asPath()
																						 + "/java/lang/Object"))
			output.write("    Object(element), index(_index)");
		else
			output.write("    " + name + "(element), index(_index)");
		output.write(newLine);

		output.write("  {" + newLine);
		output.write("    JNIEnv* env = attach();" + newLine);
		output.write("    parent = static_cast(newGlobalRef(env, array));" + newLine);
		output.write("  }" + newLine);

		// copy constructor
		output.write("  template <> inline ElementProxy< " + name
								 + " >::ElementProxy(const jace::ElementProxy< " + name
								 + " >& proxy): " + newLine);

		if (mc.getFullyQualifiedName("/").equals(JaceConstants.getProxyPackage().asPath()
																						 + "/java/lang/Object"))
			output.write("    Object(proxy), index(proxy.index)");
		else
			output.write("    " + name + "(proxy), index(proxy.index)");
		output.write(newLine);

		output.write("  {" + newLine);
		output.write("    JNIEnv* env = attach();" + newLine);
		output.write("    parent = static_cast(newGlobalRef(env, proxy.parent));" + newLine);
		output.write("  }" + newLine);
	}

	/**
	 * Generate the JFieldProxy template specification declaration.
	 *
	 * @param output the output writer
	 * @param mc the class meta-data
	 * @throws IOException if an error occurs while writing
	 */
	private void printFieldProxyTsd(Writer output, MetaClass mc) throws IOException
	{
		String name = "::" + mc.getFullyQualifiedName("::");

		// normal constructor
		output.write("  template <> inline JFieldProxy< " + name
								 + " >::JFieldProxy(jfieldID _fieldID, jvalue value, jobject _parent): " + newLine);

		if (mc.getFullyQualifiedName("/").equals(JaceConstants.getProxyPackage().asPath()
																						 + "/java/lang/Object"))
			output.write("    Object(value), fieldID(_fieldID)");
		else
			output.write("    " + name + "(value), fieldID(_fieldID)");
		output.write(newLine);

		output.write("  {" + newLine);
		output.write("    JNIEnv* env = attach();" + newLine);
		output.write(newLine);
		output.write("    if (_parent)" + newLine);
		output.write("      parent = newGlobalRef(env, _parent);" + newLine);
		output.write("    else" + newLine);
		output.write("      parent = _parent;" + newLine);
		output.write(newLine);
		output.write("    parentClass = 0;" + newLine);
		output.write("  }" + newLine);

		// static normal constructor
		output.write("  template <> inline JFieldProxy< " + name
								 + " >::JFieldProxy(jfieldID _fieldID, jvalue value, jclass _parentClass): "
								 + newLine);

		if (mc.getFullyQualifiedName("/").equals(JaceConstants.getProxyPackage().asPath()
																						 + "/java/lang/Object"))
			output.write("    Object(value), fieldID(_fieldID)");
		else
			output.write("    " + name + "(value), fieldID(_fieldID)");
		output.write(newLine);

		output.write("  {" + newLine);
		output.write("    JNIEnv* env = attach();" + newLine);
		output.write(newLine);
		output.write("    parent = 0;" + newLine);
		output.write("    parentClass = static_cast(newGlobalRef(env, _parentClass));" + newLine);
		output.write("  }" + newLine);

		// copy constructor
		output.write("  template <> inline JFieldProxy< " + name + " >::JFieldProxy(const JFieldProxy< "
								 + name
								 + " >& object): " + newLine);

		if (mc.getFullyQualifiedName("/").equals(JaceConstants.getProxyPackage().asPath()
																						 + "/java/lang/Object"))
			output.write("    Object(object)");
		else
			output.write("    " + name + "(object)");
		output.write(newLine);

		output.write("  {" + newLine);
		output.write("    fieldID = object.fieldID; " + newLine);
		output.write(newLine);
		output.write("    if (object.parent)" + newLine);
		output.write("    {" + newLine);
		output.write("      JNIEnv* env = attach();" + newLine);
		output.write("      parent = newGlobalRef(env, object.parent);" + newLine);
		output.write("    }" + newLine);
		output.write("    else" + newLine);
		output.write("      parent = 0;" + newLine);
		output.write(newLine);
		output.write("    if (object.parentClass)" + newLine);
		output.write("    {" + newLine);
		output.write("      JNIEnv* env = attach();" + newLine);
		output.write("      parentClass = static_cast(newGlobalRef(env, object.parentClass));"
								 + newLine);
		output.write("    }" + newLine);
		output.write("    else" + newLine);
		output.write("      parentClass = 0;" + newLine);
		output.write("  }" + newLine);
	}

	/**
	 * Indicates if the method parameters and return-type are part of the dependency listing.
	 *
	 * @param method the method
	 * @return true if the method parameters and return-type are part of the dependency listing
	 */
	private boolean isPartOfDependencies(ClassMethod method)
	{
		if (dependencyFilter instanceof AcceptAll)
			return true;

		String methodName = method.getName();
		boolean isConstructor = methodName.equals("");

		if (!isConstructor)
		{
			MetaClass returnType = MetaClassFactory.getMetaClass(method.getReturnType()).proxy();
			if (returnType instanceof ArrayMetaClass)
				returnType = ((ArrayMetaClass) returnType).getInnermostElementType();

			if (!dependencyFilter.accept(returnType))
			{
				log.debug("ReturnType: " + returnType + " not in dependency list");
				return false;
			}
		}

		for (TypeName parameter: method.getParameterTypes())
		{
			MetaClass parameterType = MetaClassFactory.getMetaClass(parameter).proxy();

			if (parameterType instanceof ArrayMetaClass)
				parameterType = ((ArrayMetaClass) parameterType).getInnermostElementType();

			if (!dependencyFilter.accept(parameterType))
			{
				log.debug("ParameterType: " + parameterType + " not in dependency list");
				return false;
			}
		}
		return true;
	}

	/**
	 * Generates ordinal values for C++ switch blocks.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	private void generateEnumDeclarations(Writer output) throws IOException
	{
		try
		{
			if (isEnum(classFile.getClassName()))
			{
				Util.generateComment(output, "Enum ordinal() values.");
				output.write("class Ordinals" + newLine);
				output.write("{" + newLine);
				output.write("public:" + newLine);
				output.write("  enum" + newLine);
				output.write("  {");
				ClassNode classNode = new ClassNode();
				ClassReader classReader = new ClassReader(classPath.openClass(classFile.getClassName()));
				classReader.accept(classNode, 0);
				for (Object o: classNode.methods)
				{
					MethodNode method = (MethodNode) o;
					if (!method.name.equals(""))
						continue;
					LinkedList methodStack = Lists.newLinkedList();
					/**
					 * Maps a INVOKESPECIAL node to the ordinal value it set.
					 */
					Map instructionToOrdinal = Maps.newHashMap();
					for (AbstractInsnNode node: method.instructions.toArray())
					{
						switch (node.getOpcode())
						{
							case Opcodes.NEW:
							{
								methodStack.add(node);
								break;
							}
							case Opcodes.DUP:
							{
								methodStack.add(methodStack.getLast());
								break;
							}
							case Opcodes.ICONST_0:
							{
								methodStack.add(0);
								break;
							}
							case Opcodes.ICONST_1:
							{
								methodStack.add(1);
								break;
							}
							case Opcodes.ICONST_2:
							{
								methodStack.add(2);
								break;
							}
							case Opcodes.ICONST_3:
							{
								methodStack.add(3);
								break;
							}
							case Opcodes.ICONST_4:
							{
								methodStack.add(4);
								break;
							}
							case Opcodes.ICONST_5:
							{
								methodStack.add(5);
								break;
							}
							case Opcodes.BIPUSH:
							case Opcodes.SIPUSH:
							{
								IntInsnNode intInstruction = (IntInsnNode) node;
								methodStack.add(intInstruction.operand);
								break;
							}
							case Opcodes.LDC:
							{
								LdcInsnNode ldcInstruction = (LdcInsnNode) node;
								methodStack.add(ldcInstruction.cst);
								break;
							}
							case Opcodes.INVOKESPECIAL:
							{
								if (log.isDebugEnabled())
									log.debug("INVOKESPECIAL: stack=" + methodStack);
								MethodInsnNode invoke = (MethodInsnNode) node;
								int numArguments = Type.getArgumentTypes(invoke.desc).length;
								if (log.isTraceEnabled())
								{
									log.trace("invoke.superClass=" + getSuperclass(
										TypeNameFactory.fromPath(invoke.owner)).asPath() + ", classNode.name="
														+ classNode.name);
								}

								// Enum constructors have the following signature:
								// (String name, int ordinal, arguments...)
								if (log.isTraceEnabled())
									log.trace("numArguments=" + numArguments);
								if (numArguments >= 2)
								{
									// +1 for "this" implicit argument
									int indexOfFirstArgument = methodStack.size() - (numArguments + 1);
									Integer ordinal = (Integer) methodStack.get(indexOfFirstArgument + 2);
									assert (ordinal != null): "ordinal is null at " + classNode.name;
									instructionToOrdinal.put(node, ordinal);
									if (log.isDebugEnabled())
										log.debug("INVOKESPECIAL: " + node + "=" + ordinal);
								}
								for (int i = 0, size = 1 + numArguments; i < size; ++i)
									methodStack.removeLast();
								methodStack.add(node);
								break;
							}
							case Opcodes.PUTSTATIC:
							{
								FieldInsnNode fieldNode = (FieldInsnNode) node;
								Object value = methodStack.removeLast();
								if (!fieldNode.desc.equals(Type.getObjectType(classNode.name).getDescriptor()))
									continue;
								if (!(value instanceof MethodInsnNode))
									continue;
								MethodInsnNode lastInstruction = (MethodInsnNode) value;
								if (lastInstruction.getOpcode() != Opcodes.INVOKESPECIAL)
									continue;
								int ordinal = instructionToOrdinal.get(lastInstruction);
								if (log.isDebugEnabled())
									log.debug("PUTSTATIC: " + value + "=" + ordinal);

								if (ordinal == 0)
									output.write(newLine);
								else
									output.write("," + newLine);
								output.write("      " + fieldNode.name + " = " + ordinal);
							}
						}
					}
				}
				output.write(newLine);
				output.write("  };" + newLine);
				output.write("};" + newLine);
			}
		}
		catch (ClassNotFoundException e)
		{
			throw new AssertionError(e);
		}
	}

	/**
	 * Generates the method declaration.
	 *
	 * @param output the output writer
	 * @param metaClass the MetaClass describing the class
	 * @param method the method
	 * @param invokeStyle the method invocation style
	 * @throws IOException if an error occurs while writing
	 */
	public void generateMethodDeclaration(Writer output, MetaClass metaClass, ClassMethod method,
																				InvokeStyle invokeStyle) throws IOException
	{
		if (shouldBeSkipped(method))
			return;

		if (!isPartOfDependencies(method))
			return;

		String className = metaClass.getSimpleName();
		String methodName = method.getName();
		boolean isConstructor = methodName.equals("");
		MethodAccessFlagSet accessFlagSet = method.getAccessFlags();

		// handle clashes between C++ keywords and java identifiers
		methodName = CKeyword.adjust(methodName);

		// if this is a static initializer, then we don't need to declare it
		if (methodName.equals(""))
			return;

		Util.generateComment(output, isConstructor ? "Creates a new " + className + "." : methodName);

		if (exportSymbols)
			output.write("JACE_PROXY_API ");

		// if this is a constructor, there is no return-type
		if (isConstructor)
			output.write("static " + className + " create");
		else
		{
			if (accessFlagSet.contains(MethodAccessFlag.STATIC))
				output.write("static ");

			if (invokeStyle.equals(InvokeStyle.VIRTUAL))
			{
				if (accessFlagSet.contains(MethodAccessFlag.STATIC))
					throw new IllegalStateException("A method may not be both static and virtual");
				output.write("virtual ");
			}

			MetaClass returnType = MetaClassFactory.getMetaClass(method.getReturnType()).proxy();

			if (returnType.getSimpleName().equals("JVoid"))
				output.write("void");
			else
				output.write("::" + returnType.getFullyQualifiedName("::"));
			output.write(" ");
			output.write(methodName);
		}
		output.write("(");

		List parameterTypes = method.getParameterTypes();
		DelimitedCollection parameterList = new DelimitedCollection(parameterTypes);
		DelimitedCollection.Stringifier sf = new DelimitedCollection.Stringifier()
		{
			private int current = 0;

			@Override
			public String toString(TypeName typeName)
			{
				MetaClass mc = MetaClassFactory.getMetaClass(typeName).proxy();
				String result = "::" + mc.getFullyQualifiedName("::") + " p" + current;
				++current;
				return result;
			}
		};
		output.write(parameterList.toString(sf, ", ") + ")");

		if (!isConstructor && !accessFlagSet.contains(MethodAccessFlag.STATIC))
		{
			// All of the methods should be declared const so that you can
			// call methods on const fields.
			// output.write(" const");
		}

		output.write(";" + newLine);
	}

	/**
	 * Generates the method declarations.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	private void generateMethodDeclarations(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();

		Collection methods = classFile.getMethods();
		String fullyQualifiedName = classFile.getClassName().asIdentifier();
		StringWriter constructors = new StringWriter();
		StringWriter nonConstructors = new StringWriter();
		for (Iterator i = methods.iterator(); i.hasNext();)
		{
			ClassMethod method = i.next();
			String methodName = method.getName();
			Writer writer;
			if (methodName.equals(""))
				writer = constructors;
			else
				writer = nonConstructors;
			generateMethodDeclaration(writer, metaClass, method, InvokeStyle.NORMAL);
		}
		String factoryBody = constructors.toString();

		// Interfaces do not have constructors
		if (!factoryBody.isEmpty())
		{
			output.write("class Factory" + newLine);
			output.write("{" + newLine);
			output.write("public:" + newLine);
			output.write(Util.indent(factoryBody.toString(), 2));
			output.write("};" + newLine);
			output.write(newLine);
		}
		output.write("public: " + newLine);
		Util.generateComment(output, "Creates a new null reference." + newLine + newLine
																 + "All subclasses of JObject should provide this constructor"
																 + newLine
																 + "for their own subclasses.");
		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write("explicit " + metaClass.getSimpleName() + "();" + newLine);

		Util.generateComment(output, "Copy an existing reference.");
		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write(metaClass.getSimpleName() + "(const " + metaClass.getSimpleName() + "&);" + newLine);

		output.write(nonConstructors.toString());

		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write("virtual const JClass& getJavaJniClass() const throw (::jace::JNIException);"
								 + newLine);
		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write("static const JClass& staticGetJavaJniClass() throw (::jace::JNIException);"
								 + newLine);
		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write("explicit " + metaClass.getSimpleName() + "(jvalue);" + newLine);
		if (exportSymbols)
			output.write("JACE_PROXY_API ");
		output.write("explicit " + metaClass.getSimpleName() + "(jobject);" + newLine);

		// now declare the special "one-off" methods that we add to classes like, Object, String, and Throwable to provide
		// better C++ and Java integration
		if (fullyQualifiedName.equals("java.lang.Object"))
		{
			Util.generateComment(output,
				"Provide the standard \"System.out.println()\" semantics for ostreams.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend std::ostream& operator<<(std::ostream& out, Object& object);" + newLine);
		}
		else if (fullyQualifiedName.equals("java.lang.String"))
		{
			Util.generateComment(output, "Creates a String from a C string.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("String(const char*);" + newLine);

			Util.generateComment(output,
				"Creates a new jstring from a std::string using the platform's default charset.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("String(const std::string&);" + newLine);

			Util.generateComment(output, "Creates a String from a std::wstring.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("String(const std::wstring&);" + newLine);

			Util.generateComment(output, "Handle assignment between two Strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("String& operator=(const String& str);" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Converts a String to a std::string.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("operator std::string() const;" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Converts a String to a std::wstring.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("operator std::wstring() const;" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Allows Strings to be written to ostreams.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend std::ostream& operator<<(std::ostream& stream, const String& str);"
									 + newLine);
			output.write(newLine);

			Util.generateComment(output, "Provide concatentation for Strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("String operator+(String);" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Provide concatenation between Strings and std::strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend std::string operator+(const std::string&, const String&);" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Provide concatenation between Strings and std::strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend std::string operator+(const String&, const std::string&);" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Provide comparison between Strings and std::strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend bool operator==(const std::string&, const String&);" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Provide comparison between Strings and std::strings.");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("friend bool operator==(const String&, const std::string&);" + newLine);
			output.write(newLine);
		}
		else if (fullyQualifiedName.equals("java.lang.Throwable"))
		{
			Util.generateComment(output, "Need to support a non-throwing destructor");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("~Throwable() throw ();" + newLine);
			output.write(newLine);

			Util.generateComment(output, "Overrides std::exception::what() by returning this.toString();");
			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write("const char* what() const throw();" + newLine);
			output.write(newLine);
		}
	}

	/**
	 * Generates the field declarations.
	 *
	 * @param output the output writer
	 * @param forPeer true if the fields are being generated for a peer, false for a proxy
	 * @throws IOException if an error occurs while writing
	 */
	public void generateFieldDeclarations(Writer output, boolean forPeer) throws IOException
	{
		// Generate a list of method names so we can
		// handle field/method-name clashes.
		Collection methodNames = Lists.newArrayListWithCapacity(classFile.getMethods().size());
		for (ClassMethod method: classFile.getMethods())
		{
			if (shouldBeSkipped(method))
				continue;
			methodNames.add(CKeyword.adjust(method.getName()));
		}

		for (ClassField field: classFile.getFields())
		{
			if (shouldBeSkipped(field))
				continue;

			MetaClass mc = MetaClassFactory.getMetaClass(field.getDescriptor()).proxy();

			// Don't generate the field if it's type is not part of our dependency list
			if (!dependencyFilter.accept(mc))
				continue;

			String name = field.getName();

			if (forPeer && name.equals("jaceNativeHandle"))
				continue;

			// handle clashes between C++ keywords and java identifiers by appending an underscore to the end of the java
			// identifier
			name = CKeyword.adjust(name);

			// handle clashes with method names by prefixing an underscore to the field name
			if (methodNames.contains(name))
				name = "_" + name;

			// handle clashes with "reserved fields"
			if (reservedFields.contains(name))
				name += "_Jace";

			String type = "::jace::JFieldProxy< " + "::" + mc.getFullyQualifiedName("::") + " >";
			FieldAccessFlagSet accessFlagSet = field.getAccessFlags();

			Util.generateComment(output, accessFlagSet.getName() + " " + name);

			String modifiers;
			if (accessFlagSet.contains(FieldAccessFlag.STATIC))
				modifiers = "static ";
			else
				modifiers = "";

			// Can't call non-const methods on const fields. There's probably
			// a better way of going about doing this.
			//
			// if (accessFlagSet.contains(FieldAccessFlag.FINAL))
			// modifiers = modifiers + "const ";

			if (exportSymbols)
				output.write("JACE_PROXY_API ");
			output.write(modifiers + type + " " + name + "();" + newLine);
			output.write(newLine);
		}

		if (classFile.getClassName().asIdentifier().equals("java.lang.Throwable"))
		{
			// now define the special "one-off" methods that we add to classes like,
			// Object, String, and Throwable to provide better C++ and Java integration
			Util.generateComment(output, "The message represented by this Throwable." + newLine + newLine
																	 + "This member variable is necessary to keep the contract"
																	 + newLine
																	 + "for exception.what().");
			output.write("private: " + newLine);
			output.write("std::string msg;" + newLine);
			output.write("public: " + newLine);
			output.write(newLine);
		}
	}

	/**
	 * Generates Jace-specific method declarations.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	private void generateJaceDeclarations(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		String className = metaClass.getSimpleName();

		Util.generateComment(output, "The following methods are required to integrate this class"
																 + newLine
																 + "with the Jace framework.");
		try
		{
			if (isException(classFile.getClassName()))
			{
				output.write("static JEnlister< " + className + " > enlister;" + newLine);
				output.write("template  friend class ::jace::JEnlister;" + newLine);
			}
		}
		catch (ClassNotFoundException e)
		{
			throw new IOException(e);
		}
		if (classFile.getClassName().asIdentifier().equals("java.lang.String"))
		{
			Util.generateComment(output,
				"Creates a new jstring from a std::string using the platform's default charset.");
			output.write("jstring createString(const std::string& str);" + newLine);
			output.write(newLine);
		}
		// Function namespace and return-type must be separated using parenthesis:
		//
		// REFERENCE: http://stackoverflow.com/questions/2358524/why-does-this-separate-definition-cause-an-error/2358557#2358557
		output.write("template  friend T (::jace::java_cast)(const ::jace::proxy::JObject&);"
								 + newLine);
		for (int i = 0; i < 11; ++i)
		{
			output.write("template  friend T (::jace::java_new)(");
			for (int j = 0; j < i; ++j)
			{
				output.write("A" + j + " a" + j);
				if (j < i - 1)
					output.write(", ");
			}
			output.write(");" + newLine);
		}
		output.write("template  friend T (::jace::java_new)(const char*);" + newLine);
		output.write("template  friend T (::jace::java_new)(const ::std::string&);"
								 + newLine);
		output.write("template  friend T (::jace::java_new)(const ::std::wstring&);"
								 + newLine);
		output.write("template  friend class ::jace::ElementProxy;" + newLine);
		output.write("template  friend class ::jace::JFieldProxy;" + newLine);
		output.write("template  friend class ::jace::JMethod;" + newLine);
	}

	/**
	 * Generates the opening of a namespace.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	public void beginNamespace(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		ClassPackage classPackage = metaClass.getPackage();

		List path = classPackage.getPath();
		if (path.isEmpty())
			return;

		StringBuilder namespace = new StringBuilder("BEGIN_NAMESPACE_");
		namespace.append(path.size());
		namespace.append("(");
		namespace.append(classPackage.toName(", ", false));
		namespace.append(")");
		output.write(namespace.toString() + newLine);
	}

	/**
	 * Closes the namespace declaration.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	private void endNamespace(Writer output) throws IOException
	{
		MetaClass metaClass = MetaClassFactory.getMetaClass(classFile.getClassName()).proxy();
		ClassPackage classPackage = metaClass.getPackage();

		List path = classPackage.getPath();
		if (path.isEmpty())
			return;

		StringBuilder namespace = new StringBuilder("END_NAMESPACE_");
		namespace.append(path.size());
		namespace.append("(");
		namespace.append(classPackage.toName(", ", false));
		namespace.append(")");
		output.write(namespace.toString() + newLine);
	}

	/**
	 * Generate includes for the Jace library headers, which should always be included.
	 *
	 * @param output the output writer
	 * @param forPeer true if the methods are being generated for a peer, false for a proxy
	 * @throws IOException if an error occurs while writing
	 */
	public void includeStandardHeaders(Writer output, boolean forPeer) throws IOException
	{
		// We don't need to include any of the primitive types (JInt, JByte, etc...)
		// because they are all included by both JArray.h and JFieldProxy.h
		output.write("#include \"jace/OsDep.h\"" + newLine);
		output.write("#include \"jace/Namespace.h\"" + newLine);

		if (!forPeer)
		{
			output.write("#include \"" + JaceConstants.getProxyPackage().asPath() + "/JObject.h\""
									 + newLine);
			try
			{
				if (isException(classFile.getClassName()))
					output.write("#include \"jace/JEnlister.h\"" + newLine);
			}
			catch (ClassNotFoundException e)
			{
				throw new IOException(e);
			}

			output.write("#include \"jace/JArray.h\"" + newLine);
			output.write("#include \"jace/JFieldProxy.h\"" + newLine);
			output.write("#include \"jace/JMethod.h\"" + newLine);
			output.write("#include \"jace/JField.h\"" + newLine);
			output.write("#include \"jace/JClassImpl.h\"" + newLine);
			output.write(newLine);

			String className = classFile.getClassName().asIdentifier();
			if (className.equals("java.lang.Throwable") || className.equals("java.lang.String"))
			{
				output.write("#include " + newLine);
				output.write(newLine);
			}
		}
	}

	/**
	 * Includes the headers for the super-class and all implemented interfaces.
	 *
	 * This is necessary, because we can't inherit from a class unless it is fully defined.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	public void includeDependentHeaders(Writer output) throws IOException
	{
		if (classFile.getSuperClassName() != null)
		{
			Util.generateComment(output, "The super class for this class.");
			MetaClass superClassMetaClass = MetaClassFactory.getMetaClass(classFile.getSuperClassName()).
				proxy();
			output.write(superClassMetaClass.include() + newLine);
		}

		Collection interfaces = classFile.getInterfaces();
		if (interfaces.size() > 0)
		{
			Util.generateComment(output, "The interfaces implemented by this class.");
			for (TypeName i: interfaces)
			{
				MetaClass interfaceClass = MetaClassFactory.getMetaClass(i).proxy();
				output.write(interfaceClass.include() + newLine);
			}
		}
	}

	/**
	 * Make forward declarations for all of the types
	 * for which the class is dependent.
	 *
	 * @param output the output writer
	 * @throws IOException if an error occurs while writing
	 */
	public void makeForwardDeclarations(Writer output) throws IOException
	{
		Collection dependentClasses = getForwardDeclarations();
		if (!dependentClasses.isEmpty())
		{
			Util.generateComment(output, "Forward declarations for the classes that this class uses.");
			for (MetaClass mc: dependentClasses)
			{
				// Don't include classes that aren't in our dependency list
				if (!dependencyFilter.accept(mc))
					continue;

				output.write(mc.forwardDeclare() + newLine);
				output.write(newLine);
			}
		}
	}

	/**
	 * Returns the classes that must be forward-declared for the class we are generating.
	 * 
	 * The return value includes fields, array elements, exceptions and all other parameter types
	 * but not the superclass and the interfaces implemented by this class.
	 * 
	 * @return an empty set if no classes need to be forward-declared
	 */
	public Set getForwardDeclarations()
	{
		if (log.isTraceEnabled())
			log.trace("getForwardDeclarations() for class: " + classFile.getClassName());
		// this method finds all dependencies by scanning through the field and methods belonging to the class
		Set excludedClasses = Sets.newHashSet();
		if (classFile.getSuperClassName() != null)
			excludedClasses.add(MetaClassFactory.getMetaClass(classFile.getSuperClassName()).proxy());

		for (TypeName i: classFile.getInterfaces())
			excludedClasses.add(MetaClassFactory.getMetaClass(i).proxy());

		// Add our current class to the list
		excludedClasses.add(MetaClassFactory.getMetaClass(classFile.getClassName()).proxy());

		Set result = Sets.newHashSet();
		// first, get the fields for the class. We only include fields if we are listing fullyDependent classes.
		for (ClassField field: classFile.getFields())
		{
			if (shouldBeSkipped(field))
				continue;
			MetaClass metaClass = MetaClassFactory.getMetaClass(field.getDescriptor()).proxy();
			if (log.isDebugEnabled())
				log.debug("field: " + field.getName() + ", type: " + metaClass);
			if (!excludedClasses.contains(metaClass))
				result.add(metaClass);
		}

		// now we check for method parameters and exceptions
		for (ClassMethod method: classFile.getMethods())
		{
			if (shouldBeSkipped(method))
				continue;
			if (log.isDebugEnabled())
				log.debug("method: " + method.getName());

			MetaClass returnType = MetaClassFactory.getMetaClass(method.getReturnType()).proxy();
			if (log.isDebugEnabled())
				log.debug("returnType: " + returnType.getFullyQualifiedName("."));
			addDependentClass(result, returnType, excludedClasses);

			for (TypeName parameter: method.getParameterTypes())
			{
				MetaClass parameterType = MetaClassFactory.getMetaClass(parameter).proxy();
				addDependentClass(result, parameterType, excludedClasses);
				if (log.isDebugEnabled())
					log.debug("parameter: " + parameterType.getFullyQualifiedName("."));
			}

			// We must #include exception classes in order to initialize their JEnlister references.
			// The point of this registration is so that Jace can instantiate a matching C++ exception
			// for a Java exception when it is thrown. If you don't #include the header file, then
			// Jace won't be able to find a matching C++ proxy.
			//
			// In general, you DO NOT want exception specifications in C++: If an exception gets thrown
			// that doesn't match the exception specification, it causes an instantaneous abort of the
			// program.
			for (TypeName exception: method.getExceptions())
			{
				MetaClass exceptionType = MetaClassFactory.getMetaClass(exception).proxy();
				addDependentClass(result, exceptionType, excludedClasses);
			}
		}
		return result;
	}

	/**
	 * Adds class dependencies to a set.
	 *
	 * @param result the set to add to
	 * @param classType the class
	 * @param excludedClasses the dependencies to exclude from the set
	 */
	private void addDependentClass(Collection result, MetaClass classType,
																 Set excludedClasses)
	{
		if (classType instanceof ArrayMetaClass)
		{
			ArrayMetaClass arrayType = (ArrayMetaClass) classType;
			MetaClass elementType = arrayType.getInnermostElementType();

			if (!excludedClasses.contains(elementType))
				result.add(elementType);
		}
		else if (!excludedClasses.contains(classType))
			result.add(classType);
	}

	/**
	 * Generates the C++ proxy (header and source) for the specified class.
	 *
	 * @param outputHeaders the directory to write the header file to
	 * @param outputSources the directory to write the source file to
	 * @throws IOException if an error occurs while writing the proxy files
	 */
	public void writeProxy(File outputHeaders, File outputSources)
		throws IOException
	{
		ClassMetaClass metaClass = (ClassMetaClass) MetaClassFactory.getMetaClass(
			classFile.getClassName()).proxy();
		String subDir = metaClass.getPackage().toName("/", false).replace('/', File.separatorChar);
		File fullHeaderDir = outputHeaders;
		File fullSourceDir = outputSources;

		// make the source and header directories but only if the class resides in a package. Fix for Bug 598457
		if (subDir.contains(File.separator))
		{
			fullHeaderDir = new File(fullHeaderDir, subDir);
			fullSourceDir = new File(fullSourceDir, subDir);
			if (!fullHeaderDir.exists() && !fullHeaderDir.mkdirs())
				throw new IOException("Cannot create " + fullHeaderDir.getAbsolutePath());
			if (!fullSourceDir.exists() && !fullSourceDir.mkdirs())
				throw new IOException("Cannot create " + fullSourceDir.getAbsolutePath());
		}

		// then generate the header and include files
		String classFileName = metaClass.getFileName();
		assert (!classFileName.contains("/") && !classFileName.contains("\\")): classFileName;

		if (log.isInfoEnabled())
			log.info("Generating the Proxy for " + metaClass.getFullyQualifiedName("."));
		File fullHeaderFile = new File(fullHeaderDir, classFileName + ".h");
		File actualDirectory = fullHeaderFile.getParentFile();
		if (!actualDirectory.exists() && !actualDirectory.mkdirs())
			throw new IOException("Failed to create " + actualDirectory);
		BufferedWriter out = new BufferedWriter(new FileWriter(fullHeaderFile));
		try
		{
			generateHeader(out);
		}
		finally
		{
			out.close();
		}

		File fullSourceFile = new File(fullSourceDir, classFileName + ".cpp");
		actualDirectory = fullSourceFile.getParentFile();
		if (!actualDirectory.exists() && !actualDirectory.mkdirs())
			throw new IOException("Failed to create " + actualDirectory);
		out = new BufferedWriter(new FileWriter(fullSourceFile));
		try
		{
			generateSource(out);
		}
		finally
		{
			out.close();
		}
	}

	/**
	 * Returns the logger associated with the object.
	 *
	 * @return the logger associated with the object
	 */
	private Logger getLogger()
	{
		return log;
	}

	/**
	 * Returns a String describing the usage of this tool.
	 *
	 * @return String describing the usage of this tool
	 */
	private static String getUsage()
	{
		return "Usage: ProxyGenerator  
[ options ]" + newLine + "Where:" + newLine + " \"class file\" is the path of the Java class to process" + newLine + " \"header\" indicates that a proxy header file should be generated" + newLine + " \"source\" indicates that a proxy source file should be generated" + newLine + " \"options\" can be:" + newLine + " -public: Generate public fields and members (default)" + newLine + " -protected: Generate public, protected fields and members" + newLine + " -package: Generate public, protected, package-private fields and methods." + newLine + " -private: Generate public, protected, package-private, private fields and methods." + newLine; } /** * Builds a ProxyGenerator. * * @author Gili Tzabari */ @SuppressWarnings("PublicInnerClass") public static final class Builder { private final ClassFile classFile; private final MetaClassFilter dependencyFilter; private final ClassPath classPath; private AccessibilityType accessibility = AccessibilityType.PUBLIC; private boolean exportSymbols; /** * Creates a new Builder. * * @param classPath the path to search for class files when resolving class dependencies * @param classFile the class to generate a proxy for * @param dependencyFilter indicates whether methods should be exported. Methods whose parameters or return types * are rejected by the filter are omitted. * @throws IllegalArgumentException if classFile, dependencyFilter or * classPath are null */ public Builder(ClassPath classPath, ClassFile classFile, MetaClassFilter dependencyFilter) throws IllegalArgumentException { if (classFile == null) throw new IllegalArgumentException("classFile may not be null"); if (dependencyFilter == null) throw new IllegalArgumentException("dependencyFilter may not be null"); if (classPath == null) throw new IllegalArgumentException("classPath may not be null"); this.classFile = classFile; this.dependencyFilter = dependencyFilter; this.classPath = classPath; } /** * Indicates if proxy symbols should be exported (i.e. for use in DLLs) * * @param value true if proxy symbols should be exported. The default is false. * @return the Builder */ public Builder exportSymbols(boolean value) { this.exportSymbols = value; return this; } /** * Indicates the member accessibility to expose. * * @param accessibility the member accessibility to expose, The default is AccessibilityType.PUBLIC. * @return the Builder * @throws IllegalArgumentException if accessibility is null */ public Builder accessibility(AccessibilityType accessibility) throws IllegalArgumentException { if (accessibility == null) throw new IllegalArgumentException("accessibility may not be null"); this.accessibility = accessibility; return this; } /** * Builds a ProxyGenerator. * * @return the ProxyGenerator */ public ProxyGenerator build() { return new ProxyGenerator(this); } } /** * Accepts all classes. * * @author Gili Tzabari */ @SuppressWarnings("PublicInnerClass") public static class AcceptAll implements MetaClassFilter { @Override public boolean accept(MetaClass candidate) { return true; } } /** * Accepts a collection of classes. * * @author Gili Tzabari */ @SuppressWarnings("PublicInnerClass") public static class FilteringCollection implements MetaClassFilter { private final Collection collection = Lists.newArrayList(); /** * Adds a metaclass to be accepted. * * @param metaClass the metaclass to accept */ public void add(MetaClass metaClass) { collection.add(metaClass); } @Override public boolean accept(MetaClass candidate) { return collection.contains(candidate); } } /** * Generates a C++ proxy for a java class. * * @param args the command-line argument */ @SuppressWarnings("UseOfSystemOutOrSystemErr") public static void main(String[] args) { if (args.length < 2) { System.out.println(getUsage()); return; } AccessibilityType accessibility = AccessibilityType.PUBLIC; for (int i = 2; i < args.length; ++i) { String option = args[i]; if (option.equals("-public")) accessibility = AccessibilityType.PUBLIC; if (option.equals("-protected")) accessibility = AccessibilityType.PROTECTED; else if (option.equals("-package")) accessibility = AccessibilityType.PACKAGE; else if (option.equals("-private")) accessibility = AccessibilityType.PRIVATE; else { System.out.println("Not an understood option: " + option); System.out.println(); System.out.println(getUsage()); return; } } ProxyGenerator generator = new ProxyGenerator.Builder(new ClassPath(System.getProperty( "java.class.path")), new ClassFile(new File(args[0])), new AcceptAll()).accessibility(accessibility).build(); try { @SuppressWarnings("UseOfSystemOutOrSystemErr") OutputStreamWriter writer = new OutputStreamWriter(System.out); if (args[1].equals("header")) generator.generateHeader(writer); else if (args[1].equals("source")) generator.generateSource(writer); writer.flush(); } catch (IOException e) { generator.getLogger().error("", e); } } }