/* * Copyright 2008-2017 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.griffon.compile.core.ast.transform; import griffon.core.artifact.GriffonController; import griffon.transform.Threading; import griffon.util.GriffonClassUtils; import griffon.util.MethodDescriptor; import org.codehaus.griffon.compile.core.AnnotationHandler; import org.codehaus.griffon.compile.core.AnnotationHandlerFor; import org.codehaus.griffon.compile.core.ThreadingAwareConstants; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.transform.GroovyASTTransformation; import java.util.Iterator; import java.util.List; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.THIS; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.args; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.stmnt; import static org.codehaus.griffon.compile.core.ast.transform.ThreadingAwareASTTransformation.addThreadingHandlerIfNeeded; /** * Handles generation of code for the {@code @Threading} annotation. * <p/> * * @author Andres Almiray * @since 2.0.0 */ @AnnotationHandlerFor(Threading.class) @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class ThreadingASTTransformation extends AbstractASTTransformation implements ThreadingAwareConstants, AnnotationHandler { private static final ClassNode THREADING_CNODE = makeClassSafe(Threading.class); private static final ClassNode GRIFFON_CONTROLLER_CNODE = makeClassSafe(GriffonController.class); /** * Convenience method to see if an annotated node is {@code @Threading}. * * @param node the node to check * @return true if the node is an event publisher */ public static boolean hasThreadingAnnotation(AnnotatedNode node) { for (AnnotationNode annotation : node.getAnnotations()) { if (THREADING_CNODE.equals(annotation.getClassNode())) { return true; } } return false; } /** * Handles the bulk of the processing, mostly delegating to other methods. * * @param nodes the ast nodes * @param source the source unit for the nodes */ public void visit(ASTNode[] nodes, SourceUnit source) { AnnotationNode annotation = (AnnotationNode) nodes[0]; AnnotatedNode node = (AnnotatedNode) nodes[1]; Threading.Policy threadingPolicy = getThreadingPolicy(annotation); if (threadingPolicy == Threading.Policy.SKIP) return; String threadingMethod = resolveThreadingMethod(threadingPolicy); if (node instanceof MethodNode) { ClassNode declaringClass = node.getDeclaringClass(); if (declaringClass.implementsInterface(GRIFFON_CONTROLLER_CNODE)) { return; } addThreadingHandlerIfNeeded(source, declaringClass); handleMethodForInjection(declaringClass, (MethodNode) node, threadingMethod); } else if (node instanceof ClassNode) { ClassNode declaringClass = (ClassNode) node; if (declaringClass.implementsInterface(GRIFFON_CONTROLLER_CNODE)) { return; } addThreadingHandlerIfNeeded(source, declaringClass); for (MethodNode methodNode : declaringClass.getAllDeclaredMethods()) { threadingPolicy = getThreadingPolicy(methodNode, threadingPolicy); threadingMethod = resolveThreadingMethod(threadingPolicy); handleMethodForInjection(declaringClass, methodNode, threadingMethod); } } } private String resolveThreadingMethod(Threading.Policy threadingPolicy) { String threadingMethod = METHOD_RUN_OUTSIDE_UI; switch (threadingPolicy) { case INSIDE_UITHREAD_SYNC: threadingMethod = METHOD_RUN_INSIDE_UI_SYNC; break; case INSIDE_UITHREAD_ASYNC: threadingMethod = METHOD_RUN_INSIDE_UI_ASYNC; break; case OUTSIDE_UITHREAD: default: break; } return threadingMethod; } public static Threading.Policy getThreadingPolicy(AnnotationNode annotation) { PropertyExpression value = (PropertyExpression) annotation.getMember("value"); if (value == null) return Threading.Policy.OUTSIDE_UITHREAD; return Threading.Policy.valueOf(value.getPropertyAsString()); } public static Threading.Policy getThreadingPolicy(MethodNode method, Threading.Policy defaultPolicy) { List<AnnotationNode> annotations = method.getAnnotations(THREADING_CNODE); if(annotations.size() > 0) { return getThreadingPolicy(annotations.get(0)); } return defaultPolicy; } public static void handleMethodForInjection(ClassNode classNode, MethodNode method, String threadingMethod) { MethodDescriptor md = methodDescriptorFor(method); if (GriffonClassUtils.isPlainMethod(md) && !GriffonClassUtils.isEventHandler(md) && hasVoidOrDefAsReturnType(method)) { wrapStatements(classNode, method, threadingMethod); } } private static boolean hasVoidOrDefAsReturnType(MethodNode method) { Class<?> returnType = method.getReturnType().getTypeClass(); return returnType.equals(ClassHelper.DYNAMIC_TYPE.getTypeClass()) || returnType.equals(ClassHelper.VOID_TYPE.getTypeClass()); } private static MethodDescriptor methodDescriptorFor(MethodNode method) { if (method == null) return null; Parameter[] types = method.getParameters(); String[] parameterTypes = new String[types.length]; for (int i = 0; i < types.length; i++) { parameterTypes[i] = newClass(types[i].getType()).getName(); } return new MethodDescriptor(method.getName(), parameterTypes, method.getModifiers()); } private static void wrapStatements(ClassNode declaringClass, MethodNode method, String threadingMethod) { Statement code = method.getCode(); Statement wrappedCode = wrapStatements(code, threadingMethod); if (code != wrappedCode) { method.setCode(wrappedCode); for (Parameter param : method.getParameters()) { param.setClosureSharedVariable(true); } } } private static Statement wrapStatements(Statement code, String threadingMethod) { // TODO deal with non-block statements if (!(code instanceof BlockStatement)) return code; BlockStatement codeBlock = (BlockStatement) code; List<Statement> statements = codeBlock.getStatements(); if (statements.isEmpty()) return code; VariableScope variableScope = codeBlock.getVariableScope(); BlockStatement block = new BlockStatement(); VariableScope blockScope = variableScope.copy(); makeVariablesShared(blockScope); block.setVariableScope(blockScope); ClosureExpression closure = new ClosureExpression(Parameter.EMPTY_ARRAY, code); VariableScope closureScope = variableScope.copy(); makeVariablesShared(closureScope); closure.setVariableScope(closureScope); block.addStatement(stmnt(new MethodCallExpression(THIS, threadingMethod, args(closure)))); return block; } private static void makeVariablesShared(VariableScope scope) { for (Iterator<Variable> vars = scope.getReferencedLocalVariablesIterator(); vars.hasNext(); ) { Variable var = vars.next(); var.setClosureSharedVariable(true); } } }