/* * Copyright 2009-2010 MBTE Sweden AB. * * 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.mbte.groovypp.compiler; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.classgen.BytecodeHelper; import org.codehaus.groovy.classgen.BytecodeInstruction; import org.codehaus.groovy.classgen.BytecodeSequence; import org.mbte.groovypp.compiler.bytecode.BytecodeExpr; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.*; public class ClosureUtil { private static final LinkedList<MethodNode> NONE = new LinkedList<MethodNode> (); private static boolean likeGetter(MethodNode method) { return method.getName().startsWith("get") && ClassHelper.VOID_TYPE != method.getReturnType() && method.getParameters().length == 0; } private static boolean likeSetter(MethodNode method) { return method.getName().startsWith("set") && ClassHelper.VOID_TYPE == method.getReturnType() && method.getParameters().length == 1; } public synchronized static List<MethodNode> isOneMethodAbstract (ClassNode node) { if ((node.getModifiers() & Opcodes.ACC_ABSTRACT) == 0 && !node.isInterface()) return null; final ClassNodeCache.ClassNodeInfo info = ClassNodeCache.getClassNodeInfo(node); if (info.isOneMethodAbstract == null) { List<MethodNode> am = node.getAbstractMethods(); if (am == null) { am = Collections.emptyList(); } MethodNode one = null; for (Iterator<MethodNode> it = am.iterator(); it.hasNext();) { MethodNode mn = it.next(); if (!likeGetter(mn) && !likeSetter(mn) && !traitMethod(mn) && !objectMethod(mn)) { if (one != null) { info.isOneMethodAbstract = NONE; return null; } one = mn; it.remove(); } } if (one != null) am.add(0, one); else if (am.size() != 1) { info.isOneMethodAbstract = NONE; return null; } info.isOneMethodAbstract = am; } if (info.isOneMethodAbstract == NONE) return null; return info.isOneMethodAbstract; } private static boolean objectMethod(MethodNode mn) { return mn.getName().equals("equals") && mn.getReturnType().equals(ClassHelper.boolean_TYPE) && mn.getParameters() != null && mn.getParameters().length == 1 ||mn.getName().equals("clone") && mn.getParameters() != null && mn.getParameters().length == 0; } private static boolean traitMethod(MethodNode mn) { return !mn.getAnnotations(TypeUtil.HAS_DEFAULT_IMPLEMENTATION).isEmpty(); } public static MethodNode isMatch(List<MethodNode> one, ClosureClassNode closureType, ClassNode baseType, CompilerTransformer compiler) { class Mutation { final Parameter p; final ClassNode t; public Mutation(ClassNode t, Parameter p) { this.t = t; this.p = p; } void mutate () { p.setType(t); } } if(one == null) return null; List<Mutation> mutations = null; MethodNode missing = one.get(0); Parameter[] missingMethodParameters = missing.getParameters(); List<MethodNode> methods = closureType.getDeclaredMethods("doCall"); for (MethodNode method : methods) { Parameter[] closureParameters = method.getParameters(); if (closureParameters.length != missingMethodParameters.length) continue; boolean match = true; for (int i = 0; i < closureParameters.length; i++) { Parameter closureParameter = closureParameters[i]; Parameter missingMethodParameter = missingMethodParameters[i]; ClassNode parameterType = missingMethodParameter.getType(); parameterType = TypeUtil.getSubstitutedType(parameterType, missing.getDeclaringClass().redirect(), baseType); if (!TypeUtil.isDirectlyAssignableFrom(TypeUtil.wrapSafely(parameterType), TypeUtil.wrapSafely(closureParameter.getType()))) { if (TypeUtil.isDirectlyAssignableFrom(TypeUtil.wrapSafely(closureParameter.getType()), TypeUtil.wrapSafely(parameterType))) { if (mutations == null) mutations = new LinkedList<Mutation> (); mutations.add(new Mutation(parameterType, closureParameter)); continue; } match = false; break; } } if (match) { if (mutations != null) for (Mutation mutation : mutations) { mutation.mutate(); } improveClosureType(closureType, baseType); StaticMethodBytecode.replaceMethodCode(compiler.su, compiler.context, method, compiler.compileStack, compiler.debug == -1 ? -1 : compiler.debug+1, compiler.policy, closureType.getName()); makeOneMethodClass(one, closureType, baseType, compiler, method); return method; } } return null; } public static Parameter[] eraseParameterTypes(Parameter[] parameters) { final Parameter[] ret = new Parameter[parameters.length]; for (int i = 0; i < ret.length; i++) { ret[i] = new Parameter(TypeUtil.eraseTypeArguments(parameters[i].getType()), parameters[i].getName()); } return ret; } private static void makeOneMethodClass(List<MethodNode> abstractMethods, final ClassNode closureType, ClassNode baseType, CompilerTransformer compiler, final MethodNode doCall) { boolean traitMethods = false; int k = 0; for (final MethodNode missed : abstractMethods) { final Parameter[] parameters = eraseParameterTypes(missed.getParameters()); if (k == 0) { closureType.addMethod( missed.getName(), Opcodes.ACC_PUBLIC, getSubstitutedReturnType(doCall, missed, closureType, baseType), parameters, ClassNode.EMPTY_ARRAY, new BytecodeSequence( new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitVarInsn(Opcodes.ALOAD, 0); Parameter pp[] = parameters; for (int i = 0, k = 1; i != pp.length; ++i) { final ClassNode type = pp[i].getType(); ClassNode expectedType = doCall.getParameters()[i].getType(); if (ClassHelper.isPrimitiveType(type)) { if (type == ClassHelper.long_TYPE) { mv.visitVarInsn(Opcodes.LLOAD, k++); k++; } else if (type == ClassHelper.double_TYPE) { mv.visitVarInsn(Opcodes.DLOAD, k++); k++; } else if (type == ClassHelper.float_TYPE) { mv.visitVarInsn(Opcodes.FLOAD, k++); } else { mv.visitVarInsn(Opcodes.ILOAD, k++); } BytecodeExpr.box(type, mv); BytecodeExpr.cast(TypeUtil.wrapSafely(type), TypeUtil.wrapSafely(expectedType), mv); } else { mv.visitVarInsn(Opcodes.ALOAD, k++); BytecodeExpr.checkCast(TypeUtil.wrapSafely(expectedType), mv); } BytecodeExpr.unbox(expectedType, mv); } mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(doCall.getDeclaringClass()), doCall.getName(), BytecodeHelper.getMethodDescriptor(doCall.getReturnType(), doCall.getParameters()) ); if (missed.getReturnType() != ClassHelper.VOID_TYPE) { BytecodeExpr.box(doCall.getReturnType(), mv); BytecodeExpr.checkCast(TypeUtil.wrapSafely(doCall.getReturnType()), mv); BytecodeExpr.unbox(missed.getReturnType(), mv); } BytecodeExpr.doReturn(mv, missed.getReturnType()); } } )); } else { if (traitMethod(missed)) { traitMethods = true; } else if (likeGetter(missed)) { String pname = missed.getName().substring(3); pname = Character.toLowerCase(pname.charAt(0)) + pname.substring(1); final PropertyNode propertyNode = closureType.addProperty(pname, Opcodes.ACC_PUBLIC, missed.getReturnType(), null, null, null); propertyNode.getField().addAnnotation(new AnnotationNode(TypeUtil.NO_EXTERNAL_INITIALIZATION)); } else { if (likeSetter(missed)) { String pname = missed.getName().substring(3); pname = Character.toLowerCase(pname.charAt(0)) + pname.substring(1); final PropertyNode propertyNode = closureType.addProperty(pname, Opcodes.ACC_PUBLIC, parameters[0].getType(), null, null, null); propertyNode.getField().addAnnotation(new AnnotationNode(TypeUtil.NO_EXTERNAL_INITIALIZATION)); } } } k++; } if (traitMethods) TraitASTTransformFinal.improveAbstractMethods(closureType); } private static ClassNode getSubstitutedReturnType(MethodNode doCall, MethodNode missed, ClassNode closureType, ClassNode baseType) { ClassNode returnType = missed.getReturnType(); if (missed.getParameters().length == doCall.getParameters().length) { int nParams = missed.getParameters().length; ClassNode declaringClass = missed.getDeclaringClass(); GenericsType[] typeVars = declaringClass.getGenericsTypes(); if (typeVars != null && typeVars.length > 0) { ClassNode[] formals = new ClassNode[nParams + 1]; ClassNode[] actuals = new ClassNode[nParams + 1]; for (int i = 0; i < nParams; i++) { actuals[i] = doCall.getParameters()[i].getType(); formals[i] = missed.getParameters()[i].getType(); } actuals[actuals.length - 1] = doCall.getReturnType(); formals[formals.length - 1] = missed.getReturnType(); ClassNode[] unified = TypeUnification.inferTypeArguments(typeVars, formals, actuals); ClassNode newBase = TypeUtil.withGenericTypes(baseType, unified); improveClosureType(closureType, newBase); returnType = TypeUtil.getSubstitutedType(returnType, declaringClass, newBase); } } return returnType; } public static void improveClosureType(final ClassNode closureType, ClassNode baseType) { if (baseType.isInterface()) { closureType.setInterfaces(new ClassNode[]{baseType}); closureType.setSuperClass(ClassHelper.OBJECT_TYPE); } else { closureType.setInterfaces(baseType.equals(ClassHelper.CLOSURE_TYPE) ? new ClassNode[] {ClassHelper.GENERATED_CLOSURE_Type} : ClassNode.EMPTY_ARRAY); closureType.setSuperClass(baseType); } } public static void createClosureConstructor(final ClassNode newType, final Parameter[] constrParams, Expression superArgs, CompilerTransformer compiler) { final ClassNode superClass = newType.getSuperClass(); final Parameter[] finalConstrParams; final ArgumentListExpression superCallArgs = new ArgumentListExpression(); if (superArgs != null) { final ArgumentListExpression args = (ArgumentListExpression) superArgs; if (args.getExpressions().size() > 0) { Parameter [] newParams = new Parameter [constrParams.length + args.getExpressions().size()]; System.arraycopy(constrParams, 0, newParams, 0, constrParams.length); for (int i = 0; i != args.getExpressions().size(); ++i) { final Parameter parameter = new Parameter(args.getExpressions().get(i).getType(), "$super$param$" + i); newParams [i+constrParams.length] = parameter; superCallArgs.addExpression(new VariableExpression(parameter)); } finalConstrParams = newParams; } else finalConstrParams = constrParams; } else { if (superClass == ClassHelper.CLOSURE_TYPE) { if (constrParams.length > 0) { superCallArgs.addExpression(new VariableExpression(constrParams[0])); superCallArgs.addExpression(new VariableExpression(constrParams[0])); } else { superCallArgs.addExpression(ConstantExpression.NULL); superCallArgs.addExpression(ConstantExpression.NULL); } } finalConstrParams = constrParams; } ConstructorCallExpression superCall = new ConstructorCallExpression(ClassNode.SUPER, superCallArgs); BytecodeSequence fieldInit = new BytecodeSequence(new BytecodeInstruction() { public void visit(MethodVisitor mv) { for (int i = 0, k = 1; i != constrParams.length; i++) { mv.visitVarInsn(Opcodes.ALOAD, 0); final ClassNode type = constrParams[i].getType(); if (ClassHelper.isPrimitiveType(type)) { if (type == ClassHelper.long_TYPE) { mv.visitVarInsn(Opcodes.LLOAD, k++); k++; } else if (type == ClassHelper.double_TYPE) { mv.visitVarInsn(Opcodes.DLOAD, k++); k++; } else if (type == ClassHelper.float_TYPE) { mv.visitVarInsn(Opcodes.FLOAD, k++); } else { mv.visitVarInsn(Opcodes.ILOAD, k++); } } else { mv.visitVarInsn(Opcodes.ALOAD, k++); } mv.visitFieldInsn(Opcodes.PUTFIELD, BytecodeHelper.getClassInternalName(newType), constrParams[i].getName(), BytecodeHelper.getTypeDescription(type)); } mv.visitInsn(Opcodes.RETURN); } }); BlockStatement code = new BlockStatement(); code.addStatement(new ExpressionStatement(superCall)); ConstructorNode cn = new ConstructorNode( Opcodes.ACC_PUBLIC, finalConstrParams, ClassNode.EMPTY_ARRAY, code); newType.addConstructor(cn); code.addStatement(fieldInit); new OpenVerifier().visitClass(newType); StaticMethodBytecode.replaceMethodCode(compiler.su, compiler.context, cn, compiler.compileStack, compiler.debug == -1 ? -1 : compiler.debug+1, compiler.policy, newType.getName()); if (newType.getOuterClass() != null && newType.getMethods("methodMissing").isEmpty()) { newType.addMethod("methodMissing", Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE, new Parameter[] { new Parameter(ClassHelper.STRING_TYPE, "name"), new Parameter(ClassHelper.OBJECT_TYPE, "args")}, ClassNode.EMPTY_ARRAY, new BytecodeSequence(new BytecodeInstruction(){ public void visit(MethodVisitor mv) { mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, BytecodeHelper.getClassInternalName(newType), "this$0", BytecodeHelper.getTypeDescription(newType.getOuterClass())); mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;"); mv.visitInsn(Opcodes.ARETURN); } })); } } public static void addFields(ClosureExpression ce, ClassNode newType, CompilerTransformer compiler) { for(Iterator<Variable> it = ce.getVariableScope().getReferencedLocalVariablesIterator(); it.hasNext(); ) { Variable astVar = it.next(); final Register var = compiler.compileStack.getRegister(astVar.getName(), false); ClassNode vtype; if (var != null) { vtype = compiler.getLocalVarInferenceTypes().get(astVar); if (vtype == null) vtype = var.getType(); } else { if (astVar instanceof VariableExpression && ((VariableExpression)astVar).getAccessedVariable() instanceof FieldNode) { vtype = ((FieldNode)((VariableExpression)astVar).getAccessedVariable()).getType(); } else vtype = compiler.methodNode.getDeclaringClass().getField(astVar.getName()).getType(); } if (newType.getDeclaredField(astVar.getName()) == null) { newType.addField(astVar.getName(), Opcodes.ACC_FINAL, vtype, null); } } ClassNodeCache.clearCache(newType); } public static Parameter[] createClosureConstructorParams(ClassNode newType, CompilerTransformer compiler) { List<FieldNode> fields = newType.getFields(); final List<Parameter> constrParams = new ArrayList<Parameter>(fields.size()); FieldNode ownerField = newType.getField("this$0"); if (ownerField != null) constrParams.add(new Parameter(ownerField.getType(), "this$0")); for (int i = 0; i != fields.size(); ++i) { final FieldNode fieldNode = fields.get(i); if (!fieldNode.getName().equals("this$0") && fieldNode.getAnnotations(TypeUtil.NO_EXTERNAL_INITIALIZATION).isEmpty()) constrParams.add(new Parameter(fieldNode.getType(), fieldNode.getName())); } return constrParams.toArray(new Parameter[constrParams.size()]); } public static void instantiateClass(ClassNode type, CompilerTransformer compiler, Parameter[] constrParams, Expression superArgs, MethodVisitor mv) { TraitASTTransformFinal.improveAbstractMethods(type); type.getModule().addClass(type); final String classInternalName = BytecodeHelper.getClassInternalName(type); mv.visitTypeInsn(Opcodes.NEW, classInternalName); mv.visitInsn(Opcodes.DUP); final ConstructorNode constructorNode = type.getDeclaredConstructors().get(0); for (int i = 0; i != constrParams.length; i++) { String name = constrParams[i].getName(); if ("this$0".equals(name)) { mv.visitVarInsn(Opcodes.ALOAD,0); } else { final Register var = compiler.compileStack.getRegister(name, false); if (var != null) { FieldNode field = type.getDeclaredField(name); BytecodeExpr.load(field.getType(), var.getIndex(), mv); if (!constrParams[i].getType().equals(var.getType()) && !ClassHelper.isPrimitiveType(field.getType())) { BytecodeExpr.checkCast(constrParams[i].getType(), mv); } } else { FieldNode field = compiler.methodNode.getDeclaringClass().getDeclaredField(name); mv.visitVarInsn(Opcodes.ALOAD, 0); if (field == null) // @Field name = compiler.methodNode.getName() + "$" + name; mv.visitFieldInsn(Opcodes.GETFIELD, BytecodeHelper.getClassInternalName(compiler.methodNode.getDeclaringClass()), name, BytecodeHelper.getTypeDescription(constrParams[i].getType())); } } } if (superArgs != null) { final List<Expression> list = ((ArgumentListExpression) superArgs).getExpressions(); for (int i = 0; i != list.size(); ++i) ((BytecodeExpr)list.get(i)).visit(mv); } mv.visitMethodInsn(Opcodes.INVOKESPECIAL, classInternalName, "<init>", BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, constructorNode.getParameters())); } }