/* * 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 groovy.lang.TypePolicy; import groovy.lang.Typed; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.classgen.BytecodeSequence; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.transform.ASTTransformation; import org.codehaus.groovy.transform.GroovyASTTransformation; import org.objectweb.asm.Opcodes; import java.util.*; @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION) public class CompileASTTransform implements ASTTransformation, Opcodes { private static final ClassNode COMPILE_TYPE = ClassHelper.make(Typed.class); public void visit(ASTNode[] nodes, final SourceUnit source) { if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) { throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class"); } AnnotatedNode parent = (AnnotatedNode) nodes[1]; Map<MethodNode, TypePolicy> toProcess = new LinkedHashMap<MethodNode, TypePolicy>(); final ClassNode classNode; if (parent instanceof MethodNode) { TypePolicy classPolicy = getPolicy(parent.getDeclaringClass(), source, TypePolicy.DYNAMIC); TypePolicy methodPolicy = getPolicy(parent, source, classPolicy); classNode = parent.getDeclaringClass(); if (methodPolicy != TypePolicy.DYNAMIC) { final MethodNode mn = (MethodNode) parent; addMethodToProcessingQueue(source, toProcess, methodPolicy, mn); } } else if (parent instanceof ClassNode) { classNode = (ClassNode) parent; TypePolicy classPolicy = getPolicy(classNode, source, TypePolicy.DYNAMIC); allMethods(source, toProcess, classNode, classPolicy); } else if (parent instanceof PackageNode) { TypePolicy modulePolicy = getPolicy(parent, source, TypePolicy.DYNAMIC); for (ClassNode clazz : source.getAST().getClasses()) { if (clazz instanceof InnerClassNode) continue; allMethods(source, toProcess, clazz, modulePolicy); } } else { int line = parent.getLineNumber(); int col = parent.getColumnNumber(); source.getErrorCollector().addError( new SyntaxErrorMessage(new SyntaxException("@Typed applicable only to classes or methods or package declaration" + '\n', line, col), source), true ); return; } final Expression member = ((AnnotationNode) nodes[0]).getMember("debug"); boolean debug = member != null && member instanceof ConstantExpression && ((ConstantExpression) member).getValue().equals(Boolean.TRUE); SourceUnitContext context = new SourceUnitContext(); for (Map.Entry<MethodNode, TypePolicy> entry : toProcess.entrySet()) { final MethodNode mn = entry.getKey(); final TypePolicy policy = entry.getValue(); final List<AnnotationNode> anns = mn.getAnnotations(COMPILE_TYPE); boolean localDebug = debug; if (!anns.isEmpty()) { final AnnotationNode ann = anns.get(0); final Expression localMember = ann.getMember("debug"); if (localMember != null) localDebug = localMember instanceof ConstantExpression && ((ConstantExpression) localMember).getValue().equals(Boolean.TRUE); } if ((mn.getModifiers() & Opcodes.ACC_BRIDGE) != 0 || mn.isAbstract()) continue; final Statement code = mn.getCode(); if (!(code instanceof BytecodeSequence)) { if (!mn.getName().equals("$doCall")) { String name = mn.getName().equals("<init>") ? "_init_" : mn.getName().equals("<clinit>") ? "_clinit_" : mn.getName(); StaticMethodBytecode.replaceMethodCode(source, context, mn, new CompilerStack(null), localDebug ? 0 : -1, policy, mn.getDeclaringClass().getName() + "$" + name); } } } for (MethodNode node : context.generatedFieldGetters.values()) { StaticMethodBytecode.replaceMethodCode(source, context, node, new CompilerStack(null), -1, TypePolicy.STATIC, "Neverused"); } for (MethodNode node : context.generatedFieldSetters.values()) { StaticMethodBytecode.replaceMethodCode(source, context, node, new CompilerStack(null), -1, TypePolicy.STATIC, "Neverused"); } for (MethodNode node : context.generatedMethodDelegates.values()) { StaticMethodBytecode.replaceMethodCode(source, context, node, new CompilerStack(null), -1, TypePolicy.STATIC, "Neverused"); } } private void addMethodToProcessingQueue(final SourceUnit source, final Map<MethodNode, TypePolicy> toProcess, final TypePolicy methodPolicy, MethodNode mn) { final Statement code = mn.getCode(); if (code == null) return; toProcess.put(mn, methodPolicy); code.visit(new CodeVisitorSupport(){ @Override public void visitConstructorCallExpression(ConstructorCallExpression call) { final ClassNode type = call.getType(); if (type instanceof InnerClassNode && ((InnerClassNode)type).isAnonymous()) { allMethods(source, toProcess, type, methodPolicy); } super.visitConstructorCallExpression(call); } }); } private void allMethods(SourceUnit source, Map<MethodNode, TypePolicy> toProcess, ClassNode classNode, TypePolicy classPolicy) { for (MethodNode mn : classNode.getMethods()) { if (!mn.isAbstract() && (mn.getModifiers() & ACC_SYNTHETIC) == 0) { TypePolicy methodPolicy = getPolicy(mn, source, classPolicy); if (methodPolicy != TypePolicy.DYNAMIC) { addMethodToProcessingQueue(source, toProcess, methodPolicy, mn); } } } for (MethodNode mn : classNode.getDeclaredConstructors()) { TypePolicy methodPolicy = getPolicy(mn, source, classPolicy); if (methodPolicy != TypePolicy.DYNAMIC) { addMethodToProcessingQueue(source, toProcess, methodPolicy, mn); } } Iterator<InnerClassNode> inners = classNode.getInnerClasses(); while (inners.hasNext()) { InnerClassNode node = inners.next(); if (node.isAnonymous()) // method compilation will take care continue; TypePolicy innerClassPolicy = getPolicy(node, source, classPolicy); allMethods(source, toProcess, node, innerClassPolicy); } } private TypePolicy getPolicy(AnnotatedNode ann, SourceUnit source, TypePolicy def) { final List<AnnotationNode> list = ann.getAnnotations(COMPILE_TYPE); if (list.isEmpty()) return def; if(checkDuplicateTypedAnn(list, source)) return null; for (AnnotationNode an : list) { final Expression member = an.getMember("value"); if (member instanceof PropertyExpression) { PropertyExpression pe = (PropertyExpression) member; if (pe.getObjectExpression() instanceof ClassExpression) { ClassExpression ce = (ClassExpression) pe.getObjectExpression(); if (ce.getType().getName().equals("groovy.lang.TypePolicy")) { if ("DYNAMIC".equals(pe.getPropertyAsString())) { return TypePolicy.DYNAMIC; } else { if ("MIXED".equals(pe.getPropertyAsString())) { return TypePolicy.MIXED; } else { if ("STATIC".equals(pe.getPropertyAsString())) { return TypePolicy.STATIC; } } } } } } if (member == null) { continue; } int line = ann.getLineNumber(); int col = ann.getColumnNumber(); source.getErrorCollector().addError( new SyntaxErrorMessage(new SyntaxException("Wrong 'value' for @Typed annotation" + '\n', line, col), source), true ); return null; } return TypePolicy.STATIC; } private boolean checkDuplicateTypedAnn(List<AnnotationNode> list, SourceUnit source) { if(list.size() > 1) { AnnotationNode secondAnn = list.get(1); int line = secondAnn.getLineNumber(); int col = secondAnn.getColumnNumber(); source.getErrorCollector().addError( new SyntaxErrorMessage(new SyntaxException("Duplicate @Typed annotation found" + '\n', line, col), source), true ); return true; } return false; } }