/*
* Java Interface Implementor. :)
*
* Copyright (c) 2000 Dustin Sallings <dustin@spy.net>
*/
package net.spy.util;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import net.spy.SpyObject;
/**
* Extend existing classes with missing methods required to implement a
* specified interface.
*
* Think Java Interfaces are a poor replacement for the lack of multiple
* inheritence? SO DO I! That's why this class exists.
* <p>
* Using InterfaceImplementor, you can write code that's portable among
* different API specs. You simply implement what you need out of an
* interface, and InterfaceImplementor writes a class that extends from the
* class you wrote, and fills in all the blanks for you.
* <p>
* Here's the way you use it:
* <p>
* java net.spy.util.InterfaceImplementor -interface java.sql.ResultSet
* -superclass test.TestSet -outputclass test.TestSetImpl
*/
public class InterfaceImplementor extends SpyObject {
// Functions that are already defined.
private final HashSet<String> definedFunctions;
private final Class<?> interfaceClass;
private Class<?> superClass=null;
private String outpackage=null;
private String outclass="BLAH";
/**
* Get a new InterfaceImplementor to implement the passed in interface.
*
* @param c The interface to implement.
*
* @exception NullPointerException if the passed in class is null
* @exception IllegalArgumentException if the passed in class is not
* an interface
*/
public InterfaceImplementor(Class<?> c) {
super();
// Verify the interface isn't null
if(c==null) {
throw new NullPointerException("Null interface is invalid.");
}
// Verify that it's an interface
if(!c.isInterface()) {
throw new IllegalArgumentException(
"Passed in class is not an interface.");
}
// Go ahead and initialize this here. That way we don't have to
// worry about it later.
definedFunctions=new HashSet<String>();
interfaceClass=c;
}
/**
* Get the interface we're implementing.
*/
protected Class<?> getInterface() {
return(interfaceClass);
}
/**
* Get the parent class of the generated class.
*/
protected Class<?> getSuperclass() {
return(superClass);
}
/**
* Get the name of the class we'll be generating
*/
public String getOutClassName() {
return(outclass);
}
/**
* Get the name of the package containing class we'll be generating
*/
public String getOutPackageName() {
return(outpackage);
}
/**
* Set the name of the output class.
*/
public void setOutputClass(String to) {
int lastdot=to.lastIndexOf(".");
if(lastdot==-1) {
this.outclass=to;
} else {
outpackage=to.substring(0, lastdot);
this.outclass=to.substring(lastdot+1);
}
}
/**
* Set an optional superclass that defines some of the methods for the
* implementation.
*
* @param c Superclass
*
* @exception NullPointerException if the passed in class is null
* @exception IllegalArgumentException if the passed in class isn't
* valid for this operation.
*/
public void setSuperClass(Class<?> c) {
if(c==null) {
throw new NullPointerException("Null class is invalid.");
}
int modifiers=c.getModifiers();
if(Modifier.isFinal(modifiers)) {
throw new IllegalArgumentException(
"You can't extend from final classes.");
}
if(Modifier.isInterface(modifiers)) {
throw new IllegalArgumentException(
"Interfaces aren't valid here.");
}
superClass=c;
// Extract the methods
getMethods(c);
}
// Extract the methods from the above.
private void getMethods(Class<?> c) {
// First, get the declared ones
Method[] methods=c.getDeclaredMethods();
for(int i=0; i<methods.length; i++) {
int modifiers=methods[i].getModifiers();
// Ignore abstract methods
if(!Modifier.isAbstract(modifiers)) {
definedFunctions.add(getSignature(methods[i]));
}
}
// Then, get the rest (which includes some declared ones)
methods=c.getMethods();
for(int i=0; i<methods.length; i++) {
int modifiers=methods[i].getModifiers();
// Ignore abstract methods
if(!Modifier.isAbstract(modifiers)) {
definedFunctions.add(getSignature(methods[i]));
}
}
}
private String decodeType(Class<?> type) {
String rv=null;
if(type.isArray()) {
rv=decodeType(type.getComponentType()) + "[]";
} else {
rv=type.getName();
}
return(rv);
}
/**
* Get the method signature.
*
* @param method method needing the signature
* @return the method signature, as a String
*/
protected String getSignature(Method method) {
return(getSignature(method, true));
}
/**
* Get a String representing this method signature.
*
* @param method the name of the method
* @param needExceptions true if exceptions are needed as part of the
* signature string
*/
protected String getSignature(Method method, boolean needExceptions) {
String ret="";
// Get the modifiers
int modifiers=method.getModifiers();
// Clear the abstract flag
modifiers&=~(Modifier.ABSTRACT);
// Add the modifiers to our string
ret=Modifier.toString(modifiers);
// Get the return type
Class<?> rt=method.getReturnType();
ret+=" " + decodeType(rt) + " ";
// Add the method name
String name=method.getName();
// OK, now deal with parameters
Class<?>[] types=method.getParameterTypes();
ret+=name + "(";
if(types.length > 0) {
for(int i=0; i<types.length; i++) {
ret+=decodeType(types[i]) + " a" + i + ", ";
}
// Strip off the last comma
ret=ret.substring(0, ret.length()-2);
}
// Get rid of the last comma and add a paren
ret+=") ";
if(needExceptions) {
ret+=getExSignature(method);
}
return(ret.trim());
}
/**
* Get the relative javadoc signature for this method.
*/
protected String getDocLink(Method method) {
String ret=method.getName();
Class<?>[] types=method.getParameterTypes();
ret+="(";
if(types.length > 0) {
for(int i=0; i<types.length; i++) {
ret+=decodeType(types[i]);
ret+=",";
}
// Strip off the last ,
ret=ret.substring(0, ret.length()-1);
}
ret+=")";
return(ret);
}
private String getExSignature(Method method) {
String ret="";
// Now flip through the exceptions
Class<?>[] e=method.getExceptionTypes();
if(e.length>0) {
ret+="\n\t\tthrows ";
for(int i=0; i<e.length; i++) {
ret+=e[i].getName() + ",";
}
// Strip off the last comma
ret=ret.substring(0, ret.length()-1);
}
return(ret);
}
// Get the constructor signature
private String getSignature(Constructor<?> con) {
String ret=null;
// Get the modifiers
int modifiers=con.getModifiers();
// Add the modifiers to our string
ret=Modifier.toString(modifiers) + " " + outclass;
Class<?>[] types=con.getParameterTypes();
ret+="(";
if(types.length > 0) {
for(int i=0; i<types.length; i++) {
ret+=decodeType(types[i]) + " a" + i + ", ";
}
// Strip off the last ,space
ret=ret.substring(0, ret.length()-2);
}
ret+=") ";
// Now flip through the exceptions
Class<?>[] e=con.getExceptionTypes();
if(e.length>0) {
ret+="\n\t\tthrows ";
for(int i=0; i<e.length; i++) {
ret+=e[i].getName() + ", ";
}
// Strip off the last ,space
ret=ret.substring(0, ret.length()-2);
}
return(ret);
}
/**
* Get the relative javadoc signature for this Constructor.
*/
protected String getDocLink(Constructor<?> con) {
String ret=con.getName();
Class<?>[] types=con.getParameterTypes();
ret+="(";
if(types.length > 0) {
for(int i=0; i<types.length; i++) {
ret+=decodeType(types[i]);
ret+=",";
}
// Strip off the last ,
ret=ret.substring(0, ret.length()-1);
}
ret+=")";
return(ret);
}
/**
* Implement the given method.
*
* Subclasses may override this to provide a different method
* implementation
*
* @param method the method to be implemented.
* @return the text required to implement this method
*/
protected String implement(Method method) {
// Start
String ret="\t/**\n"
+ "\t * InterfaceImplementor implementation of "
+ method.getName() + ".\n"
+ "\t * @see " + interfaceClass.getName() + "#"
+ getDocLink(method) + "\n"
+ "\t */\n";
ret+="\t" + getSignature(method) + " {\n";
Class<?>[] e=method.getExceptionTypes();
// If we can throw an exception, do so.
if(e.length>0) {
ret+="\t\tthrow new "
+ e[0].getName() + "(\""
+ getSignature(method, false)
+ " not implemented yet.\");\n";
} else {
// OK, let's check the return value...
Class<?> rt=method.getReturnType();
if(rt.isPrimitive()) {
if(rt == Boolean.TYPE) {
ret+="\t\treturn false;\n";
} else if(rt == Void.TYPE) {
// Do nothing (nothing to do!)
} else {
ret+="\t\treturn 0;\n";
}
} else {
ret+="\t\treturn null;\n";
}
}
ret+="\t}\n\n";
return(ret);
}
// Implement a constructor that calls the super constructor
private String implementConstructor(Constructor<?> con) {
String ret="\t/**\n"
+ "\t * Constructor provided by InterfaceImplementor.\n";
if(superClass != null) {
ret+="\t * @see " + superClass.getName() + "#"
+ getDocLink(con) + "\n";
}
ret+="\t */\n";
ret+="\t" + getSignature(con) + " {\n";
ret+="\t\tsuper(";
Class<?>[] params=con.getParameterTypes();
if(params.length>0) {
for(int i=0; i<params.length; i++) {
ret+="a" + i + ", ";
}
// Get rid of the trailing ,space
ret=ret.substring(0, ret.length()-2);
}
ret+=");\n\t}\n\n";
return(ret);
}
/**
* Anything that should appear before the automatically generated
* constructors.
*/
protected String preConstructors() {
return(null);
}
/**
* Anything that should appear before the automatically generated
* methods.
*/
protected String preMethods() {
return(null);
}
/**
* Generate the source code for the class this object represents.
*/
public String makeSource() {
String ret="";
// If there's a package, declare it
if(outpackage!=null) {
ret+="package " + outpackage + ";\n\n";
}
ret+="/**\n"
+ " * InterfaceImplementor implementation of "
+ interfaceClass.getName() + ".\n"
+ " */\n";
ret+="public class " + outclass + " ";
// If there's a superclass, extend it
if(superClass!=null) {
ret+="extends " + superClass.getName() + " ";
}
ret+="implements " + interfaceClass.getName() + " {\n\n";
// Do any pre-constructor stuff
String pc=preConstructors();
if(pc!=null) {
ret+=pc;
}
// If there's a superclass, grab all of the constructors from that
// superclass and make sure they all get called.
if(superClass!=null && buildConstructors()) {
Constructor<?>[] constructors=superClass.getConstructors();
for(int i=0; i<constructors.length; i++) {
ret+=implementConstructor(constructors[i]);
}
}
// Stuff that needs to be added after all methods.
String pm=preMethods();
if(pm!=null) {
ret+=pm;
}
// Now, implement the methods of the interface
Method[] methods=interfaceClass.getMethods();
for(int i=0; i<methods.length; i++) {
String sig=getSignature(methods[i]);
if(!definedFunctions.contains(sig)) {
ret+=implement(methods[i]);
}
}
ret+=("}\n");
return(ret);
}
/**
* If true, build the default constructors.
*/
protected boolean buildConstructors() {
return(true);
}
/**
* Write this implementation out to a given file.
*
* @param outdir the base directory to write the file
* @throws IOException if there's a problem writing the file
*/
public void writeSourceToFile(String outdir) throws IOException {
String fn=outdir + File.separatorChar;
String op=getOutPackageName();
String oc=getOutClassName();
// Figure out if there's a package name, if so, make sure the
// dirs exist and all that.
if(op!=null) {
File packagepath= new File(
fn + op.replace('.', File.separatorChar));
packagepath.mkdirs();
fn=packagepath.toString() + File.separatorChar;
}
// Stick the classname.java to the end
fn+=oc + ".java";
// Write it out...
System.out.println("Writing output to " + fn);
FileWriter fw=new FileWriter(fn);
try {
fw.write(makeSource());
} finally {
CloseUtil.close(fw);
}
}
// A method com
private static void usage() {
System.err.println("Usage: InterfaceImplementor"
+ " -interface className [-superclass className]\n"
+ "\t[-outputdir outputDir] [-outputclass className]");
}
public static void main(String args[]) throws Exception {
String superclassName=null;
String interfaceName=null;
String outclass=null;
String outdir=".";
// parse the arguments
for(int i=0; i<args.length; i++) {
if(args[i].equals("-superclass")) {
superclassName=args[++i];
} else if(args[i].equals("-interface")) {
interfaceName=args[++i];
} else if(args[i].equals("-outputclass")) {
outclass=args[++i];
} else if(args[i].equals("-outputdir")) {
outdir=args[++i];
} else {
System.err.println("Unknown argument: " + args[i]);
usage();
throw new Exception("Unknown argument: " + args[i]);
}
}
// Make sure an interface was given.
if(interfaceName == null) {
System.err.println("No superinterface given.");
usage();
throw new Exception("No superinterface given.");
}
// Get an interface implementor
InterfaceImplementor i=
new InterfaceImplementor(Class.forName(interfaceName));
// Set the superclass
if(superclassName!=null) {
System.out.println("Loading super class: " + superclassName);
i.setSuperClass(Class.forName(superclassName));
}
// If the user specified an output class, create the .java file to
// make it, else send it to stdout with the class name BLAH
if(outclass!=null) {
// Set the output class name
i.setOutputClass(outclass);
i.writeSourceToFile(outdir);
} else {
System.out.print(i.makeSource());
}
}
}