/* | |
* Copyright 2003-2007 the original author or authors. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.codehaus.groovy.tools.javac; | |
import groovy.lang.GroovyObjectSupport; | |
import org.codehaus.groovy.ast.*; | |
import org.codehaus.groovy.ast.expr.ArgumentListExpression; | |
import org.codehaus.groovy.ast.expr.ConstantExpression; | |
import org.codehaus.groovy.ast.expr.ConstructorCallExpression; | |
import org.codehaus.groovy.ast.expr.Expression; | |
import org.codehaus.groovy.ast.stmt.BlockStatement; | |
import org.codehaus.groovy.ast.stmt.ExpressionStatement; | |
import org.codehaus.groovy.ast.stmt.Statement; | |
import org.codehaus.groovy.control.ResolveVisitor; | |
import org.objectweb.asm.Opcodes; | |
import java.io.*; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Modifier; | |
import java.util.*; | |
public class JavaStubGenerator | |
{ | |
private boolean java5 = false; | |
private File outputPath; | |
private List toCompile = new ArrayList(); | |
public JavaStubGenerator(File outputPath, boolean java5) { | |
this.outputPath = outputPath; | |
this.java5 = java5; | |
} | |
public JavaStubGenerator( File outputPath) { | |
this(outputPath, false); | |
} | |
private void mkdirs(File parent, String relativeFile) { | |
int index = relativeFile.lastIndexOf('/'); | |
if (index==-1) return; | |
File dir = new File(parent,relativeFile.substring(0,index)); | |
dir.mkdirs(); | |
} | |
public void generateClass(ClassNode classNode) throws FileNotFoundException { | |
// Only attempt to render our self if our super-class is resolved, else wait for it | |
if (!classNode.getSuperClass().isResolved()) { | |
return; | |
} | |
String fileName = classNode.getName().replace('.', '/'); | |
mkdirs(outputPath,fileName); | |
toCompile.add(fileName); | |
File file = new File(outputPath, fileName + ".java"); | |
FileOutputStream fos = new FileOutputStream(file); | |
PrintWriter out = new PrintWriter(fos); | |
try { | |
String packageName = classNode.getPackageName(); | |
if (packageName != null) { | |
out.println("package " + packageName + ";\n"); | |
} | |
genImports(classNode, out); | |
boolean isInterface = classNode.isInterface(); | |
boolean isEnum = (classNode.getModifiers() & Opcodes.ACC_ENUM) !=0; | |
printModifiers(out, classNode.getModifiers() | |
& ~(isInterface ? Opcodes.ACC_ABSTRACT : 0)); | |
if (isInterface) { | |
out.print ("interface "); | |
} else if (isEnum) { | |
out.print ("enum "); | |
} else { | |
out.print ("class "); | |
} | |
out.println(classNode.getNameWithoutPackage()); | |
ClassNode superClass = classNode.getSuperClass(); | |
if (!isInterface) { | |
if (superClass.equals(ClassHelper.OBJECT_TYPE)) { | |
superClass = ClassHelper.make(GroovyObjectSupport.class); | |
} | |
out.print(" extends "); | |
printTypeName(superClass,out); | |
} | |
ClassNode[] interfaces = classNode.getInterfaces(); | |
if (interfaces != null && interfaces.length > 0) { | |
if (isInterface) { | |
out.println(" extends"); | |
} else { | |
out.println(" implements"); | |
} | |
for (int i = 0; i < interfaces.length - 1; ++i) | |
out.println(" " + interfaces[i].getName() + ","); | |
out.print(" "); | |
printTypeName(interfaces[interfaces.length - 1],out); | |
} | |
out.println(" {"); | |
genMethods(classNode, out); | |
genFields(classNode, out); | |
genProps(classNode, out); | |
out.println("}"); | |
} finally { | |
try { | |
out.close(); | |
} catch (Exception e) { | |
// ignore | |
} | |
try { | |
fos.close(); | |
} catch (IOException e) { | |
// ignore | |
} | |
} | |
} | |
private void genMethods(ClassNode classNode, PrintWriter out) { | |
getContructors(classNode, out); | |
List methods = classNode.getMethods(); | |
if (methods != null) | |
for (Iterator it = methods.iterator(); it.hasNext();) { | |
MethodNode methodNode = (MethodNode) it.next(); | |
genMethod(methodNode, out); | |
} | |
} | |
private void getContructors(ClassNode classNode, PrintWriter out) { | |
List constrs = classNode.getDeclaredConstructors(); | |
if (constrs != null) | |
for (Iterator it = constrs.iterator(); it.hasNext();) { | |
ConstructorNode constrNode = (ConstructorNode) it.next(); | |
genConstructor(constrNode, out); | |
} | |
} | |
private void genFields(ClassNode classNode, PrintWriter out) { | |
List fields = classNode.getFields(); | |
if (fields == null) return; | |
ArrayList enumFields = new ArrayList(fields.size()); | |
for (Iterator it = fields.iterator(); it.hasNext();) { | |
FieldNode fieldNode = (FieldNode) it.next(); | |
boolean isEnumField = (fieldNode.getModifiers() & Opcodes.ACC_ENUM) !=0; | |
boolean isSynthetic = (fieldNode.getModifiers() & Opcodes.ACC_SYNTHETIC) !=0; | |
if (isEnumField) { | |
enumFields.add(fieldNode); | |
} else if (!isSynthetic) { | |
genField(fieldNode, out); | |
} | |
} | |
genEnumFields(enumFields, out); | |
} | |
private void genProps(ClassNode classNode, PrintWriter out) { | |
List props = classNode.getProperties(); | |
if (props != null) | |
for (Iterator it = props.iterator(); it.hasNext();) { | |
PropertyNode propNode = (PropertyNode) it.next(); | |
genProp(propNode, out); | |
} | |
} | |
private void genProp(PropertyNode propNode, PrintWriter out) { | |
String name = propNode.getName().substring(0, 1).toUpperCase() | |
+ propNode.getName().substring(1); | |
String getterName = "get" + name; | |
boolean skipGetter = false; | |
List getterCandidates = propNode.getField().getOwner().getMethods(getterName); | |
if (getterCandidates != null) | |
for (Iterator it = getterCandidates.iterator(); it.hasNext();) { | |
MethodNode method = (MethodNode) it.next(); | |
if (method.getParameters().length == 0) { | |
skipGetter = true; | |
} | |
} | |
if (!skipGetter) { | |
printModifiers(out, propNode.getModifiers()); | |
printType(propNode.getType(), out); | |
out.print(" "); | |
out.print(getterName); | |
out.print("() { "); | |
printReturn(out, propNode.getType()); | |
out.println(" }"); | |
} | |
String setterName = "set" + name; | |
boolean skipSetter = false; | |
List setterCandidates = propNode.getField().getOwner().getMethods( setterName); | |
if (setterCandidates != null) | |
for (Iterator it = setterCandidates.iterator(); it.hasNext();) { | |
MethodNode method = (MethodNode) it.next(); | |
if (method.getParameters().length == 1) { | |
skipSetter = true; | |
} | |
} | |
if (!skipSetter) { | |
printModifiers(out, propNode.getModifiers()); | |
out.print("void "); | |
out.print(setterName); | |
out.print("("); | |
printType(propNode.getType(), out); | |
out.println(" value) {}"); | |
} | |
} | |
private void genEnumFields(List fields, PrintWriter out) { | |
if (fields.size()==0) return; | |
boolean first = true; | |
for (Iterator iterator = fields.iterator(); iterator.hasNext();) { | |
FieldNode fieldNode = (FieldNode) iterator.next(); | |
if (!first) { | |
out.print(", "); | |
} else { | |
first = false; | |
} | |
out.print(fieldNode.getName()); | |
} | |
out.println(); | |
} | |
private void genField(FieldNode fieldNode, PrintWriter out) { | |
printModifiers(out, fieldNode.getModifiers()); | |
printType(fieldNode.getType(), out); | |
out.print(" "); | |
out.print(fieldNode.getName()); | |
out.println(";"); | |
} | |
private ConstructorCallExpression getConstructorCallExpression( | |
ConstructorNode constructorNode) { | |
Statement code = constructorNode.getCode(); | |
if (!(code instanceof BlockStatement)) | |
return null; | |
BlockStatement block = (BlockStatement) code; | |
List stats = block.getStatements(); | |
if (stats == null || stats.size() == 0) | |
return null; | |
Statement stat = (Statement) stats.get(0); | |
if (!(stat instanceof ExpressionStatement)) | |
return null; | |
Expression expr = ((ExpressionStatement) stat).getExpression(); | |
if (!(expr instanceof ConstructorCallExpression)) | |
return null; | |
return (ConstructorCallExpression) expr; | |
} | |
private void genConstructor(ConstructorNode constructorNode, PrintWriter out) { | |
// printModifiers(out, constructorNode.getModifiers()); | |
out.print("public "); // temporary hack | |
out.print(constructorNode.getDeclaringClass().getNameWithoutPackage()); | |
printParams(constructorNode, out); | |
ConstructorCallExpression constrCall = getConstructorCallExpression(constructorNode); | |
if (constrCall == null || !constrCall.isSpecialCall()) { | |
out.println(" {}"); | |
} | |
else { | |
out.println(" {"); | |
genSpecialContructorArgs(out, constructorNode, constrCall); | |
out.println("}"); | |
} | |
} | |
private ConstructorNode selectAccessibleConstructorFromSuper(ConstructorNode node) { | |
ClassNode type = node.getDeclaringClass(); | |
ClassNode superType = type.getSuperClass(); | |
for (Iterator iter = superType.getDeclaredConstructors().iterator(); iter.hasNext();) { | |
ConstructorNode c = (ConstructorNode)iter.next(); | |
// Only look at things we can actually call | |
if (c.isPublic() || c.isProtected()) { | |
return c; | |
} | |
} | |
if (!superType.isResolved()) { | |
throw new Error("Super-class (" + superType.getName() + ")should have been resolved already for type: " + type.getName()); | |
} | |
Constructor[] constructors = superType.getTypeClass().getDeclaredConstructors(); | |
for (int i=0; i<constructors.length; i++) { | |
int mod = constructors[i].getModifiers(); | |
// Only look at things we can actualy call | |
if (Modifier.isPublic(mod) || Modifier.isProtected(mod)) { | |
Class[] types = constructors[i].getParameterTypes(); | |
Parameter[] params = new Parameter[types.length]; | |
for (int j=0; j<types.length; j++) { | |
ClassNode ptype = ClassHelper.make(types[j]); | |
params[j] = new Parameter(ptype, types[j].getName()); | |
} | |
return new ConstructorNode(mod, params, null, null); | |
} | |
} | |
return null; | |
} | |
private void genSpecialContructorArgs(PrintWriter out, ConstructorNode node, ConstructorCallExpression constrCall) { | |
// Select a constructor from our class, or super-class which is legal to call, | |
// then write out an invoke w/nulls using casts to avoid abigous crapo | |
ConstructorNode c = selectAccessibleConstructorFromSuper(node); | |
if (c != null) { | |
out.print("super ("); | |
Parameter[] params = c.getParameters(); | |
for (int i=0; i<params.length; i++) { | |
printDefaultValue(out, params[i].getType()); | |
if (i + 1 < params.length) { | |
out.print(", "); | |
} | |
} | |
out.println(");"); | |
return; | |
} | |
// Otherwise try the older method based on the constructor's call expression | |
Expression arguments = constrCall.getArguments(); | |
if (constrCall.isSuperCall()) { | |
out.print("super("); | |
} | |
else { | |
out.print("this("); | |
} | |
// Else try to render some arguments | |
if (arguments instanceof ArgumentListExpression) { | |
ArgumentListExpression argumentListExpression = (ArgumentListExpression) arguments; | |
List args = argumentListExpression.getExpressions(); | |
for (Iterator it = args.iterator(); it.hasNext();) { | |
Expression arg = (Expression) it.next(); | |
if (arg instanceof ConstantExpression) { | |
ConstantExpression expression = (ConstantExpression) arg; | |
Object o = expression.getValue(); | |
if (o instanceof String) { | |
out.print("(String)null"); | |
} | |
else { | |
out.print(expression.getText()); | |
} | |
} | |
else { | |
printDefaultValue(out, arg.getType()); | |
} | |
if (arg != args.get(args.size() - 1)) { | |
out.print(", "); | |
} | |
} | |
} | |
out.println(");"); | |
} | |
private void genMethod(MethodNode methodNode, PrintWriter out) { | |
if (methodNode.getName().equals("<clinit>")) return; | |
if (!methodNode.getDeclaringClass().isInterface()) | |
printModifiers(out, methodNode.getModifiers()); | |
printType(methodNode.getReturnType(), out); | |
out.print(" "); | |
out.print(methodNode.getName()); | |
printParams(methodNode, out); | |
if ((methodNode.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) { | |
out.println(";"); | |
} else { | |
out.print(" { "); | |
ClassNode retType = methodNode.getReturnType(); | |
printReturn(out, retType); | |
out.println("}"); | |
} | |
} | |
private void printReturn(PrintWriter out, ClassNode retType) { | |
String retName = retType.getName(); | |
if (!retName.equals("void")) { | |
out.print("return "); | |
printDefaultValue(out, retType); | |
out.print(";"); | |
} | |
} | |
private void printDefaultValue(PrintWriter out, ClassNode type) { | |
out.print("("); | |
printType(type,out); | |
out.print(")"); | |
if (ClassHelper.isPrimitiveType(type)) { | |
if (type==ClassHelper.boolean_TYPE){ | |
out.print("false"); | |
} else { | |
out.print("0"); | |
} | |
} else { | |
out.print("null"); | |
} | |
} | |
private void printType(ClassNode type, PrintWriter out) { | |
// | |
// NOTE: Only render generics for type if we are allowed to use Java5 stuff | |
// | |
if (java5 && type.isUsingGenerics()) { | |
GenericsType[] types = type.getGenericsTypes(); | |
printTypeName(type,out); | |
out.print("<"); | |
for (int i = 0; i < types.length; i++) { | |
if (i != 0) { | |
out.print(", "); | |
} | |
out.print(types[i]); | |
} | |
out.print(">"); | |
} | |
else if (type.isArray()) { | |
printType(type.getComponentType(),out); | |
out.print("[]"); | |
} | |
else { | |
printTypeName(type,out); | |
} | |
} | |
private void printTypeName(ClassNode type, PrintWriter out) { | |
if (ClassHelper.isPrimitiveType(type)) { | |
if (type==ClassHelper.boolean_TYPE) { | |
out.print("boolean"); | |
} else if (type==ClassHelper.char_TYPE) { | |
out.print("char"); | |
} else if (type==ClassHelper.int_TYPE) { | |
out.print("int"); | |
} else if (type==ClassHelper.short_TYPE) { | |
out.print("short"); | |
} else if (type==ClassHelper.long_TYPE) { | |
out.print("long"); | |
} else if (type==ClassHelper.float_TYPE) { | |
out.print("float"); | |
} else if (type==ClassHelper.double_TYPE) { | |
out.print("double"); | |
} else if (type==ClassHelper.byte_TYPE) { | |
out.print("byte"); | |
} else { | |
out.print("void"); | |
} | |
} else { | |
out.print(type.getName().replace('$', '.')); | |
} | |
} | |
private void printParams(MethodNode methodNode, PrintWriter out) { | |
out.print("("); | |
Parameter[] parameters = methodNode.getParameters(); | |
if (parameters != null && parameters.length != 0) { | |
for (int i = 0; i != parameters.length; ++i) { | |
printType(parameters[i].getType(), out); | |
out.print(" "); | |
out.print(parameters[i].getName()); | |
if (i + 1 < parameters.length) { | |
out.print(", "); | |
} | |
} | |
} | |
out.print(")"); | |
} | |
private void printModifiers(PrintWriter out, int modifiers) { | |
if ((modifiers & Opcodes.ACC_PUBLIC) != 0) | |
out.print("public "); | |
if ((modifiers & Opcodes.ACC_PROTECTED) != 0) | |
out.print("protected "); | |
if ((modifiers & Opcodes.ACC_PRIVATE) != 0) | |
out.print("private "); | |
if ((modifiers & Opcodes.ACC_STATIC) != 0) | |
out.print("static "); | |
if ((modifiers & Opcodes.ACC_SYNCHRONIZED) != 0) | |
out.print("synchronized "); | |
if ((modifiers & Opcodes.ACC_ABSTRACT) != 0) | |
out.print("abstract "); | |
} | |
private void genImports(ClassNode classNode, PrintWriter out) { | |
Set imports = new HashSet(); | |
// | |
// HACK: Add the default imports... since things like Closure and GroovyObject seem to parse out w/o fully qualified classnames. | |
// | |
imports.addAll(Arrays.asList(ResolveVisitor.DEFAULT_IMPORTS)); | |
ModuleNode moduleNode = classNode.getModule(); | |
for (Iterator it = moduleNode.getImportPackages().iterator(); it.hasNext();) { | |
imports.add(it.next()); | |
} | |
for (Iterator it = moduleNode.getImports().iterator(); it.hasNext();) { | |
ImportNode imp = (ImportNode) it.next(); | |
String name = imp.getType().getName(); | |
int lastDot = name.lastIndexOf('.'); | |
if (lastDot != -1) | |
imports.add(name.substring(0, lastDot + 1)); | |
} | |
for (Iterator it = imports.iterator(); it.hasNext();) { | |
String imp = (String) it.next(); | |
out.print("import "); | |
out.print(imp); | |
out.println("*;"); | |
} | |
out.println(); | |
} | |
public void clean() { | |
for (Iterator it = toCompile.iterator(); it.hasNext();) { | |
String path = (String) it.next(); | |
new File(outputPath, path + ".java").delete(); | |
} | |
} | |
} |