void visit()

in grails-datamapping-core/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy [151:378]


    void visit(ClassNode classNode, SourceUnit sourceUnit) {
        if (classNode.getNodeMetaData(AstUtils.TRANSFORM_APPLIED_MARKER) == APPLIED_MARKER) {
            return
        }

        if ((classNode instanceof InnerClassNode) || classNode.isEnum()) {
            // do not apply transform to enums or inner classes
            return
        }

        if (hasAnnotation(classNode, ClassHelper.make(Embeddable))) {
            // do not apply transform to embeddedable JPA classes
            return
        }

        final boolean isJpaEntity = hasAnnotation(classNode, JPA_ENTITY_CLASS_NODE)

        AstUtils.addTransformedEntityName(classNode.name)
        // Add the entity annotation and enable generic replacement
        classNode.setUsingGenerics(true)

        if (!isJpaEntity) {
            AstUtils.addAnnotationIfNecessary(classNode, Entity.class)
            try {
                AstUtils.addAnnotationIfNecessary(classNode, (Class<? extends Annotation>) getClass().classLoader.loadClass('grails.persistence.Entity'))
            } catch (Throwable e) {
                try {
                    def cl = Thread.currentThread().contextClassLoader
                    AstUtils.addAnnotationIfNecessary(classNode, (Class<? extends Annotation>) Class.forName('grails.persistence.Entity', true, cl))
                } catch (Throwable e2) {
                    // Only GORM classes on the classpath continue
                }
            }
        }

        // Add the Jackson @JsonIgnoreProperties if Jackson is present
        // Add @JsonIgnoreProperties(['dirtyPropertyNames', 'errors', 'dirty', 'attached', 'version'])
        if (ClassUtils.isPresent("com.fasterxml.jackson.annotation.JsonIgnoreProperties")) {
            AnnotationNode ignorePropertiesAnn = AstUtils.addAnnotationOrGetExisting(classNode, (Class<? extends Annotation>) getClass().classLoader.loadClass('com.fasterxml.jackson.annotation.JsonIgnoreProperties'))
            Expression existing = ignorePropertiesAnn.getMember("value")
            if (existing == null) {
                ignorePropertiesAnn.setMember("value", IGNORED_PROPERTIES)
            } else {
                if (existing instanceof ListExpression) {
                    ListExpression listExpression = (ListExpression) existing
                    for (exp in IGNORED_PROPERTIES.expressions) {
                        listExpression.addExpression(exp)
                    }
                }
            }
        }


        def rxEntityClassNode = AstUtils.findInterface(classNode, "grails.gorm.rx.RxEntity")
        boolean isRxEntity = rxEntityClassNode != null

        if (!isJpaEntity) {
            // Add default id
            injectIdProperty(classNode)
        }

        if (!isRxEntity && !isJpaEntity) {
            // Add version
            injectVersionProperty(classNode)
        }

        // inject toString()
        if (!isJpaEntity) {
            injectToStringMethod(classNode)
        }

        // inject the GORM entity trait unless it is an RX entity
        MethodNode addToMethodNode = ADD_TO_METHOD_NODE
        MethodNode removeFromMethodNode = REMOVE_FROM_METHOD_NODE
        MethodNode getAssociationMethodNode = GET_ASSOCIATION_ID_METHOD_NODE

        if (!isRxEntity) {
            def classGormEntityTrait = pickGormEntityTrait(classNode, sourceUnit)
            AstUtils.injectTrait(classNode, classGormEntityTrait)
        } else {
            addToMethodNode = rxEntityClassNode.getMethods("addTo").get(0)
            removeFromMethodNode = rxEntityClassNode.getMethods("removeFrom").get(0)
            getAssociationMethodNode = rxEntityClassNode.getMethods("getAssociationId").get(0)
        }

        // inject associations
        if (isJpaEntity) {
            injectAssociationsForJpaEntity(classNode, addToMethodNode, removeFromMethodNode, getAssociationMethodNode)
        } else {
            injectAssociations(classNode, addToMethodNode, removeFromMethodNode, getAssociationMethodNode)
        }

        // now apply dirty checking behavior
        def dirtyCheckTransformer = new DirtyCheckingTransformer()
        dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode, GormEntityDirtyCheckable)


        // convert the methodMissing and propertyMissing implementations to $static_methodMissing and $static_propertyMissing for the static versions
        def methodMissingBody = new BlockStatement()
        def methodNameParam = new Parameter(ClassHelper.make(String), "name")
        def methodArgsParam = new Parameter(AstUtils.OBJECT_CLASS_NODE, "args")
        def methodMissingArgs = new ArgumentListExpression(methodNameParam, methodArgsParam)
        def methodMissingMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticMethodMissing", methodMissingArgs)
        methodMissingBody.addStatement(
                new ExpressionStatement(methodMissingMethodCall)
        )

        def methodMissingParameters = [methodNameParam, methodArgsParam] as Parameter[]
        classNode.addMethod('$static_methodMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, methodMissingParameters, null, methodMissingBody)


        // $static_propertyMissing setter
        def propertyMissingSetBody = new BlockStatement()
        def propertyMissingSetNameParam = new Parameter(ClassHelper.make(String), "name")
        def propertyMissingSetValueParam = new Parameter(AstUtils.OBJECT_CLASS_NODE, "value")
        def propertyMissingSetArgs = new ArgumentListExpression(propertyMissingSetNameParam, propertyMissingSetValueParam)
        def propertyMissingSetMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticPropertyMissing", propertyMissingSetArgs)
        propertyMissingSetBody.addStatement(
                new ExpressionStatement(propertyMissingSetMethodCall)
        )
        def propertyMissingSetParameters = [propertyMissingSetNameParam, propertyMissingSetValueParam] as Parameter[]
        classNode.addMethod('$static_propertyMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, propertyMissingSetParameters, null, propertyMissingSetBody)

        // $static_propertyMissing getter
        def propertyMissingGetBody = new BlockStatement()
        def propertyMissingGetNameParam = new Parameter(ClassHelper.make(String), "name")
        def propertyMissingGetArgs = new ArgumentListExpression(propertyMissingGetNameParam)
        def propertyMissingGetMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticPropertyMissing", propertyMissingGetArgs)
        propertyMissingGetBody.addStatement(
                new ExpressionStatement(propertyMissingGetMethodCall)
        )
        def propertyMissingGetParameters = [propertyMissingGetNameParam] as Parameter[]
        classNode.addMethod('$static_propertyMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, propertyMissingGetParameters, null, propertyMissingGetBody)


        // now process named query associations
        // see https://grails.github.io/grails-doc/latest/ref/Domain%20Classes/namedQueries.html

        // for each method call create a named query proxy lookup
        def thisClassNode = classNode
        def namedQueriesProp = thisClassNode.getProperty(GormProperties.NAMED_QUERIES)
        def currentClassNode = classNode
        while (namedQueriesProp != null) {
            def expression = namedQueriesProp.getInitialExpression()
            if (expression instanceof ClosureExpression) {
                ClosureExpression ce = (ClosureExpression) expression
                def statement = ce.code
                if (statement instanceof BlockStatement) {
                    BlockStatement body = (BlockStatement) statement
                    def allStatements = body.statements
                    for (s in allStatements) {
                        if (s instanceof ExpressionStatement) {
                            ExpressionStatement es = (ExpressionStatement) s
                            if (es.expression instanceof MethodCallExpression) {
                                MethodCallExpression mce = (MethodCallExpression) es.expression
                                String methodName = mce.getMethodAsString()

                                Expression argsX = mce.arguments

                                if (argsX instanceof ArgumentListExpression) {
                                    ArgumentListExpression ale = (ArgumentListExpression) argsX
                                    Expression first = ale.expressions.size() == 1 ? ale.getExpression(0) : null
                                    if (first instanceof ClosureExpression) {
                                        ClosureExpression closureX = (ClosureExpression) first
                                        Parameter[] closureParams = closureX.parameters
                                        boolean hasParameters = closureParams != null && closureParams.length > 0
                                        Parameter[] newParams = hasParameters ? AstUtils.copyParameters(closureParams) : AstUtils.ZERO_PARAMETERS
                                        MethodNode existing = thisClassNode.getMethod(methodName, newParams)

                                        if (existing == null || !existing.getDeclaringClass().equals(thisClassNode)) {
                                            def queryOperationsClassNode = AstUtils.nonGeneric(ClassHelper.make(GormQueryOperations))
                                            final GenericsType[] genericsTypes = queryOperationsClassNode.getGenericsTypes()
                                            final Map<String, ClassNode> parameterNameToParameterValue = new LinkedHashMap<String, ClassNode>()
                                            if (genericsTypes != null) {
                                                for (GenericsType gt : genericsTypes) {
                                                    parameterNameToParameterValue.put(gt.getName(), thisClassNode.getPlainNodeReference());
                                                }
                                            }
                                            AstUtils.replaceGenericsPlaceholders(queryOperationsClassNode, parameterNameToParameterValue)

                                            def methodBody = new BlockStatement()
                                            ArgumentListExpression createNamedQueryArgs = args(classX(thisClassNode), constX(methodName))
                                            for (arg in args(newParams)) {
                                                createNamedQueryArgs.addExpression(arg)
                                            }
                                            MethodCallExpression createNamedQueryCall = callX(classX(GormEnhancer), CREATE_NAMED_QUERY, createNamedQueryArgs)
                                            List<MethodNode> createNamedQueryMethods = ClassHelper.makeCached(GormEnhancer).getMethods(CREATE_NAMED_QUERY)
                                            if (!createNamedQueryMethods.isEmpty()) {
                                                createNamedQueryCall.setMethodTarget(createNamedQueryMethods.find() { MethodNode mn -> hasParameters ? mn.parameters.length == 3 : mn.parameters.length == 2 })
                                            }
                                            methodBody.addStatement(returnS(createNamedQueryCall))
                                            MethodNode newMethod = new MethodNode(methodName, Modifier.PUBLIC | Modifier.STATIC, queryOperationsClassNode, newParams, null, methodBody)
                                            thisClassNode.addMethod(newMethod)

                                            if (!hasParameters) {

                                                String namedQueryGetter = NameUtils.getGetterName(methodName)
                                                existing = thisClassNode.getMethod(namedQueryGetter, AstUtils.ZERO_PARAMETERS)
                                                if (existing == null || !existing.getDeclaringClass().equals(thisClassNode)) {
                                                    newMethod = new MethodNode(namedQueryGetter, Modifier.PUBLIC | Modifier.STATIC, queryOperationsClassNode, AstUtils.ZERO_PARAMETERS, null, methodBody)
                                                    thisClassNode.addMethod(newMethod)
                                                }
                                            }
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
                currentClassNode = currentClassNode.getSuperClass()
                namedQueriesProp = currentClassNode.getProperty(GormProperties.NAMED_QUERIES)
            }

        }
        def additionalTransforms = ServiceLoader.load(AdditionalGormEntityTransformation, getClass().classLoader)
        for (additionalTransform in additionalTransforms) {
            if (additionalTransform.isAvailable()) {
                additionalTransform.visit(classNode, sourceUnit)
            }
        }

        if (compilationUnit != null && !isRxEntity) {
            org.codehaus.groovy.transform.trait.TraitComposer.doExtendTraits(classNode, sourceUnit, compilationUnit);
        }
        classNode.putNodeMetaData(AstUtils.TRANSFORM_APPLIED_MARKER, APPLIED_MARKER)
    }