private void changeBaseScriptType( final AnnotatedNode parent, final ClassNode cNode, final ClassNode baseScriptType) { if (!cNode.isScriptBody()) { addError("Annotation " + MY_TYPE_NAME + " can only be used within a Script.", parent); return; } if (!baseScriptType.isScript()) { addError( "Declared type " + baseScriptType + " does not extend groovy.lang.Script class!", parent); return; } cNode.setSuperClass(baseScriptType); // Method in base script that will contain the script body code. MethodNode runScriptMethod = ClassHelper.findSAM(baseScriptType); // If they want to use a name other than than "run", then make the change. if (isCustomScriptBodyMethod(runScriptMethod)) { MethodNode defaultMethod = cNode.getDeclaredMethod("run", Parameter.EMPTY_ARRAY); // GROOVY-6706: Sometimes an NPE is thrown here. // The reason is that our transform is getting called more than once sometimes. if (defaultMethod != null) { cNode.removeMethod(defaultMethod); MethodNode methodNode = new MethodNode( runScriptMethod.getName(), runScriptMethod.getModifiers() & ~ACC_ABSTRACT, runScriptMethod.getReturnType(), runScriptMethod.getParameters(), runScriptMethod.getExceptions(), defaultMethod.getCode()); // The AST node metadata has the flag that indicates that this method is a script body. // It may also be carrying data for other AST transforms. methodNode.copyNodeMetaData(defaultMethod); cNode.addMethod(methodNode); } } // If the new script base class does not have a contextual constructor (g.l.Binding), then we // won't either. // We have to do things this way (and rely on just default constructors) because the logic that // generates // the constructors for our script class have already run. if (cNode.getSuperClass().getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS) == null) { ConstructorNode orphanedConstructor = cNode.getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS); cNode.removeConstructor(orphanedConstructor); } }
@Override public List<MethodNode> handleMissingMethod( final ClassNode receiver, final String name, final ArgumentListExpression argumentList, final ClassNode[] argumentTypes, final MethodCall call) { String[] decomposed = Traits.decomposeSuperCallName(name); if (decomposed != null) { return convertToDynamicCall(call, receiver, decomposed, argumentTypes); } if (call instanceof MethodCallExpression) { MethodCallExpression mce = (MethodCallExpression) call; if (mce.getReceiver() instanceof VariableExpression) { VariableExpression var = (VariableExpression) mce.getReceiver(); // GROOVY-7322 // static method call in trait? ClassNode type = null; if (isStaticTraitReceiver(receiver, var)) { type = receiver.getGenericsTypes()[0].getType(); } else if (isThisTraitReceiver(var)) { type = receiver; } if (type != null && Traits.isTrait(type)) { ClassNode helper = Traits.findHelper(type); Parameter[] params = new Parameter[argumentTypes.length + 1]; params[0] = new Parameter(ClassHelper.CLASS_Type.getPlainNodeReference(), "staticSelf"); for (int i = 1; i < params.length; i++) { params[i] = new Parameter(argumentTypes[i - 1], "p" + i); } MethodNode method = helper.getDeclaredMethod(name, params); if (method != null) { return Collections.singletonList(makeDynamic(call, method.getReturnType())); } } } ClassNode dynamic = mce.getNodeMetaData(TraitASTTransformation.DO_DYNAMIC); if (dynamic != null) { return Collections.singletonList(makeDynamic(call, dynamic)); } } return NOTFOUND; }
/** * Adds a static method to the given class node that delegates to the given method and resolves * the object to invoke the method on from the given expression. * * @param expression The expression * @param classNode The class node * @param delegateMethod The delegate method * @return The added method node or null if it couldn't be added */ public static MethodNode addDelegateStaticMethod( Expression expression, ClassNode classNode, MethodNode delegateMethod) { Parameter[] parameterTypes = delegateMethod.getParameters(); String declaredMethodName = delegateMethod.getName(); if (classNode.hasDeclaredMethod(declaredMethodName, parameterTypes)) { return null; } BlockStatement methodBody = new BlockStatement(); ArgumentListExpression arguments = new ArgumentListExpression(); for (Parameter parameterType : parameterTypes) { arguments.addExpression(new VariableExpression(parameterType.getName())); } MethodCallExpression methodCallExpression = new MethodCallExpression(expression, declaredMethodName, arguments); methodCallExpression.setMethodTarget(delegateMethod); ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, delegateMethod); VariableExpression apiVar = addApiVariableDeclaration(expression, delegateMethod, methodBody); IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException); methodBody.addStatement(ifStatement); ClassNode returnType = nonGeneric(delegateMethod.getReturnType()); if (METHOD_MISSING_METHOD_NAME.equals(declaredMethodName)) { declaredMethodName = STATIC_METHOD_MISSING_METHOD_NAME; } MethodNode methodNode = classNode.getDeclaredMethod(declaredMethodName, parameterTypes); if (methodNode == null) { methodNode = new MethodNode( declaredMethodName, Modifier.PUBLIC | Modifier.STATIC, returnType, copyParameters(parameterTypes), GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, methodBody); methodNode.addAnnotations(delegateMethod.getAnnotations()); classNode.addMethod(methodNode); } return methodNode; }
/** * Tests whether the ClasNode implements the specified method name. * * @param classNode The ClassNode * @param methodName The method name * @return True if it does implement the method */ public static boolean implementsZeroArgMethod(ClassNode classNode, String methodName) { MethodNode method = classNode.getDeclaredMethod(methodName, Parameter.EMPTY_ARRAY); return method != null && (method.isPublic() || method.isProtected()) && !method.isAbstract(); }