package Tools;

import java.lang.reflect.*;
import java.io.*;

/**
 * Reads a class' definition using reflection, and spits out a subclass
 * (or implementation) that adds debugging comments before each method
 * call. Useful for all sorts of mechanically-generated tweaks to existing
 * classes.
 */
public class MakeDebugClass {
	String inClassname;
	Class inClass;
	String outClassname;
	Writer outWriter;
	Options opts;

	public static void main(String args[]) {
		(new MakeDebugClass()).realMain(args);
	}

	public void realMain(String args[]) {
		try {
			opts = new Options(args) {
				public void defineOptions() {
					programName = "MakeDebugClass";
					defineArgument(
			"inClass", true, "The class to annotate", null);
					defineArgument(
			"outClass", false, "The name of the class to write out", null);
					defineOption(
			"abstract", "Implement and instrument abstract methods", "true");
					defineOption(
			"println", "Print a message for every method invocation", "true");
				}
			};
			inClassname = opts.get("inClass");
			inClass = Class.forName(inClassname);
	
			outClassname = opts.get("outClass", "Debug"+inClass);
			File file = new File(outClassname+".java");
			if (file.exists()) {
				throw new RuntimeException("hey, "+file+" already exists.");
			}
			FileOutputStream outFile = new FileOutputStream(file);
			outWriter = new OutputStreamWriter(outFile);

			doClass();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	void doClass()
		throws Exception {
		outWriter.write("\n\n// Mechanically-generated file\n");
		outWriter.write("public class "+outClassname+"\n");
		if (inClass.isInterface()) {
			outWriter.write("	implements "+inClassname+" {\n");
		} else {
			outWriter.write("	extends "+inClassname+" {\n");
		}
		outWriter.write("\n");
		doConstructors();
		doMethods();

		outWriter.write("}\n");
		outWriter.close();
	}

	void doConstructors() throws Exception {
		Constructor ctors[] = inClass.getConstructors();
		for (int ci=0; ci<ctors.length; ci++) {
			Constructor ctor = ctors[ci];
			doMethodyThing(new ConstructorWrapper(ctor));
		}
	}

	void doMethods() throws Exception {
		Method methods[] = inClass.getMethods();
		for (int mi=0; mi<methods.length; mi++) {
			Method method = methods[mi];
			if (!method.getDeclaringClass().equals(inClass)) {
				// outWriter.write("	// ignoring "+method.getName()
				// 	+" from "+method.getDeclaringClass().getName()+"\n");
				continue;
			}
			doMethodyThing(new MethodWrapper(method));
		}
	}

	/**
	 * Make Constructors and Methods look sorta the same, typewise.
	 */
	static abstract class Wrapper {
		Member member;
		Wrapper(Member member) {
			this.member = member;
		}
		int getModifiers() {
			return member.getModifiers();
		}

		abstract String getName();
		abstract Class[] getParameterTypes();
		abstract Class[] getExceptionTypes();
		abstract String getReturnTypeString();
		abstract String getSuperCall();
		abstract boolean isVoid();
		abstract boolean isStatic();
	}

	class MethodWrapper extends Wrapper {
		Method method;
		MethodWrapper(Method method) {
			super(method);
			this.method = method;
		}
		String getName() {
			return member.getName();
		}
		String getReturnTypeString() {
			return javaName(method.getReturnType())+" ";
		}
		Class[] getParameterTypes() {
			return method.getParameterTypes();
		}
		Class[] getExceptionTypes() {
			return method.getExceptionTypes();
		}
		String getSuperCall() {
			String s="";
			if (!isVoid()) {
				s="return ";
			}
			if (isStatic()) {
				s+=inClassname+"."+getName();
			} else {
				s+="super."+getName();
			}
			return s;
		}
		boolean isVoid() {
			return method.getReturnType().equals(Void.TYPE);
		}
		boolean isStatic() {
			return Modifier.isStatic(method.getModifiers());
		}
	}

	class ConstructorWrapper extends Wrapper {
		Constructor ctor;
		ConstructorWrapper(Constructor ctor) {
			super(ctor);
			this.ctor = ctor;
		}
		String getName() {
			return outClassname;
		}
		String getReturnTypeString() {
			return "";
		}
		Class[] getParameterTypes() {
			return ctor.getParameterTypes();
		}
		Class[] getExceptionTypes() {
			return ctor.getExceptionTypes();
		}
		String getSuperCall() {
			return "super";
		}
		boolean isVoid() {
			return true;
		}
		boolean isStatic() {
			return false;
		}
	}

	void doMethodyThing(Wrapper wr)
		throws Exception {

		int modifiers = wr.getModifiers();
		boolean wasAbstract = false;
		if (Modifier.isAbstract(modifiers)) {
			if (opts.getBoolean("abstract")) {
				modifiers &= ~Modifier.ABSTRACT;
				wasAbstract = true;
			} else {
				return;
			}
		}
		outWriter.write("	"
			+Modifier.toString(modifiers)
			+" ");
		outWriter.write(wr.getReturnTypeString());
		outWriter.write(wr.getName());
		outWriter.write("(");
		Class[] params = wr.getParameterTypes();
		String invocation = "(";
		for (int pi=0; pi<params.length; pi++) {
			outWriter.write(javaName(params[pi])+" p"+pi);
			invocation+="p"+pi;
			if (pi<params.length-1) {
				outWriter.write(", ");
				invocation+=", ";
			}
		}
		invocation+=");";
		outWriter.write(")");
		Class[] excs = wr.getExceptionTypes();
		if (excs.length>0) {
			outWriter.write("\n		throws ");
			for (int ei=0; ei<excs.length; ei++) {
				outWriter.write(javaName(excs[ei]));
				if (ei<excs.length-1) {
					outWriter.write(", ");
				}
			}
		}
		outWriter.write(" {\n");
		String debugcmd = "";
		if (opts.getBoolean("println")) {
			debugcmd = "		System.err.println(\""
				+outClassname+"."+wr.getName()+"()\");\n";
		}
		String superinvocation;
		if (wasAbstract) {
			// don't invoke super, it doesn't do anything.
			superinvocation = "";
		} else {
			superinvocation = "		"+wr.getSuperCall()+invocation+"\n";
		}
		if (wr instanceof MethodWrapper) {
			outWriter.write(debugcmd+superinvocation);
		} else {
			// ctors go in other order because stupid super() has to
			// come first
			outWriter.write(superinvocation+debugcmd);
		}
		outWriter.write("	}\n");
		outWriter.write("\n");
	}

	public static String javaName(Class c) {
		if (c.isArray()) {
			return javaName(c.getComponentType())+"[]";
		}
		String s = c.getName();
		if (s.startsWith("class ")) {
			s = s.substring(6);
		}
		return s;
	}
}
