private Expression convertInOperatorToTernary( final BinaryExpression bin, final Expression rightExpression, final Expression leftExpression) { MethodCallExpression call = new MethodCallExpression(rightExpression, "isCase", leftExpression); call.setMethodTarget( (MethodNode) bin.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET)); call.setSourcePosition(bin); call.copyNodeMetaData(bin); TernaryExpression tExp = new TernaryExpression( new BooleanExpression( new BinaryExpression( rightExpression, Token.newSymbol("==", -1, -1), new ConstantExpression(null))), new BinaryExpression( leftExpression, Token.newSymbol("==", -1, -1), new ConstantExpression(null)), call); return staticCompilationTransformer.transform(tExp); }
private TernaryExpression createIfInstanceOfAsImmutableStatement( Expression expr, ClassNode type, Expression elseStatement) { return new TernaryExpression( new BooleanExpression( new BinaryExpression( expr, Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1), new ClassExpression(type))), createAsImmutableExpression(expr, type), elseStatement); }
public static ExpressionStatement createPrintlnStatement(String message, String variable) { return new ExpressionStatement( new MethodCallExpression( AbstractGrailsArtefactTransformer.THIS_EXPRESSION, "println", new ArgumentListExpression( new BinaryExpression( new ConstantExpression(message), Token.newSymbol(Types.PLUS, 0, 0), new VariableExpression(variable))))); }
private IfStatement getAutowiringIfStatement( ClassExpression targetClass, VariableExpression fieldExpression, BinaryExpression testTargetAssignment) { VariableExpression appCtxVar = new VariableExpression("applicationContext"); BooleanExpression applicationContextCheck = new BooleanExpression( new BinaryExpression( new BinaryExpression( fieldExpression, GrailsASTUtils.EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION), Token.newSymbol("&&", 0, 0), new BinaryExpression( appCtxVar, GrailsASTUtils.NOT_EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION))); BlockStatement performAutowireBlock = new BlockStatement(); ArgumentListExpression arguments = new ArgumentListExpression(); arguments.addExpression(fieldExpression); arguments.addExpression(new ConstantExpression(1)); arguments.addExpression(new ConstantExpression(false)); BlockStatement assignFromApplicationContext = new BlockStatement(); ArgumentListExpression argWithClassName = new ArgumentListExpression(); MethodCallExpression getClassNameMethodCall = new MethodCallExpression(targetClass, "getName", new ArgumentListExpression()); argWithClassName.addExpression(getClassNameMethodCall); assignFromApplicationContext.addStatement( new ExpressionStatement( new BinaryExpression( fieldExpression, ASSIGN, new MethodCallExpression(appCtxVar, "getBean", argWithClassName)))); BlockStatement elseBlock = new BlockStatement(); elseBlock.addStatement(new ExpressionStatement(testTargetAssignment)); performAutowireBlock.addStatement( new IfStatement( new BooleanExpression( new MethodCallExpression(appCtxVar, "containsBean", argWithClassName)), assignFromApplicationContext, elseBlock)); performAutowireBlock.addStatement( new ExpressionStatement( new MethodCallExpression( new PropertyExpression(appCtxVar, "autowireCapableBeanFactory"), "autowireBeanProperties", arguments))); return new IfStatement(applicationContextCheck, performAutowireBlock, new BlockStatement()); }
/** * Wraps a method body in try / catch logic that catches any errors and logs an error, but does * not rethrow! * * @param methodNode The method node */ public static void wrapMethodBodyInTryCatchDebugStatements(MethodNode methodNode) { BlockStatement code = (BlockStatement) methodNode.getCode(); BlockStatement newCode = new BlockStatement(); TryCatchStatement tryCatchStatement = new TryCatchStatement(code, new BlockStatement()); newCode.addStatement(tryCatchStatement); methodNode.setCode(newCode); BlockStatement catchBlock = new BlockStatement(); ArgumentListExpression logArguments = new ArgumentListExpression(); logArguments.addExpression( new BinaryExpression( new ConstantExpression("Error initializing class: "), Token.newSymbol(Types.PLUS, 0, 0), new VariableExpression("e"))); logArguments.addExpression(new VariableExpression("e")); catchBlock.addStatement( new ExpressionStatement( new MethodCallExpression(new VariableExpression("log"), "error", logArguments))); tryCatchStatement.addCatch( new CatchStatement(new Parameter(new ClassNode(Throwable.class), "e"), catchBlock)); }
protected void addInitErrorsMethod(final ClassNode paramTypeClassNode) { final ASTNode initErrorsMethod = paramTypeClassNode.getMethod( INIT_ERRORS_METHOD_NAME, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (initErrorsMethod == null) { final BlockStatement initErrorsMethodCode = new BlockStatement(); final BinaryExpression errorsIsNullExpression = new BinaryExpression( ERRORS_EXPRESSION, Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), NULL_EXPRESSION); Expression beanPropertyBindingResultConstructorArgs = new ArgumentListExpression( new VariableExpression("this"), new ConstantExpression(paramTypeClassNode.getName())); final Statement newEvaluatorExpression = new ExpressionStatement( new BinaryExpression( ERRORS_EXPRESSION, EQUALS_SYMBOL, new ConstructorCallExpression( new ClassNode(ValidationErrors.class), beanPropertyBindingResultConstructorArgs))); final Statement initErrorsIfNullStatement = new IfStatement( new BooleanExpression(errorsIsNullExpression), newEvaluatorExpression, new ExpressionStatement(new EmptyExpression())); initErrorsMethodCode.addStatement(initErrorsIfNullStatement); paramTypeClassNode.addMethod( new MethodNode( INIT_ERRORS_METHOD_NAME, Modifier.PRIVATE, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, initErrorsMethodCode)); } }
Expression transformMethodCallExpression(final MethodCallExpression expr) { Expression objectExpression = expr.getObjectExpression(); if (expr.isSafe()) { MethodCallExpression notSafe = new MethodCallExpression(objectExpression, expr.getMethod(), expr.getArguments()); notSafe.copyNodeMetaData(expr); notSafe.setSpreadSafe(expr.isSpreadSafe()); notSafe.setSourcePosition(expr); notSafe.setMethodTarget(expr.getMethodTarget()); TernaryExpression texpr = new TernaryExpression( new BooleanExpression( new BinaryExpression( objectExpression, Token.newSymbol( "!=", objectExpression.getLineNumber(), objectExpression.getColumnNumber()), ConstantExpression.NULL)), notSafe, ConstantExpression.NULL); return staticCompilationTransformer.transform(texpr); } ClassNode type = staticCompilationTransformer .getTypeChooser() .resolveType(objectExpression, staticCompilationTransformer.getClassNode()); if (type != null && type.isArray()) { String method = expr.getMethodAsString(); ClassNode componentType = type.getComponentType(); if ("getAt".equals(method)) { Expression arguments = expr.getArguments(); if (arguments instanceof TupleExpression) { List<Expression> argList = ((TupleExpression) arguments).getExpressions(); if (argList.size() == 1) { Expression indexExpr = argList.get(0); ClassNode argType = staticCompilationTransformer .getTypeChooser() .resolveType(indexExpr, staticCompilationTransformer.getClassNode()); ClassNode indexType = ClassHelper.getWrapper(argType); if (componentType.isEnum() && ClassHelper.Number_TYPE == indexType) { // workaround for generated code in enums which use .next() returning a Number indexType = ClassHelper.Integer_TYPE; } if (argType != null && ClassHelper.Integer_TYPE == indexType) { BinaryExpression binaryExpression = new BinaryExpression( objectExpression, Token.newSymbol("[", indexExpr.getLineNumber(), indexExpr.getColumnNumber()), indexExpr); binaryExpression.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, componentType); return staticCompilationTransformer.transform(binaryExpression); } } } } if ("putAt".equals(method)) { Expression arguments = expr.getArguments(); if (arguments instanceof TupleExpression) { List<Expression> argList = ((TupleExpression) arguments).getExpressions(); if (argList.size() == 2) { Expression indexExpr = argList.get(0); Expression objExpr = argList.get(1); ClassNode argType = staticCompilationTransformer .getTypeChooser() .resolveType(indexExpr, staticCompilationTransformer.getClassNode()); if (argType != null && ClassHelper.Integer_TYPE == ClassHelper.getWrapper(argType)) { BinaryExpression arrayGet = new BinaryExpression( objectExpression, Token.newSymbol("[", indexExpr.getLineNumber(), indexExpr.getColumnNumber()), indexExpr); arrayGet.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, componentType); BinaryExpression assignment = new BinaryExpression( arrayGet, Token.newSymbol("=", objExpr.getLineNumber(), objExpr.getColumnNumber()), objExpr); return staticCompilationTransformer.transform(assignment); } } } } } return staticCompilationTransformer.superTransform(expr); }
/** * Transformation used by the {@link grails.test.mixin.TestFor} annotation to signify the class * under test. * * @author Graeme Rocher * @since 2.0 */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) @SuppressWarnings("rawtypes") public class TestForTransformation extends TestMixinTransformation { private static final ClassNode MY_TYPE = new ClassNode(TestFor.class); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final Token ASSIGN = Token.newSymbol("=", -1, -1); protected static final Map<String, Class> artefactTypeToTestMap = new HashMap<String, Class>(); static { artefactTypeToTestMap.put(ControllerArtefactHandler.TYPE, ControllerUnitTestMixin.class); artefactTypeToTestMap.put(TagLibArtefactHandler.TYPE, GroovyPageUnitTestMixin.class); artefactTypeToTestMap.put( FiltersConfigArtefactHandler.getTYPE().toString(), FiltersUnitTestMixin.class); artefactTypeToTestMap.put(UrlMappingsArtefactHandler.TYPE, UrlMappingsUnitTestMixin.class); artefactTypeToTestMap.put(ServiceArtefactHandler.TYPE, ServiceUnitTestMixin.class); } public static final String DOMAIN_TYPE = "Domain"; public static final ClassNode BEFORE_CLASS_NODE = new ClassNode(Before.class); public static final AnnotationNode BEFORE_ANNOTATION = new AnnotationNode(BEFORE_CLASS_NODE); public static final AnnotationNode TEST_ANNOTATION = new AnnotationNode(new ClassNode(Test.class)); public static final ClassNode GROOVY_TEST_CASE_CLASS = new ClassNode(GroovyTestCase.class); private ResourceLocator resourceLocator; public ResourceLocator getResourceLocator() { if (resourceLocator == null) { resourceLocator = new DefaultResourceLocator(); BuildSettings buildSettings = BuildSettingsHolder.getSettings(); String basedir; if (buildSettings != null) { basedir = buildSettings.getBaseDir().getAbsolutePath(); } else { basedir = "."; } resourceLocator.setSearchLocation(basedir); } return resourceLocator; } @Override public void visit(ASTNode[] astNodes, SourceUnit source) { if (!(astNodes[0] instanceof AnnotationNode) || !(astNodes[1] instanceof AnnotatedNode)) { throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class"); } AnnotatedNode parent = (AnnotatedNode) astNodes[1]; AnnotationNode node = (AnnotationNode) astNodes[0]; if (!MY_TYPE.equals(node.getClassNode()) || !(parent instanceof ClassNode)) { return; } ClassNode classNode = (ClassNode) parent; if (classNode.isInterface() || Modifier.isAbstract(classNode.getModifiers())) { return; } boolean junit3Test = isJunit3Test(classNode); boolean spockTest = isSpockTest(classNode); boolean isJunit = classNode.getName().endsWith("Tests"); if (!junit3Test && !spockTest && !isJunit) return; Expression value = node.getMember("value"); ClassExpression ce; if (value instanceof ClassExpression) { ce = (ClassExpression) value; testFor(classNode, ce); } else { if (!junit3Test) { List<AnnotationNode> annotations = classNode.getAnnotations(MY_TYPE); if (annotations.size() > 0) return; // bail out, in this case it was already applied as a local transform // no explicit class specified try by convention String fileName = source.getName(); String className = GrailsResourceUtils.getClassName(new FileSystemResource(fileName)); if (className != null) { boolean isSpock = className.endsWith("Spec"); String targetClassName = null; if (isJunit) { targetClassName = className.substring(0, className.indexOf("Tests")); } else if (isSpock) { targetClassName = className.substring(0, className.indexOf("Spec")); } if (targetClassName != null) { Resource targetResource = getResourceLocator().findResourceForClassName(targetClassName); if (targetResource != null) { try { if (GrailsResourceUtils.isDomainClass(targetResource.getURL())) { testFor( classNode, new ClassExpression( new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE))); } else { for (String artefactType : artefactTypeToTestMap.keySet()) { if (targetClassName.endsWith(artefactType)) { testFor( classNode, new ClassExpression( new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE))); break; } } } } catch (IOException e) { // ignore } } } } } } } /** * Main entry point for the calling the TestForTransformation programmatically. * * @param classNode The class node that represents th test * @param ce The class expression that represents the class to test */ public void testFor(ClassNode classNode, ClassExpression ce) { boolean junit3Test = isJunit3Test(classNode); // make sure the 'log' property is not the one from GroovyTestCase FieldNode log = classNode.getField("log"); if (log == null || log.getDeclaringClass().equals(GROOVY_TEST_CASE_CLASS)) { LoggingTransformer.addLogField(classNode, classNode.getName()); } boolean isSpockTest = isSpockTest(classNode); if (!isSpockTest && !junit3Test) { // assume JUnit 4 Map<String, MethodNode> declaredMethodsMap = classNode.getDeclaredMethodsMap(); for (String methodName : declaredMethodsMap.keySet()) { MethodNode methodNode = declaredMethodsMap.get(methodName); if (isCandidateMethod(methodNode) && methodNode.getName().startsWith("test")) { if (methodNode.getAnnotations().size() == 0) { methodNode.addAnnotation(TEST_ANNOTATION); } } } } final MethodNode methodToAdd = weaveMock(classNode, ce, true); if (methodToAdd != null && junit3Test) { addMethodCallsToMethod(classNode, SET_UP_METHOD, Arrays.asList(methodToAdd)); } } private Map<ClassNode, List<Class>> wovenMixins = new HashMap<ClassNode, List<Class>>(); protected MethodNode weaveMock( ClassNode classNode, ClassExpression value, boolean isClassUnderTest) { ClassNode testTarget = value.getType(); String className = testTarget.getName(); MethodNode testForMethod = null; for (String artefactType : artefactTypeToTestMap.keySet()) { if (className.endsWith(artefactType)) { Class mixinClass = artefactTypeToTestMap.get(artefactType); if (!isAlreadyWoven(classNode, mixinClass)) { weaveMixinClass(classNode, mixinClass); if (isClassUnderTest) { testForMethod = addClassUnderTestMethod(classNode, value, artefactType); } else { addMockCollaboratorToSetup(classNode, value, artefactType); } return testForMethod; } addMockCollaboratorToSetup(classNode, value, artefactType); return null; } } // must be a domain class weaveMixinClass(classNode, DomainClassUnitTestMixin.class); if (isClassUnderTest) { testForMethod = addClassUnderTestMethod(classNode, value, DOMAIN_TYPE); } else { addMockCollaboratorToSetup(classNode, value, DOMAIN_TYPE); } return testForMethod; } private void addMockCollaboratorToSetup( ClassNode classNode, ClassExpression targetClassExpression, String artefactType) { BlockStatement methodBody; if (isJunit3Test(classNode)) { methodBody = getJunit3Setup(classNode); addMockCollaborator(artefactType, targetClassExpression, methodBody); } else { addToJunit4BeforeMethods(classNode, artefactType, targetClassExpression); } } private void addToJunit4BeforeMethods( ClassNode classNode, String artefactType, ClassExpression targetClassExpression) { Map<String, MethodNode> declaredMethodsMap = classNode.getDeclaredMethodsMap(); boolean weavedIntoBeforeMethods = false; for (MethodNode methodNode : declaredMethodsMap.values()) { if (isDeclaredBeforeMethod(methodNode)) { Statement code = getMethodBody(methodNode); addMockCollaborator(artefactType, targetClassExpression, (BlockStatement) code); weavedIntoBeforeMethods = true; } } if (!weavedIntoBeforeMethods) { BlockStatement junit4Setup = getJunit4Setup(classNode); addMockCollaborator(artefactType, targetClassExpression, junit4Setup); } } private Statement getMethodBody(MethodNode methodNode) { Statement code = methodNode.getCode(); if (!(code instanceof BlockStatement)) { BlockStatement body = new BlockStatement(); body.addStatement(code); code = body; } return code; } private boolean isDeclaredBeforeMethod(MethodNode methodNode) { return isPublicInstanceMethod(methodNode) && hasAnnotation(methodNode, Before.class) && !hasAnnotation(methodNode, MixinMethod.class); } private boolean isPublicInstanceMethod(MethodNode methodNode) { return !methodNode.isSynthetic() && !methodNode.isStatic() && methodNode.isPublic(); } private BlockStatement getJunit4Setup(ClassNode classNode) { MethodNode setupMethod = classNode.getMethod(SET_UP_METHOD, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (setupMethod == null) { setupMethod = new MethodNode( SET_UP_METHOD, Modifier.PUBLIC, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, null, new BlockStatement()); setupMethod.addAnnotation(MIXIN_METHOD_ANNOTATION); classNode.addMethod(setupMethod); } if (setupMethod.getAnnotations(BEFORE_CLASS_NODE).size() == 0) { setupMethod.addAnnotation(BEFORE_ANNOTATION); } return getOrCreateMethodBody(classNode, setupMethod, SET_UP_METHOD); } private BlockStatement getJunit3Setup(ClassNode classNode) { return getOrCreateNoArgsMethodBody(classNode, SET_UP_METHOD); } private boolean isAlreadyWoven(ClassNode classNode, Class mixinClass) { List<Class> mixinClasses = wovenMixins.get(classNode); if (mixinClasses == null) { mixinClasses = new ArrayList<Class>(); mixinClasses.add(mixinClass); wovenMixins.put(classNode, mixinClasses); } else { if (mixinClasses.contains(mixinClass)) { return true; } mixinClasses.add(mixinClass); } return false; } protected void weaveMixinClass(ClassNode classNode, Class mixinClass) { ListExpression listExpression = new ListExpression(); listExpression.addExpression(new ClassExpression(new ClassNode(mixinClass))); weaveMixinsIntoClass(classNode, listExpression); } protected MethodNode addClassUnderTestMethod( ClassNode classNode, ClassExpression targetClass, String type) { String methodName = "setup" + type + "UnderTest"; String fieldName = GrailsNameUtils.getPropertyName(type); String getterName = GrailsNameUtils.getGetterName(fieldName); fieldName = '$' + fieldName; if (classNode.getField(fieldName) == null) { classNode.addField(fieldName, Modifier.PRIVATE, targetClass.getType(), null); } MethodNode methodNode = classNode.getMethod(methodName, GrailsArtefactClassInjector.ZERO_PARAMETERS); VariableExpression fieldExpression = new VariableExpression(fieldName); if (methodNode == null) { BlockStatement setupMethodBody = new BlockStatement(); addMockCollaborator(type, targetClass, setupMethodBody); methodNode = new MethodNode( methodName, Modifier.PUBLIC, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, null, setupMethodBody); methodNode.addAnnotation(BEFORE_ANNOTATION); methodNode.addAnnotation(MIXIN_METHOD_ANNOTATION); classNode.addMethod(methodNode); } MethodNode getter = classNode.getMethod(getterName, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (getter == null) { BlockStatement getterBody = new BlockStatement(); getter = new MethodNode( getterName, Modifier.PUBLIC, targetClass.getType().getPlainNodeReference(), GrailsArtefactClassInjector.ZERO_PARAMETERS, null, getterBody); BinaryExpression testTargetAssignment = new BinaryExpression( fieldExpression, ASSIGN, new ConstructorCallExpression( targetClass.getType(), GrailsArtefactClassInjector.ZERO_ARGS)); IfStatement autowiringIfStatement = getAutowiringIfStatement(targetClass, fieldExpression, testTargetAssignment); getterBody.addStatement(autowiringIfStatement); getterBody.addStatement(new ReturnStatement(fieldExpression)); classNode.addMethod(getter); } return methodNode; } private IfStatement getAutowiringIfStatement( ClassExpression targetClass, VariableExpression fieldExpression, BinaryExpression testTargetAssignment) { VariableExpression appCtxVar = new VariableExpression("applicationContext"); BooleanExpression applicationContextCheck = new BooleanExpression( new BinaryExpression( new BinaryExpression( fieldExpression, GrailsASTUtils.EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION), Token.newSymbol("&&", 0, 0), new BinaryExpression( appCtxVar, GrailsASTUtils.NOT_EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION))); BlockStatement performAutowireBlock = new BlockStatement(); ArgumentListExpression arguments = new ArgumentListExpression(); arguments.addExpression(fieldExpression); arguments.addExpression(new ConstantExpression(1)); arguments.addExpression(new ConstantExpression(false)); BlockStatement assignFromApplicationContext = new BlockStatement(); ArgumentListExpression argWithClassName = new ArgumentListExpression(); MethodCallExpression getClassNameMethodCall = new MethodCallExpression(targetClass, "getName", new ArgumentListExpression()); argWithClassName.addExpression(getClassNameMethodCall); assignFromApplicationContext.addStatement( new ExpressionStatement( new BinaryExpression( fieldExpression, ASSIGN, new MethodCallExpression(appCtxVar, "getBean", argWithClassName)))); BlockStatement elseBlock = new BlockStatement(); elseBlock.addStatement(new ExpressionStatement(testTargetAssignment)); performAutowireBlock.addStatement( new IfStatement( new BooleanExpression( new MethodCallExpression(appCtxVar, "containsBean", argWithClassName)), assignFromApplicationContext, elseBlock)); performAutowireBlock.addStatement( new ExpressionStatement( new MethodCallExpression( new PropertyExpression(appCtxVar, "autowireCapableBeanFactory"), "autowireBeanProperties", arguments))); return new IfStatement(applicationContextCheck, performAutowireBlock, new BlockStatement()); } protected void addMockCollaborator( String mockType, ClassExpression targetClass, BlockStatement methodBody) { ArgumentListExpression args = new ArgumentListExpression(); args.addExpression(targetClass); methodBody .getStatements() .add( 0, new ExpressionStatement( new MethodCallExpression(THIS_EXPRESSION, "mock" + mockType, args))); } }
/** @author Jeff Brown */ public class ASTValidationErrorsHelper implements ASTErrorsHelper { private static final ConstantExpression NULL_EXPRESSION = new ConstantExpression(null); private static final String SET_ERRORS_METHOD_NAME = "setErrors"; private static final String GET_ERRORS_METHOD_NAME = "getErrors"; private static final String HAS_ERRORS_METHOD_NAME = "hasErrors"; private static final String CLEAR_ERRORS_METHOD_NAME = "clearErrors"; private static final String INIT_ERRORS_METHOD_NAME = "initErrors"; private static final String ERRORS_PROPERTY_NAME = "errors"; private static final Token EQUALS_SYMBOL = Token.newSymbol(Types.EQUALS, 0, 0); private static final ClassNode ERRORS_CLASS_NODE = new ClassNode(Errors.class); private static final VariableExpression ERRORS_EXPRESSION = new VariableExpression(ERRORS_PROPERTY_NAME); private static final TupleExpression EMPTY_TUPLE = new TupleExpression(); public void injectErrorsCode(ClassNode classNode) { addErrorsField(classNode); addInitErrorsMethod(classNode); addGetErrorsMethod(classNode); addHasErrorsMethod(classNode); addSetErrorsMethod(classNode); addClearErrorsMethod(classNode); } protected void addErrorsField(final ClassNode paramTypeClassNode) { final ASTNode errorsField = paramTypeClassNode.getField(ERRORS_PROPERTY_NAME); if (errorsField == null) { paramTypeClassNode.addField( new FieldNode( ERRORS_PROPERTY_NAME, Modifier.PRIVATE, ERRORS_CLASS_NODE, paramTypeClassNode, NULL_EXPRESSION)); } } protected void addInitErrorsMethod(final ClassNode paramTypeClassNode) { final ASTNode initErrorsMethod = paramTypeClassNode.getMethod( INIT_ERRORS_METHOD_NAME, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (initErrorsMethod == null) { final BlockStatement initErrorsMethodCode = new BlockStatement(); final BinaryExpression errorsIsNullExpression = new BinaryExpression( ERRORS_EXPRESSION, Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), NULL_EXPRESSION); Expression beanPropertyBindingResultConstructorArgs = new ArgumentListExpression( new VariableExpression("this"), new ConstantExpression(paramTypeClassNode.getName())); final Statement newEvaluatorExpression = new ExpressionStatement( new BinaryExpression( ERRORS_EXPRESSION, EQUALS_SYMBOL, new ConstructorCallExpression( new ClassNode(ValidationErrors.class), beanPropertyBindingResultConstructorArgs))); final Statement initErrorsIfNullStatement = new IfStatement( new BooleanExpression(errorsIsNullExpression), newEvaluatorExpression, new ExpressionStatement(new EmptyExpression())); initErrorsMethodCode.addStatement(initErrorsIfNullStatement); paramTypeClassNode.addMethod( new MethodNode( INIT_ERRORS_METHOD_NAME, Modifier.PRIVATE, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, initErrorsMethodCode)); } } protected void addClearErrorsMethod(final ClassNode paramTypeClassNode) { final ASTNode clearErrorsMethod = paramTypeClassNode.getMethod( CLEAR_ERRORS_METHOD_NAME, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (clearErrorsMethod == null) { final BlockStatement clearErrorsMethodCode = new BlockStatement(); Expression nullOutErrorsFieldExpression = new BinaryExpression(ERRORS_EXPRESSION, EQUALS_SYMBOL, NULL_EXPRESSION); clearErrorsMethodCode.addStatement(new ExpressionStatement(nullOutErrorsFieldExpression)); paramTypeClassNode.addMethod( new MethodNode( CLEAR_ERRORS_METHOD_NAME, Modifier.PUBLIC, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, clearErrorsMethodCode)); } } protected void addHasErrorsMethod(final ClassNode paramTypeClassNode) { final ASTNode getErrorsMethod = paramTypeClassNode.getMethod( HAS_ERRORS_METHOD_NAME, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (getErrorsMethod == null) { final BlockStatement hasErrorsMethodCode = new BlockStatement(); final Expression initErrorsMethodCallExpression = new MethodCallExpression( new VariableExpression("this"), INIT_ERRORS_METHOD_NAME, EMPTY_TUPLE); hasErrorsMethodCode.addStatement(new ExpressionStatement(initErrorsMethodCallExpression)); final Statement returnStatement = new ReturnStatement( new BooleanExpression( new MethodCallExpression( ERRORS_EXPRESSION, HAS_ERRORS_METHOD_NAME, EMPTY_TUPLE))); hasErrorsMethodCode.addStatement(returnStatement); paramTypeClassNode.addMethod( new MethodNode( HAS_ERRORS_METHOD_NAME, Modifier.PUBLIC, new ClassNode(Boolean.class), GrailsArtefactClassInjector.ZERO_PARAMETERS, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, hasErrorsMethodCode)); } } protected void addGetErrorsMethod(final ClassNode paramTypeClassNode) { final ASTNode getErrorsMethod = paramTypeClassNode.getMethod( GET_ERRORS_METHOD_NAME, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (getErrorsMethod == null) { final BlockStatement getErrorsMethodCode = new BlockStatement(); final Expression initErrorsMethodCallExpression = new MethodCallExpression( new VariableExpression("this"), INIT_ERRORS_METHOD_NAME, EMPTY_TUPLE); getErrorsMethodCode.addStatement(new ExpressionStatement(initErrorsMethodCallExpression)); final Statement returnStatement = new ReturnStatement(ERRORS_EXPRESSION); getErrorsMethodCode.addStatement(returnStatement); paramTypeClassNode.addMethod( new MethodNode( GET_ERRORS_METHOD_NAME, Modifier.PUBLIC, ERRORS_CLASS_NODE, GrailsArtefactClassInjector.ZERO_PARAMETERS, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, getErrorsMethodCode)); } } protected void addSetErrorsMethod(final ClassNode paramTypeClassNode) { final String errorsArgumentName = "$errorsArg"; MethodNode setErrorsMethod = paramTypeClassNode.getMethod( SET_ERRORS_METHOD_NAME, new Parameter[] {new Parameter(ERRORS_CLASS_NODE, errorsArgumentName)}); if (setErrorsMethod == null) { final Expression assignErrorsExpression = new BinaryExpression( ERRORS_EXPRESSION, EQUALS_SYMBOL, new VariableExpression(errorsArgumentName)); setErrorsMethod = new MethodNode( SET_ERRORS_METHOD_NAME, Modifier.PUBLIC, ClassHelper.VOID_TYPE, new Parameter[] {new Parameter(ERRORS_CLASS_NODE, errorsArgumentName)}, GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, new ExpressionStatement(assignErrorsExpression)); paramTypeClassNode.addMethod(setErrorsMethod); } } }
/** * Helper methods for working with Groovy AST trees. * * @author Graeme Rocher * @since 0.3 */ public class GrailsASTUtils { public static final String METHOD_MISSING_METHOD_NAME = "methodMissing"; public static final String STATIC_METHOD_MISSING_METHOD_NAME = "$static_methodMissing"; public static final Token EQUALS_OPERATOR = Token.newSymbol("==", 0, 0); public static final Token NOT_EQUALS_OPERATOR = Token.newSymbol("!=", 0, 0); public static final ClassNode MISSING_METHOD_EXCEPTION = new ClassNode(MissingMethodException.class); public static final ConstantExpression NULL_EXPRESSION = new ConstantExpression(null); public static final Token ASSIGNMENT_OPERATOR = Token.newSymbol(Types.ASSIGNMENT_OPERATOR, 0, 0); public static void warning( final SourceUnit sourceUnit, final ASTNode node, final String warningMessage) { final String sample = sourceUnit.getSample(node.getLineNumber(), node.getColumnNumber(), new Janitor()); GrailsConsole.getInstance().warning(warningMessage + "\n\n" + sample); } /** * Generates a fatal compilation error * * @param sourceUnit the SourceUnit * @param astNode the ASTNode which caused the error * @param message The error message */ public static void error( final SourceUnit sourceUnit, final ASTNode astNode, final String message) { error(sourceUnit, astNode, message, true); } /** * Generates a fatal compilation error * * @param sourceUnit the SourceUnit * @param astNode the ASTNode which caused the error * @param message The error message * @param fatal indicates if this is a fatal error */ public static void error( final SourceUnit sourceUnit, final ASTNode astNode, final String message, final boolean fatal) { final SyntaxException syntaxException = new SyntaxException(message, astNode.getLineNumber(), astNode.getColumnNumber()); final SyntaxErrorMessage syntaxErrorMessage = new SyntaxErrorMessage(syntaxException, sourceUnit); sourceUnit.getErrorCollector().addError(syntaxErrorMessage, fatal); } /** * Returns whether a classNode has the specified property or not * * @param classNode The ClassNode * @param propertyName The name of the property * @return True if the property exists in the ClassNode */ public static boolean hasProperty(ClassNode classNode, String propertyName) { if (classNode == null || StringUtils.isBlank(propertyName)) { return false; } final MethodNode method = classNode.getMethod(GrailsNameUtils.getGetterName(propertyName), Parameter.EMPTY_ARRAY); if (method != null) return true; for (PropertyNode pn : classNode.getProperties()) { if (pn.getName().equals(propertyName) && !pn.isPrivate()) { return true; } } return false; } public static boolean hasOrInheritsProperty(ClassNode classNode, String propertyName) { if (hasProperty(classNode, propertyName)) { return true; } ClassNode parent = classNode.getSuperClass(); while (parent != null && !getFullName(parent).equals("java.lang.Object")) { if (hasProperty(parent, propertyName)) { return true; } parent = parent.getSuperClass(); } return false; } /** * 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(); } @SuppressWarnings("rawtypes") public static boolean implementsOrInheritsZeroArgMethod( ClassNode classNode, String methodName, List ignoreClasses) { if (implementsZeroArgMethod(classNode, methodName)) { return true; } ClassNode parent = classNode.getSuperClass(); while (parent != null && !getFullName(parent).equals("java.lang.Object")) { if (!ignoreClasses.contains(parent) && implementsZeroArgMethod(parent, methodName)) { return true; } parent = parent.getSuperClass(); } return false; } /** * Gets the full name of a ClassNode. * * @param classNode The class node * @return The full name */ public static String getFullName(ClassNode classNode) { return classNode.getName(); } public static ClassNode getFurthestParent(ClassNode classNode) { ClassNode parent = classNode.getSuperClass(); while (parent != null && !getFullName(parent).equals("java.lang.Object")) { classNode = parent; parent = parent.getSuperClass(); } return classNode; } public static ClassNode getFurthestUnresolvedParent(ClassNode classNode) { ClassNode parent = classNode.getSuperClass(); while (parent != null && !getFullName(parent).equals("java.lang.Object") && !parent.isResolved() && !Modifier.isAbstract(parent.getModifiers())) { classNode = parent; parent = parent.getSuperClass(); } return classNode; } /** * Adds a delegate method to the target class node where the first argument is to the delegate * method is 'this'. In other words a method such as foo(Object instance, String bar) would be * added with a signature of foo(String) and 'this' is passed to the delegate instance * * @param classNode The class node * @param delegate The expression that looks up the delegate * @param declaredMethod The declared method * @return The added method node or null if it couldn't be added */ public static MethodNode addDelegateInstanceMethod( ClassNode classNode, Expression delegate, MethodNode declaredMethod) { return addDelegateInstanceMethod(classNode, delegate, declaredMethod, true); } /** * Adds a delegate method to the target class node where the first argument is to the delegate * method is 'this'. In other words a method such as foo(Object instance, String bar) would be * added with a signature of foo(String) and 'this' is passed to the delegate instance * * @param classNode The class node * @param delegate The expression that looks up the delegate * @param declaredMethod The declared method * @param thisAsFirstArgument Whether 'this' should be passed as the first argument to the method * @return The added method node or null if it couldn't be added */ public static MethodNode addDelegateInstanceMethod( ClassNode classNode, Expression delegate, MethodNode declaredMethod, boolean thisAsFirstArgument) { Parameter[] parameterTypes = thisAsFirstArgument ? getRemainingParameterTypes(declaredMethod.getParameters()) : declaredMethod.getParameters(); String methodName = declaredMethod.getName(); if (classNode.hasDeclaredMethod(methodName, parameterTypes)) { return null; } String propertyName = GrailsClassUtils.getPropertyForGetter(methodName); if (propertyName != null && parameterTypes.length == 0 && classNode.hasProperty(propertyName)) { return null; } propertyName = GrailsClassUtils.getPropertyForSetter(methodName); if (propertyName != null && parameterTypes.length == 1 && classNode.hasProperty(propertyName)) { return null; } BlockStatement methodBody = new BlockStatement(); ArgumentListExpression arguments = createArgumentListFromParameters(parameterTypes, thisAsFirstArgument); ClassNode returnType = nonGeneric(declaredMethod.getReturnType()); MethodCallExpression methodCallExpression = new MethodCallExpression(delegate, methodName, arguments); methodCallExpression.setMethodTarget(declaredMethod); ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, declaredMethod); VariableExpression apiVar = addApiVariableDeclaration(delegate, declaredMethod, methodBody); IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException); methodBody.addStatement(ifStatement); MethodNode methodNode = new MethodNode( methodName, Modifier.PUBLIC, returnType, copyParameters(parameterTypes), GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, methodBody); methodNode.addAnnotations(declaredMethod.getAnnotations()); classNode.addMethod(methodNode); return methodNode; } private static IfStatement createIfElseStatementForApiMethodCall( MethodCallExpression methodCallExpression, VariableExpression apiVar, ThrowStatement missingMethodException) { BlockStatement ifBlock = new BlockStatement(); ifBlock.addStatement(missingMethodException); BlockStatement elseBlock = new BlockStatement(); elseBlock.addStatement(new ExpressionStatement(methodCallExpression)); return new IfStatement( new BooleanExpression(new BinaryExpression(apiVar, EQUALS_OPERATOR, NULL_EXPRESSION)), ifBlock, elseBlock); } private static VariableExpression addApiVariableDeclaration( Expression delegate, MethodNode declaredMethod, BlockStatement methodBody) { VariableExpression apiVar = new VariableExpression("$api_" + declaredMethod.getName()); DeclarationExpression de = new DeclarationExpression(apiVar, ASSIGNMENT_OPERATOR, delegate); methodBody.addStatement(new ExpressionStatement(de)); return apiVar; } private static ThrowStatement createMissingMethodThrowable( ClassNode classNode, MethodNode declaredMethodNode) { ArgumentListExpression exceptionArgs = new ArgumentListExpression(); exceptionArgs.addExpression(new ConstantExpression(declaredMethodNode.getName())); exceptionArgs.addExpression(new ClassExpression(classNode)); return new ThrowStatement( new ConstructorCallExpression(MISSING_METHOD_EXCEPTION, exceptionArgs)); } /** * Creates an argument list from the given parameter types. * * @param parameterTypes The parameter types * @param thisAsFirstArgument Whether to include a reference to 'this' as the first argument * @return the arguments */ public static ArgumentListExpression createArgumentListFromParameters( Parameter[] parameterTypes, boolean thisAsFirstArgument) { ArgumentListExpression arguments = new ArgumentListExpression(); if (thisAsFirstArgument) { arguments.addExpression(AbstractGrailsArtefactTransformer.THIS_EXPRESSION); } for (Parameter parameterType : parameterTypes) { arguments.addExpression(new VariableExpression(parameterType.getName())); } return arguments; } /** * Gets the remaining parameters excluding the first parameter in the given list * * @param parameters The parameters * @return A new array with the first parameter removed */ public static Parameter[] getRemainingParameterTypes(Parameter[] parameters) { if (parameters.length == 0) { return GrailsArtefactClassInjector.ZERO_PARAMETERS; } Parameter[] newParameters = new Parameter[parameters.length - 1]; System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1); return newParameters; } /** * Adds a static method call to given class node that delegates to the given method * * @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(ClassNode classNode, MethodNode delegateMethod) { ClassExpression classExpression = new ClassExpression(delegateMethod.getDeclaringClass()); return addDelegateStaticMethod(classExpression, classNode, delegateMethod); } /** * 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; } /** * Adds or modifies an existing constructor to delegate to the given static constructor method for * initialization logic. * * @param classNode The class node * @param constructorMethod The constructor static method */ public static void addDelegateConstructor(ClassNode classNode, MethodNode constructorMethod) { BlockStatement constructorBody = new BlockStatement(); Parameter[] constructorParams = getRemainingParameterTypes(constructorMethod.getParameters()); ArgumentListExpression arguments = createArgumentListFromParameters(constructorParams, true); MethodCallExpression constructCallExpression = new MethodCallExpression( new ClassExpression(constructorMethod.getDeclaringClass()), "initialize", arguments); constructCallExpression.setMethodTarget(constructorMethod); ExpressionStatement constructorInitExpression = new ExpressionStatement(constructCallExpression); if (constructorParams.length > 0) { constructorBody.addStatement( new ExpressionStatement( new ConstructorCallExpression( ClassNode.THIS, GrailsArtefactClassInjector.ZERO_ARGS))); } constructorBody.addStatement(constructorInitExpression); if (constructorParams.length == 0) { // handle default constructor ConstructorNode constructorNode = getDefaultConstructor(classNode); if (constructorNode != null) { List<AnnotationNode> annotations = constructorNode.getAnnotations(new ClassNode(GrailsDelegatingConstructor.class)); if (annotations.size() == 0) { Statement existingBodyCode = constructorNode.getCode(); if (existingBodyCode instanceof BlockStatement) { ((BlockStatement) existingBodyCode).addStatement(constructorInitExpression); } else { constructorNode.setCode(constructorBody); } } } else { constructorNode = new ConstructorNode(Modifier.PUBLIC, constructorBody); classNode.addConstructor(constructorNode); } constructorNode.addAnnotation( new AnnotationNode(new ClassNode(GrailsDelegatingConstructor.class))); } else { // create new constructor, restoring default constructor if there is none ConstructorNode cn = findConstructor(classNode, constructorParams); if (cn == null) { cn = new ConstructorNode( Modifier.PUBLIC, copyParameters(constructorParams), null, constructorBody); classNode.addConstructor(cn); } else { List<AnnotationNode> annotations = cn.getAnnotations(new ClassNode(GrailsDelegatingConstructor.class)); if (annotations.size() == 0) { Statement code = cn.getCode(); constructorBody.addStatement(code); cn.setCode(constructorBody); } } ConstructorNode defaultConstructor = getDefaultConstructor(classNode); if (defaultConstructor == null) { // add empty classNode.addConstructor(new ConstructorNode(Modifier.PUBLIC, new BlockStatement())); } cn.addAnnotation(new AnnotationNode(new ClassNode(GrailsDelegatingConstructor.class))); } } /** * Finds a constructor for the given class node and parameter types * * @param classNode The class node * @param constructorParams The parameter types * @return The located constructor or null */ public static ConstructorNode findConstructor( ClassNode classNode, Parameter[] constructorParams) { List<ConstructorNode> declaredConstructors = classNode.getDeclaredConstructors(); for (ConstructorNode declaredConstructor : declaredConstructors) { if (parametersEqual(constructorParams, declaredConstructor.getParameters())) { return declaredConstructor; } } return null; } /** @return true if the two arrays are of the same size and have the same contents */ public static boolean parametersEqual(Parameter[] a, Parameter[] b) { if (a.length != b.length) { return false; } for (int i = 0; i < a.length; i++) { if (!a[i].getType().equals(b[i].getType())) { return false; } } return true; } /** * Obtains the default constructor for the given class node. * * @param classNode The class node * @return The default constructor or null if there isn't one */ public static ConstructorNode getDefaultConstructor(ClassNode classNode) { for (ConstructorNode cons : classNode.getDeclaredConstructors()) { if (cons.getParameters().length == 0) { return cons; } } return null; } private static Parameter[] copyParameters(Parameter[] parameterTypes) { Parameter[] newParameterTypes = new Parameter[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { Parameter parameterType = parameterTypes[i]; Parameter newParameter = new Parameter( nonGeneric(parameterType.getType()), parameterType.getName(), parameterType.getInitialExpression()); newParameter.addAnnotations(parameterType.getAnnotations()); newParameterTypes[i] = newParameter; } return newParameterTypes; } public static ClassNode nonGeneric(ClassNode type) { if (type.isUsingGenerics()) { final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName()); nonGen.setRedirect(type); nonGen.setGenericsTypes(null); nonGen.setUsingGenerics(false); return nonGen; } if (type.isArray()) { final ClassNode nonGen = ClassHelper.makeWithoutCaching(Object.class); nonGen.setUsingGenerics(false); return nonGen.makeArray(); } return type.getPlainNodeReference(); } public static boolean isCandidateInstanceMethod(ClassNode classNode, MethodNode declaredMethod) { Parameter[] parameterTypes = declaredMethod.getParameters(); return isCandidateMethod(declaredMethod) && parameterTypes != null && parameterTypes.length > 0 && isAssignableFrom(parameterTypes[0].getType(), classNode); } /** * Determines if the class or interface represented by the superClass argument is either the same * as, or is a superclass or superinterface of, the class or interface represented by the * specified subClass parameter. * * @param superClass The super class to check * @param childClass The sub class the check * @return true if the childClass is either equal to or a sub class of the specified superClass */ private static boolean isAssignableFrom(ClassNode superClass, ClassNode childClass) { ClassNode currentSuper = childClass; while (currentSuper != null) { if (currentSuper.equals(superClass)) { return true; } currentSuper = currentSuper.getSuperClass(); } return false; } public static boolean isCandidateMethod(MethodNode declaredMethod) { return !declaredMethod.isSynthetic() && !declaredMethod.getName().contains("$") && Modifier.isPublic(declaredMethod.getModifiers()) && !Modifier.isAbstract(declaredMethod.getModifiers()); } public static boolean isConstructorMethod(MethodNode declaredMethod) { return declaredMethod.isStatic() && declaredMethod.isPublic() && declaredMethod.getName().equals("initialize") && declaredMethod.getParameters().length >= 1 && declaredMethod .getParameters()[0] .getType() .equals(AbstractGrailsArtefactTransformer.OBJECT_CLASS); } public static void addDelegateInstanceMethods( ClassNode classNode, ClassNode delegateNode, Expression delegateInstance) { addDelegateInstanceMethods(classNode, classNode, delegateNode, delegateInstance); } public static void addDelegateInstanceMethods( ClassNode supportedSuperType, ClassNode classNode, ClassNode delegateNode, Expression delegateInstance) { while (!delegateNode.equals(AbstractGrailsArtefactTransformer.OBJECT_CLASS)) { List<MethodNode> declaredMethods = delegateNode.getMethods(); for (MethodNode declaredMethod : declaredMethods) { if (isConstructorMethod(declaredMethod)) { addDelegateConstructor(classNode, declaredMethod); } else if (isCandidateInstanceMethod(supportedSuperType, declaredMethod)) { addDelegateInstanceMethod(classNode, delegateInstance, declaredMethod); } } delegateNode = delegateNode.getSuperClass(); } } public static void addFieldIfNonExistent( ClassNode classNode, ClassNode fieldType, String fieldName) { if (classNode != null && classNode.getField(fieldName) == null) { classNode.addField( fieldName, Modifier.PRIVATE, fieldType, new ConstructorCallExpression(fieldType, new ArgumentListExpression())); } } public static void addAnnotationIfNecessary( ClassNode classNode, @SuppressWarnings("unused") Class<Entity> entityClass) { List<AnnotationNode> annotations = classNode.getAnnotations(); ClassNode annotationClassNode = new ClassNode(Entity.class); AnnotationNode annotationToAdd = new AnnotationNode(annotationClassNode); if (annotations.isEmpty()) { classNode.addAnnotation(annotationToAdd); } else { boolean foundAnn = findAnnotation(annotationClassNode, annotations) != null; if (!foundAnn) classNode.addAnnotation(annotationToAdd); } } public static AnnotationNode findAnnotation( ClassNode annotationClassNode, List<AnnotationNode> annotations) { for (AnnotationNode annotation : annotations) { if (annotation.getClassNode().equals(annotationClassNode)) { return annotation; } } return null; } public static AnnotationNode findAnnotation(ClassNode classNode, Class type) { List<AnnotationNode> annotations = classNode.getAnnotations(); if (annotations != null) { return findAnnotation(new ClassNode(type), annotations); } return null; } /** * Returns true if classNode is marked with annotationClass * * @param classNode A ClassNode to inspect * @param annotationClass an annotation to look for * @return true if classNode is marked with annotationClass, otherwise false */ public static boolean hasAnnotation( final ClassNode classNode, final Class<? extends Annotation> annotationClass) { List<AnnotationNode> annotations = classNode.getAnnotations(new ClassNode(annotationClass)); return annotations.size() > 0; } /** * @param classNode a ClassNode to search * @param annotationsToLookFor Annotations to look for * @return true if classNode is marked with any of the annotations in annotationsToLookFor */ public static boolean hasAnyAnnotations( final ClassNode classNode, final Class<? extends Annotation>... annotationsToLookFor) { return CollectionUtils.exists( Arrays.asList(annotationsToLookFor), new Predicate() { @SuppressWarnings({"unchecked", "rawtypes"}) public boolean evaluate(Object object) { return hasAnnotation(classNode, (Class) object); } }); } public static void addMethodIfNotPresent(ClassNode controllerClassNode, MethodNode methodNode) { MethodNode existing = controllerClassNode.getMethod(methodNode.getName(), methodNode.getParameters()); if (existing == null) { controllerClassNode.addMethod(methodNode); } } public static ExpressionStatement createPrintlnStatement(String message) { return new ExpressionStatement( new MethodCallExpression( AbstractGrailsArtefactTransformer.THIS_EXPRESSION, "println", new ArgumentListExpression(new ConstantExpression(message)))); } public static ExpressionStatement createPrintlnStatement(String message, String variable) { return new ExpressionStatement( new MethodCallExpression( AbstractGrailsArtefactTransformer.THIS_EXPRESSION, "println", new ArgumentListExpression( new BinaryExpression( new ConstantExpression(message), Token.newSymbol(Types.PLUS, 0, 0), new VariableExpression(variable))))); } /** * Wraps a method body in try / catch logic that catches any errors and logs an error, but does * not rethrow! * * @param methodNode The method node */ public static void wrapMethodBodyInTryCatchDebugStatements(MethodNode methodNode) { BlockStatement code = (BlockStatement) methodNode.getCode(); BlockStatement newCode = new BlockStatement(); TryCatchStatement tryCatchStatement = new TryCatchStatement(code, new BlockStatement()); newCode.addStatement(tryCatchStatement); methodNode.setCode(newCode); BlockStatement catchBlock = new BlockStatement(); ArgumentListExpression logArguments = new ArgumentListExpression(); logArguments.addExpression( new BinaryExpression( new ConstantExpression("Error initializing class: "), Token.newSymbol(Types.PLUS, 0, 0), new VariableExpression("e"))); logArguments.addExpression(new VariableExpression("e")); catchBlock.addStatement( new ExpressionStatement( new MethodCallExpression(new VariableExpression("log"), "error", logArguments))); tryCatchStatement.addCatch( new CatchStatement(new Parameter(new ClassNode(Throwable.class), "e"), catchBlock)); } /** * Evaluates a constraints closure and returns metadata about the constraints configured in the * closure. The Map returned has property names as keys and the value associated with each of * those property names is a Map<String, Expression> which has constraint names as keys and the * Expression associated with that constraint as values * * @param closureExpression the closure expression to evaluate * @return the Map as described above */ public static Map<String, Map<String, Expression>> getConstraintMetadata( final ClosureExpression closureExpression) { final List<MethodCallExpression> methodExpressions = new ArrayList<MethodCallExpression>(); final Map<String, Map<String, Expression>> results = new LinkedHashMap<String, Map<String, Expression>>(); final Statement closureCode = closureExpression.getCode(); if (closureCode instanceof BlockStatement) { final List<Statement> closureStatements = ((BlockStatement) closureCode).getStatements(); for (final Statement closureStatement : closureStatements) { if (closureStatement instanceof ExpressionStatement) { final Expression expression = ((ExpressionStatement) closureStatement).getExpression(); if (expression instanceof MethodCallExpression) { methodExpressions.add((MethodCallExpression) expression); } } else if (closureStatement instanceof ReturnStatement) { final ReturnStatement returnStatement = (ReturnStatement) closureStatement; Expression expression = returnStatement.getExpression(); if (expression instanceof MethodCallExpression) { methodExpressions.add((MethodCallExpression) expression); } } for (final MethodCallExpression methodCallExpression : methodExpressions) { final Expression objectExpression = methodCallExpression.getObjectExpression(); if (objectExpression instanceof VariableExpression && "this".equals(((VariableExpression) objectExpression).getName())) { final Expression methodCallArguments = methodCallExpression.getArguments(); if (methodCallArguments instanceof TupleExpression) { final List<Expression> methodCallArgumentExpressions = ((TupleExpression) methodCallArguments).getExpressions(); if (methodCallArgumentExpressions != null && methodCallArgumentExpressions.size() == 1 && methodCallArgumentExpressions.get(0) instanceof NamedArgumentListExpression) { final Map<String, Expression> constraintNameToExpression = new LinkedHashMap<String, Expression>(); final List<MapEntryExpression> mapEntryExpressions = ((NamedArgumentListExpression) methodCallArgumentExpressions.get(0)) .getMapEntryExpressions(); for (final MapEntryExpression mapEntryExpression : mapEntryExpressions) { final Expression keyExpression = mapEntryExpression.getKeyExpression(); if (keyExpression instanceof ConstantExpression) { final Object value = ((ConstantExpression) keyExpression).getValue(); if (value instanceof String) { constraintNameToExpression.put( (String) value, mapEntryExpression.getValueExpression()); } } } results.put(methodCallExpression.getMethodAsString(), constraintNameToExpression); } } } } } } return results; } @Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.SOURCE) private static @interface GrailsDelegatingConstructor {} }
/** * Handy methods when working with the Groovy AST * * @author Guillaume Laforge * @author Paul King * @author Andre Steingress * @author Graeme Rocher */ public class GeneralUtils { public static final Token ASSIGN = Token.newSymbol(Types.ASSIGN, -1, -1); public static final Token EQ = Token.newSymbol(Types.COMPARE_EQUAL, -1, -1); public static final Token NE = Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1); public static final Token LT = Token.newSymbol(Types.COMPARE_LESS_THAN, -1, -1); public static final Token AND = Token.newSymbol(Types.LOGICAL_AND, -1, -1); public static final Token OR = Token.newSymbol(Types.LOGICAL_OR, -1, -1); public static final Token CMP = Token.newSymbol(Types.COMPARE_TO, -1, -1); private static final Token INSTANCEOF = Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1); private static final Token PLUS = Token.newSymbol(Types.PLUS, -1, -1); private static final Token INDEX = Token.newSymbol("[", -1, -1); public static BinaryExpression andX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, AND, rhv); } public static ArgumentListExpression args(Expression... expressions) { List<Expression> args = new ArrayList<Expression>(); Collections.addAll(args, expressions); return new ArgumentListExpression(args); } public static ArgumentListExpression args(List<Expression> expressions) { return new ArgumentListExpression(expressions); } public static ArgumentListExpression args(Parameter[] parameters) { return new ArgumentListExpression(parameters); } public static ArgumentListExpression args(String... names) { List<Expression> vars = new ArrayList<Expression>(); for (String name : names) { vars.add(varX(name)); } return new ArgumentListExpression(vars); } public static Statement assignS(Expression target, Expression value) { return new ExpressionStatement(assignX(target, value)); } public static Expression assignX(Expression target, Expression value) { return new BinaryExpression(target, ASSIGN, value); } public static Expression attrX(Expression oe, Expression prop) { return new AttributeExpression(oe, prop); } public static BlockStatement block(VariableScope varScope, Statement... stmts) { BlockStatement block = new BlockStatement(); block.setVariableScope(varScope); for (Statement stmt : stmts) block.addStatement(stmt); return block; } public static BlockStatement block(Statement... stmts) { BlockStatement block = new BlockStatement(); for (Statement stmt : stmts) block.addStatement(stmt); return block; } public static MethodCallExpression callSuperX(String methodName, Expression args) { return callX(varX("super"), methodName, args); } public static MethodCallExpression callSuperX(String methodName) { return callSuperX(methodName, MethodCallExpression.NO_ARGUMENTS); } public static MethodCallExpression callThisX(String methodName, Expression args) { return callX(varX("this"), methodName, args); } public static MethodCallExpression callThisX(String methodName) { return callThisX(methodName, MethodCallExpression.NO_ARGUMENTS); } public static MethodCallExpression callX( Expression receiver, String methodName, Expression args) { return new MethodCallExpression(receiver, methodName, args); } public static MethodCallExpression callX( Expression receiver, Expression method, Expression args) { return new MethodCallExpression(receiver, method, args); } public static MethodCallExpression callX(Expression receiver, String methodName) { return callX(receiver, methodName, MethodCallExpression.NO_ARGUMENTS); } public static StaticMethodCallExpression callX( ClassNode receiver, String methodName, Expression args) { return new StaticMethodCallExpression(receiver, methodName, args); } public static StaticMethodCallExpression callX(ClassNode receiver, String methodName) { return callX(receiver, methodName, MethodCallExpression.NO_ARGUMENTS); } public static CastExpression castX(ClassNode type, Expression expression) { return new CastExpression(type, expression); } public static CastExpression castX( ClassNode type, Expression expression, boolean ignoreAutoboxing) { return new CastExpression(type, expression, ignoreAutoboxing); } public static ClassExpression classX(ClassNode clazz) { return new ClassExpression(clazz); } public static ClassExpression classX(Class clazz) { return classX(ClassHelper.make(clazz).getPlainNodeReference()); } public static ClosureExpression closureX(Parameter[] params, Statement code) { return new ClosureExpression(params, code); } public static ClosureExpression closureX(Statement code) { return closureX(Parameter.EMPTY_ARRAY, code); } public static Parameter[] cloneParams(Parameter[] source) { Parameter[] result = new Parameter[source.length]; for (int i = 0; i < source.length; i++) { Parameter srcParam = source[i]; Parameter dstParam = new Parameter(srcParam.getOriginType(), srcParam.getName()); result[i] = dstParam; } return result; } public static BinaryExpression cmpX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, CMP, rhv); } public static ConstantExpression constX(Object val) { return new ConstantExpression(val); } public static ConstantExpression constX(Object val, boolean keepPrimitive) { return new ConstantExpression(val, keepPrimitive); } /** * Copies all <tt>candidateAnnotations</tt> with retention policy {@link * java.lang.annotation.RetentionPolicy#RUNTIME} and {@link * java.lang.annotation.RetentionPolicy#CLASS}. * * <p>Annotations with {@link org.codehaus.groovy.runtime.GeneratedClosure} members are not * supported at present. */ public static void copyAnnotatedNodeAnnotations( final AnnotatedNode annotatedNode, final List<AnnotationNode> copied, List<AnnotationNode> notCopied) { List<AnnotationNode> annotationList = annotatedNode.getAnnotations(); for (AnnotationNode annotation : annotationList) { List<AnnotationNode> annotations = annotation.getClassNode().getAnnotations(AbstractASTTransformation.RETENTION_CLASSNODE); if (annotations.isEmpty()) continue; if (hasClosureMember(annotation)) { notCopied.add(annotation); continue; } AnnotationNode retentionPolicyAnnotation = annotations.get(0); Expression valueExpression = retentionPolicyAnnotation.getMember("value"); if (!(valueExpression instanceof PropertyExpression)) continue; PropertyExpression propertyExpression = (PropertyExpression) valueExpression; boolean processAnnotation = propertyExpression.getProperty() instanceof ConstantExpression && ("RUNTIME" .equals(((ConstantExpression) (propertyExpression.getProperty())).getValue()) || "CLASS" .equals( ((ConstantExpression) (propertyExpression.getProperty())).getValue())); if (processAnnotation) { AnnotationNode newAnnotation = new AnnotationNode(annotation.getClassNode()); for (Map.Entry<String, Expression> member : annotation.getMembers().entrySet()) { newAnnotation.addMember(member.getKey(), member.getValue()); } newAnnotation.setSourcePosition(annotatedNode); copied.add(newAnnotation); } } } public static Statement createConstructorStatementDefault(FieldNode fNode) { final String name = fNode.getName(); final ClassNode fType = fNode.getType(); final Expression fieldExpr = propX(varX("this"), name); Expression initExpr = fNode.getInitialValueExpression(); Statement assignInit; if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) { if (ClassHelper.isPrimitiveType(fType)) { assignInit = EmptyStatement.INSTANCE; } else { assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); } } else { assignInit = assignS(fieldExpr, initExpr); } fNode.setInitialValueExpression(null); Expression value = findArg(name); return ifElseS(equalsNullX(value), assignInit, assignS(fieldExpr, castX(fType, value))); } public static ConstructorCallExpression ctorX(ClassNode type, Expression args) { return new ConstructorCallExpression(type, args); } public static ConstructorCallExpression ctorX(ClassNode type) { return new ConstructorCallExpression(type, ArgumentListExpression.EMPTY_ARGUMENTS); } public static Statement ctorSuperS(Expression args) { return stmt(ctorX(ClassNode.SUPER, args)); } public static Statement ctorThisS(Expression args) { return stmt(ctorX(ClassNode.THIS, args)); } public static Statement ctorSuperS() { return stmt(ctorX(ClassNode.SUPER)); } public static Statement ctorThisS() { return stmt(ctorX(ClassNode.THIS)); } public static Statement declS(Expression target, Expression init) { return new ExpressionStatement(new DeclarationExpression(target, ASSIGN, init)); } public static BinaryExpression eqX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, EQ, rhv); } public static BooleanExpression equalsNullX(Expression argExpr) { return new BooleanExpression(eqX(argExpr, new ConstantExpression(null))); } public static FieldExpression fieldX(FieldNode fieldNode) { return new FieldExpression(fieldNode); } public static FieldExpression fieldX(ClassNode owner, String fieldName) { return new FieldExpression(owner.getField(fieldName)); } public static Expression findArg(String argName) { return new PropertyExpression(new VariableExpression("args"), argName); } public static List<MethodNode> getAllMethods(ClassNode type) { ClassNode node = type; List<MethodNode> result = new ArrayList<MethodNode>(); while (node != null) { result.addAll(node.getMethods()); node = node.getSuperClass(); } return result; } public static List<PropertyNode> getAllProperties(ClassNode type) { ClassNode node = type; List<PropertyNode> result = new ArrayList<PropertyNode>(); while (node != null) { result.addAll(node.getProperties()); node = node.getSuperClass(); } return result; } public static String getGetterName(PropertyNode pNode) { return "get" + Verifier.capitalize(pNode.getName()); } public static List<FieldNode> getInstanceNonPropertyFields(ClassNode cNode) { final List<FieldNode> result = new ArrayList<FieldNode>(); for (FieldNode fNode : cNode.getFields()) { if (!fNode.isStatic() && cNode.getProperty(fNode.getName()) == null) { result.add(fNode); } } return result; } public static List<String> getInstanceNonPropertyFieldNames(ClassNode cNode) { List<FieldNode> fList = getInstanceNonPropertyFields(cNode); List<String> result = new ArrayList<String>(fList.size()); for (FieldNode fNode : fList) { result.add(fNode.getName()); } return result; } public static List<PropertyNode> getInstanceProperties(ClassNode cNode) { final List<PropertyNode> result = new ArrayList<PropertyNode>(); for (PropertyNode pNode : cNode.getProperties()) { if (!pNode.isStatic()) { result.add(pNode); } } return result; } public static List<String> getInstancePropertyNames(ClassNode cNode) { List<PropertyNode> pList = BeanUtils.getAllProperties(cNode, false, false, true); List<String> result = new ArrayList<String>(pList.size()); for (PropertyNode pNode : pList) { result.add(pNode.getName()); } return result; } public static List<FieldNode> getInstancePropertyFields(ClassNode cNode) { final List<FieldNode> result = new ArrayList<FieldNode>(); for (PropertyNode pNode : cNode.getProperties()) { if (!pNode.isStatic()) { result.add(pNode.getField()); } } return result; } public static Set<ClassNode> getInterfacesAndSuperInterfaces(ClassNode type) { Set<ClassNode> res = new HashSet<ClassNode>(); if (type.isInterface()) { res.add(type); return res; } ClassNode next = type; while (next != null) { Collections.addAll(res, next.getInterfaces()); next = next.getSuperClass(); } return res; } public static List<FieldNode> getSuperNonPropertyFields(ClassNode cNode) { final List<FieldNode> result; if (cNode == ClassHelper.OBJECT_TYPE) { result = new ArrayList<FieldNode>(); } else { result = getSuperNonPropertyFields(cNode.getSuperClass()); } for (FieldNode fNode : cNode.getFields()) { if (!fNode.isStatic() && cNode.getProperty(fNode.getName()) == null) { result.add(fNode); } } return result; } public static List<FieldNode> getSuperPropertyFields(ClassNode cNode) { final List<FieldNode> result; if (cNode == ClassHelper.OBJECT_TYPE) { result = new ArrayList<FieldNode>(); } else { result = getSuperPropertyFields(cNode.getSuperClass()); } for (PropertyNode pNode : cNode.getProperties()) { if (!pNode.isStatic()) { result.add(pNode.getField()); } } return result; } public static BinaryExpression hasClassX(Expression instance, ClassNode cNode) { return eqX(classX(cNode), callX(instance, "getClass")); } private static boolean hasClosureMember(AnnotationNode annotation) { Map<String, Expression> members = annotation.getMembers(); for (Map.Entry<String, Expression> member : members.entrySet()) { if (member.getValue() instanceof ClosureExpression) return true; if (member.getValue() instanceof ClassExpression) { ClassExpression classExpression = (ClassExpression) member.getValue(); Class<?> typeClass = classExpression.getType().isResolved() ? classExpression.getType().redirect().getTypeClass() : null; if (typeClass != null && GeneratedClosure.class.isAssignableFrom(typeClass)) return true; } } return false; } public static boolean hasDeclaredMethod(ClassNode cNode, String name, int argsCount) { List<MethodNode> ms = cNode.getDeclaredMethods(name); for (MethodNode m : ms) { Parameter[] paras = m.getParameters(); if (paras != null && paras.length == argsCount) { return true; } } return false; } public static BinaryExpression hasEqualFieldX(FieldNode fNode, Expression other) { return eqX(varX(fNode), propX(other, fNode.getName())); } public static BinaryExpression hasEqualPropertyX( ClassNode annotatedNode, PropertyNode pNode, VariableExpression other) { return eqX(getterThisX(annotatedNode, pNode), getterX(other.getOriginType(), other, pNode)); } @Deprecated public static BinaryExpression hasEqualPropertyX(PropertyNode pNode, Expression other) { String getterName = getGetterName(pNode); return eqX(callThisX(getterName), callX(other, getterName)); } public static BooleanExpression hasSameFieldX(FieldNode fNode, Expression other) { return sameX(varX(fNode), propX(other, fNode.getName())); } public static BooleanExpression hasSamePropertyX(PropertyNode pNode, Expression other) { String getterName = getGetterName(pNode); return sameX(callThisX(getterName), callX(other, getterName)); } public static Statement ifElseS(Expression cond, Statement thenStmt, Statement elseStmt) { return new IfStatement( cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), thenStmt, elseStmt); } public static Statement ifS(Expression cond, Expression trueExpr) { return ifS(cond, new ExpressionStatement(trueExpr)); } public static Statement ifS(Expression cond, Statement trueStmt) { return new IfStatement( cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), trueStmt, EmptyStatement.INSTANCE); } public static Expression indexX(Expression target, Expression value) { return new BinaryExpression(target, INDEX, value); } public static BooleanExpression isInstanceOfX(Expression objectExpression, ClassNode cNode) { return new BooleanExpression(new BinaryExpression(objectExpression, INSTANCEOF, classX(cNode))); } public static BooleanExpression isOneX(Expression expr) { return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(1))); } public static boolean isOrImplements(ClassNode type, ClassNode interfaceType) { return type.equals(interfaceType) || type.implementsInterface(interfaceType); } public static BooleanExpression isTrueX(Expression argExpr) { return new BooleanExpression( new BinaryExpression(argExpr, EQ, new ConstantExpression(Boolean.TRUE))); } public static BooleanExpression isZeroX(Expression expr) { return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(0))); } public static BinaryExpression ltX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, LT, rhv); } public static String makeDescriptorWithoutReturnType(MethodNode mn) { StringBuilder sb = new StringBuilder(); sb.append(mn.getName()).append(':'); for (Parameter p : mn.getParameters()) { sb.append(p.getType()).append(','); } return sb.toString(); } public static BinaryExpression neX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, NE, rhv); } public static BooleanExpression notNullX(Expression argExpr) { return new BooleanExpression(new BinaryExpression(argExpr, NE, new ConstantExpression(null))); } public static NotExpression notX(Expression expr) { return new NotExpression( expr instanceof BooleanExpression ? expr : new BooleanExpression(expr)); } public static BinaryExpression orX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, OR, rhv); } public static Parameter param(ClassNode type, String name) { return param(type, name, null); } public static Parameter param(ClassNode type, String name, Expression initialExpression) { Parameter param = new Parameter(type, name); if (initialExpression != null) { param.setInitialExpression(initialExpression); } return param; } public static Parameter[] params(Parameter... params) { return params != null ? params : Parameter.EMPTY_ARRAY; } public static BinaryExpression plusX(Expression lhv, Expression rhv) { return new BinaryExpression(lhv, PLUS, rhv); } public static Expression propX(Expression owner, String property) { return new PropertyExpression(owner, property); } public static Expression propX(Expression owner, Expression property) { return new PropertyExpression(owner, property); } public static Statement returnS(Expression expr) { return new ReturnStatement(new ExpressionStatement(expr)); } public static Statement safeExpression(Expression fieldExpr, Expression expression) { return new IfStatement( equalsNullX(fieldExpr), new ExpressionStatement(fieldExpr), new ExpressionStatement(expression)); } public static BooleanExpression sameX(Expression self, Expression other) { return new BooleanExpression(callX(self, "is", args(other))); } public static Statement stmt(Expression expr) { return new ExpressionStatement(expr); } public static TernaryExpression ternaryX( Expression cond, Expression trueExpr, Expression elseExpr) { return new TernaryExpression( cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), trueExpr, elseExpr); } public static VariableExpression varX(String name) { return new VariableExpression(name); } public static VariableExpression varX(Variable variable) { return new VariableExpression(variable); } public static VariableExpression varX(String name, ClassNode type) { return new VariableExpression(name, type); } /** * This method is similar to {@link #propX(Expression, Expression)} but will make sure that if the * property being accessed is defined inside the classnode provided as a parameter, then a getter * call is generated instead of a field access. * * @param annotatedNode the class node where the property node is accessed from * @param pNode the property being accessed * @return a method call expression or a property expression */ public static Expression getterThisX(ClassNode annotatedNode, PropertyNode pNode) { ClassNode owner = pNode.getDeclaringClass(); if (annotatedNode.equals(owner)) { String getterName = "get" + MetaClassHelper.capitalize(pNode.getName()); boolean existingExplicitGetter = annotatedNode.getMethod(getterName, Parameter.EMPTY_ARRAY) != null; if (ClassHelper.boolean_TYPE.equals(pNode.getOriginType()) && !existingExplicitGetter) { getterName = "is" + MetaClassHelper.capitalize(pNode.getName()); } return callThisX(getterName); } return propX(new VariableExpression("this"), pNode.getName()); } /** * This method is similar to {@link #propX(Expression, Expression)} but will make sure that if the * property being accessed is defined inside the classnode provided as a parameter, then a getter * call is generated instead of a field access. * * @param annotatedNode the class node where the property node is accessed from * @param receiver the object having the property * @param pNode the property being accessed * @return a method call expression or a property expression */ public static Expression getterX( ClassNode annotatedNode, Expression receiver, PropertyNode pNode) { ClassNode owner = pNode.getDeclaringClass(); if (annotatedNode.equals(owner)) { String getterName = "get" + MetaClassHelper.capitalize(pNode.getName()); boolean existingExplicitGetter = annotatedNode.getMethod(getterName, Parameter.EMPTY_ARRAY) != null; if (ClassHelper.boolean_TYPE.equals(pNode.getOriginType()) && !existingExplicitGetter) { getterName = "is" + MetaClassHelper.capitalize(pNode.getName()); } return callX(receiver, getterName); } return propX(receiver, pNode.getName()); } }
Expression transformBinaryExpression(final BinaryExpression bin) { if (bin instanceof DeclarationExpression) { Expression optimized = transformDeclarationExpression(bin); if (optimized != null) { return optimized; } } Object[] list = bin.getNodeMetaData(BINARY_EXP_TARGET); Token operation = bin.getOperation(); int operationType = operation.getType(); Expression rightExpression = bin.getRightExpression(); Expression leftExpression = bin.getLeftExpression(); if (bin instanceof DeclarationExpression && leftExpression instanceof VariableExpression) { ClassNode declarationType = ((VariableExpression) leftExpression).getOriginType(); if (rightExpression instanceof ConstantExpression) { ClassNode unwrapper = ClassHelper.getUnwrapper(declarationType); ClassNode wrapper = ClassHelper.getWrapper(declarationType); if (!rightExpression.getType().equals(declarationType) && wrapper.isDerivedFrom(ClassHelper.Number_TYPE) && WideningCategories.isDoubleCategory(unwrapper)) { ConstantExpression constant = (ConstantExpression) rightExpression; if (constant.getValue() != null) { return optimizeConstantInitialization( bin, operation, constant, leftExpression, declarationType); } } } } if (operationType == Types.EQUAL && leftExpression instanceof PropertyExpression) { MethodNode directMCT = leftExpression.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); if (directMCT != null) { return transformPropertyAssignmentToSetterCall( (PropertyExpression) leftExpression, rightExpression, directMCT); } } if (operationType == Types.COMPARE_EQUAL || operationType == Types.COMPARE_NOT_EQUAL) { // let's check if one of the operands is the null constant CompareToNullExpression compareToNullExpression = null; if (isNullConstant(leftExpression)) { compareToNullExpression = new CompareToNullExpression( staticCompilationTransformer.transform(rightExpression), operationType == Types.COMPARE_EQUAL); } else if (isNullConstant(rightExpression)) { compareToNullExpression = new CompareToNullExpression( staticCompilationTransformer.transform(leftExpression), operationType == Types.COMPARE_EQUAL); } if (compareToNullExpression != null) { compareToNullExpression.setSourcePosition(bin); return compareToNullExpression; } } else if (operationType == Types.KEYWORD_IN) { return convertInOperatorToTernary(bin, rightExpression, leftExpression); } if (list != null) { if (operationType == Types.COMPARE_TO) { StaticTypesTypeChooser typeChooser = staticCompilationTransformer.getTypeChooser(); ClassNode classNode = staticCompilationTransformer.getClassNode(); ClassNode leftType = typeChooser.resolveType(leftExpression, classNode); if (leftType.implementsInterface(ClassHelper.COMPARABLE_TYPE)) { ClassNode rightType = typeChooser.resolveType(rightExpression, classNode); if (rightType.implementsInterface(ClassHelper.COMPARABLE_TYPE)) { Expression left = staticCompilationTransformer.transform(leftExpression); Expression right = staticCompilationTransformer.transform(rightExpression); MethodCallExpression call = new MethodCallExpression(left, "compareTo", new ArgumentListExpression(right)); call.setImplicitThis(false); call.setMethodTarget(COMPARE_TO_METHOD); CompareIdentityExpression compareIdentity = new CompareIdentityExpression(left, right); compareIdentity.putNodeMetaData( StaticTypesMarker.INFERRED_RETURN_TYPE, ClassHelper.boolean_TYPE); TernaryExpression result = new TernaryExpression( new BooleanExpression(compareIdentity), // a==b CONSTANT_ZERO, new TernaryExpression( new BooleanExpression(new CompareToNullExpression(left, true)), // a==null CONSTANT_MINUS_ONE, new TernaryExpression( new BooleanExpression( new CompareToNullExpression(right, true)), // b==null CONSTANT_ONE, call))); compareIdentity.putNodeMetaData( StaticTypesMarker.INFERRED_RETURN_TYPE, ClassHelper.int_TYPE); result.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, ClassHelper.int_TYPE); TernaryExpression expr = (TernaryExpression) result.getFalseExpression(); expr.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, ClassHelper.int_TYPE); expr.getFalseExpression() .putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, ClassHelper.int_TYPE); return result; } } } boolean isAssignment = StaticTypeCheckingSupport.isAssignment(operationType); MethodCallExpression call; MethodNode node = (MethodNode) list[0]; String name = (String) list[1]; Expression left = staticCompilationTransformer.transform(leftExpression); Expression right = staticCompilationTransformer.transform(rightExpression); BinaryExpression optimized = tryOptimizeCharComparison(left, right, bin); if (optimized != null) { optimized.removeNodeMetaData(BINARY_EXP_TARGET); return transformBinaryExpression(optimized); } call = new MethodCallExpression(left, name, new ArgumentListExpression(right)); call.setImplicitThis(false); call.setMethodTarget(node); MethodNode adapter = StaticCompilationTransformer.BYTECODE_BINARY_ADAPTERS.get(operationType); if (adapter != null) { ClassExpression sba = new ClassExpression(StaticCompilationTransformer.BYTECODE_ADAPTER_CLASS); // replace with compareEquals call = new MethodCallExpression(sba, "compareEquals", new ArgumentListExpression(left, right)); call.setMethodTarget(adapter); call.setImplicitThis(false); } if (!isAssignment) return call; // case of +=, -=, /=, ... // the method represents the operation type only, and we must add an assignment return new BinaryExpression( left, Token.newSymbol("=", operation.getStartLine(), operation.getStartColumn()), call); } if (bin.getOperation().getType() == Types.EQUAL && leftExpression instanceof TupleExpression && rightExpression instanceof ListExpression) { // multiple assignment ListOfExpressionsExpression cle = new ListOfExpressionsExpression(); boolean isDeclaration = bin instanceof DeclarationExpression; List<Expression> leftExpressions = ((TupleExpression) leftExpression).getExpressions(); List<Expression> rightExpressions = ((ListExpression) rightExpression).getExpressions(); Iterator<Expression> leftIt = leftExpressions.iterator(); Iterator<Expression> rightIt = rightExpressions.iterator(); if (isDeclaration) { while (leftIt.hasNext()) { Expression left = leftIt.next(); if (rightIt.hasNext()) { Expression right = rightIt.next(); BinaryExpression bexp = new DeclarationExpression(left, bin.getOperation(), right); bexp.setSourcePosition(right); cle.addExpression(bexp); } } } else { // (next, result) = [ result, next+result ] // --> // def tmp1 = result // def tmp2 = next+result // next = tmp1 // result = tmp2 int size = rightExpressions.size(); List<Expression> tmpAssignments = new ArrayList<Expression>(size); List<Expression> finalAssignments = new ArrayList<Expression>(size); for (int i = 0; i < Math.min(size, leftExpressions.size()); i++) { Expression left = leftIt.next(); Expression right = rightIt.next(); VariableExpression tmpVar = new VariableExpression("$tmpVar$" + tmpVarCounter++); BinaryExpression bexp = new DeclarationExpression(tmpVar, bin.getOperation(), right); bexp.setSourcePosition(right); tmpAssignments.add(bexp); bexp = new BinaryExpression(left, bin.getOperation(), new VariableExpression(tmpVar)); bexp.setSourcePosition(left); finalAssignments.add(bexp); } for (Expression tmpAssignment : tmpAssignments) { cle.addExpression(tmpAssignment); } for (Expression finalAssignment : finalAssignments) { cle.addExpression(finalAssignment); } } return staticCompilationTransformer.transform(cle); } return staticCompilationTransformer.superTransform(bin); }