From e1a990bfe69672599f4fcc19dd1568d7387729d5 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Mon, 28 Aug 2023 16:11:13 +0200 Subject: [PATCH] Fix processing order of introduced method in Java (#9784) --- .../websocket/QueryParamClientWebSocket.java | 3 +- .../groovy/TypeElementVisitorTransform.groovy | 71 +++-- .../PublicAbstractMethodVisitor.java | 2 + .../processing/PublicMethodVisitor.java | 2 + .../TypeElementVisitorProcessor.java | 251 ++++-------------- .../processing/visitor/JavaClassElement.java | 58 +++- .../beans/IntroducedBeanVisitorSpec.groovy | 218 +++++++++++++++ .../introduction/beans/MyRepoVisitor2.java | 22 ++ ...icronaut.inject.visitor.TypeElementVisitor | 1 + 9 files changed, 373 insertions(+), 255 deletions(-) create mode 100644 inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/IntroducedBeanVisitorSpec.groovy create mode 100644 inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/MyRepoVisitor2.java diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/websocket/QueryParamClientWebSocket.java b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/websocket/QueryParamClientWebSocket.java index f0655e3329f..1bf4c5d1f5b 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/websocket/QueryParamClientWebSocket.java +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/websocket/QueryParamClientWebSocket.java @@ -1,6 +1,7 @@ package io.micronaut.http.server.netty.websocket; import io.micronaut.http.HttpRequest; +import io.micronaut.http.annotation.QueryValue; import io.micronaut.websocket.WebSocketSession; import io.micronaut.websocket.annotation.ClientWebSocket; import io.micronaut.websocket.annotation.OnMessage; @@ -14,7 +15,7 @@ public class QueryParamClientWebSocket { private String dinner; @OnOpen - public void onOpen(String dinner, WebSocketSession session, HttpRequest request) { // <3> + public void onOpen(@QueryValue String dinner, WebSocketSession session, HttpRequest request) { // <3> this.session = session; this.request = request; this.dinner = dinner; diff --git a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/TypeElementVisitorTransform.groovy b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/TypeElementVisitorTransform.groovy index 44cdcc9e27d..c37ab48f67b 100644 --- a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/TypeElementVisitorTransform.groovy +++ b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/TypeElementVisitorTransform.groovy @@ -86,7 +86,7 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA continue } try { - def visitor = new ElementVisitor(source, compilationUnit, classNode, [loadedVisitor], visitorContext, targetClassElement) + def visitor = new ElementVisitor(source, compilationUnit, classNode, loadedVisitor, visitorContext, targetClassElement) visitor.visitClass(classNode) } catch (ProcessingException ex) { visitorContext.fail(ex.getMessage(), ex.getOriginatingElement() as ASTNode) @@ -111,7 +111,7 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA final CompilationUnit compilationUnit final GroovyVisitorContext visitorContext private final ClassNode concreteClass - private final Collection typeElementVisitors + private final LoadedVisitor visitor private ClassElement targetClassElement @@ -119,12 +119,12 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA SourceUnit sourceUnit, CompilationUnit compilationUnit, ClassNode targetClassNode, - Collection typeElementVisitors, + LoadedVisitor visitor, GroovyVisitorContext visitorContext, ClassElement targetClassElement) { this.targetClassElement = targetClassElement this.compilationUnit = compilationUnit - this.typeElementVisitors = typeElementVisitors + this.visitor = visitor this.concreteClass = targetClassNode this.sourceUnit = sourceUnit this.visitorContext = visitorContext @@ -134,10 +134,8 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA if ((targetClassElement as GroovyClassElement).getNativeType().annotatedNode() != node) { targetClassElement = visitorContext.getElementFactory().newSourceClassElement(node, visitorContext.getElementAnnotationMetadataFactory()) } - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesClass(targetClassElement)) { - it.getVisitor().visitClass(targetClassElement, visitorContext) - } + if (visitor.matchesClass(targetClassElement)) { + visitor.getVisitor().visitClass(targetClassElement, visitorContext) } GroovyClassElement classElement = targetClassElement as GroovyClassElement def properties = classElement.getSyntheticBeanProperties() @@ -148,7 +146,9 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA visitConstructor(cn) } for (MemberElement memberElement : classElement.getSourceEnclosedElements(ElementQuery.ALL_FIELD_AND_METHODS)) { - if (memberElement instanceof FieldElement) { + if (memberElement instanceof EnumConstantElement) { + visitEnumConstant(memberElement) + } else if (memberElement instanceof FieldElement) { visitField(memberElement) } else if (memberElement instanceof MethodElement) { visitMethod(memberElement) @@ -158,49 +158,38 @@ class TypeElementVisitorTransform implements ASTTransformation, CompilationUnitA } } - void visitConstructor(ConstructorNode node) { + private void visitConstructor(ConstructorNode node) { def e = visitorContext.getElementFactory() .newConstructorElement(targetClassElement, node, visitorContext.getElementAnnotationMetadataFactory()) - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesElement(e)) { - it.getVisitor().visitConstructor(e, visitorContext) - } + if (visitor.matchesElement(e)) { + visitor.getVisitor().visitConstructor(e, visitorContext) } } - void visitMethod(MethodElement e) { - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesElement(e)) { - it.getVisitor().visitMethod(e, visitorContext) - } + private void visitMethod(MethodElement e) { + if (visitor.matchesElement(e)) { + visitor.getVisitor().visitMethod(e, visitorContext) } } - void visitField(FieldElement fieldElement) { - if (fieldElement instanceof EnumConstantElement) { - EnumConstantElement enumConstantElement = fieldElement - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesElement(enumConstantElement)) { - it.getVisitor().visitEnumConstant(enumConstantElement, visitorContext) - } - } - } else { - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesElement(fieldElement)) { - it.getVisitor().visitField(fieldElement, visitorContext) - } - } + private void visitField(FieldElement fieldElement) { + if (visitor.matchesElement(fieldElement)) { + visitor.getVisitor().visitField(fieldElement, visitorContext) } } - void visitNativeProperty(PropertyElement propertyNode) { - for (LoadedVisitor it : typeElementVisitors) { - if (it.matchesElement(propertyNode)) { - propertyNode.field.ifPresent(f -> it.getVisitor().visitField(f, visitorContext)) - // visit synthetic getter/setter methods - propertyNode.writeMethod.ifPresent(m -> it.getVisitor().visitMethod(m, visitorContext)) - propertyNode.readMethod.ifPresent(m -> it.getVisitor().visitMethod(m, visitorContext)) - } + private void visitEnumConstant(EnumConstantElement enumConstantElement) { + if (visitor.matchesElement(enumConstantElement)) { + visitor.getVisitor().visitEnumConstant(enumConstantElement, visitorContext) + } + } + + private void visitNativeProperty(PropertyElement propertyNode) { + if (visitor.matchesElement(propertyNode)) { + propertyNode.field.ifPresent(f -> visitor.getVisitor().visitField(f, visitorContext)) + // visit synthetic getter/setter methods + propertyNode.writeMethod.ifPresent(m -> visitor.getVisitor().visitMethod(m, visitorContext)) + propertyNode.readMethod.ifPresent(m -> visitor.getVisitor().visitMethod(m, visitorContext)) } } } diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/PublicAbstractMethodVisitor.java b/inject-java/src/main/java/io/micronaut/annotation/processing/PublicAbstractMethodVisitor.java index 9979dff7f50..103eee9cd16 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/PublicAbstractMethodVisitor.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/PublicAbstractMethodVisitor.java @@ -33,7 +33,9 @@ * @author graemerocher * @see javax.lang.model.util.AbstractTypeVisitor8 * @since 1.0 + * @deprecated No longer used */ +@Deprecated(forRemoval = true) public abstract class PublicAbstractMethodVisitor extends PublicMethodVisitor { private final TypeElement classElement; diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/PublicMethodVisitor.java b/inject-java/src/main/java/io/micronaut/annotation/processing/PublicMethodVisitor.java index 381201f3dae..04943983b47 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/PublicMethodVisitor.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/PublicMethodVisitor.java @@ -31,7 +31,9 @@ * @author graemerocher * @see javax.lang.model.util.AbstractTypeVisitor8 * @since 1.0 + * @deprecated No longer used */ +@Deprecated(forRemoval = true) public abstract class PublicMethodVisitor extends SuperclassAwareTypeVisitor { /** diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/TypeElementVisitorProcessor.java b/inject-java/src/main/java/io/micronaut/annotation/processing/TypeElementVisitorProcessor.java index 5c11b696d1a..2f1c4f16c04 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/TypeElementVisitorProcessor.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/TypeElementVisitorProcessor.java @@ -19,10 +19,8 @@ import io.micronaut.annotation.processing.visitor.JavaElementFactory; import io.micronaut.annotation.processing.visitor.JavaNativeElement; import io.micronaut.annotation.processing.visitor.LoadedVisitor; -import io.micronaut.aop.Introduction; import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.Generated; -import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.io.service.SoftServiceLoader; @@ -30,12 +28,13 @@ import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.core.version.VersionUtils; -import io.micronaut.inject.processing.ProcessingException; import io.micronaut.inject.ast.ConstructorElement; +import io.micronaut.inject.ast.ElementQuery; import io.micronaut.inject.ast.EnumConstantElement; import io.micronaut.inject.ast.FieldElement; +import io.micronaut.inject.ast.MemberElement; import io.micronaut.inject.ast.MethodElement; -import io.micronaut.inject.processing.JavaModelUtils; +import io.micronaut.inject.processing.ProcessingException; import io.micronaut.inject.visitor.TypeElementVisitor; import io.micronaut.inject.visitor.VisitorContext; import io.micronaut.inject.writer.AbstractBeanDefinitionBuilder; @@ -43,14 +42,8 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedOptions; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementScanner8; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -65,8 +58,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static javax.lang.model.element.ElementKind.FIELD; - /** *

The annotation processed used to execute type element visitors.

* @@ -264,12 +255,9 @@ public boolean process(Set annotations, RoundEnvironment for (LoadedVisitor loadedVisitor : loadedVisitors) { for (JavaClassElement javaClassElement : javaClassElements) { try { - if (!loadedVisitor.matchesClass(javaClassElement)) { - continue; + if (loadedVisitor.matchesClass(javaClassElement)) { + visitClass(loadedVisitor, javaClassElement); } - TypeElement typeElement = javaClassElement.getNativeType().element(); - String className = typeElement.getQualifiedName().toString(); - typeElement.accept(new ElementVisitor(javaClassElement, typeElement, Collections.singletonList(loadedVisitor)), className); } catch (ProcessingException e) { JavaNativeElement originatingElement = (JavaNativeElement) e.getOriginatingElement(); if (originatingElement == null) { @@ -311,6 +299,49 @@ public boolean process(Set annotations, RoundEnvironment return false; } + private void visitClass(LoadedVisitor visitor, JavaClassElement classElement) { + visitor.getVisitor().visitClass(classElement, javaVisitorContext); + + for (ConstructorElement constructorElement : classElement.getSourceEnclosedElements(ElementQuery.CONSTRUCTORS)) { + visitConstructor(visitor, constructorElement); + } + for (MemberElement memberElement : classElement.getSourceEnclosedElements(ElementQuery.ALL_FIELD_AND_METHODS)) { + if (memberElement instanceof EnumConstantElement enumConstantElement) { + visitEnumConstant(visitor, enumConstantElement); + } else if (memberElement instanceof FieldElement fieldElement) { + visitField(visitor, fieldElement); + } else if (memberElement instanceof MethodElement methodElement) { + visitMethod(visitor, methodElement); + } else { + throw new IllegalStateException("Unknown element: " + memberElement); + } + } + } + + private void visitConstructor(LoadedVisitor visitor, ConstructorElement constructorElement) { + if (visitor.matchesElement(constructorElement)) { + visitor.getVisitor().visitConstructor(constructorElement, javaVisitorContext); + } + } + + private void visitMethod(LoadedVisitor visitor, MethodElement methodElement) { + if (visitor.matchesElement(methodElement)) { + visitor.getVisitor().visitMethod(methodElement, javaVisitorContext); + } + } + + private void visitEnumConstant(LoadedVisitor visitor, EnumConstantElement enumConstantElement) { + if (visitor.matchesElement(enumConstantElement)) { + visitor.getVisitor().visitEnumConstant(enumConstantElement, javaVisitorContext); + } + } + + private void visitField(LoadedVisitor visitor, FieldElement fieldElement) { + if (visitor.matchesElement(fieldElement)) { + visitor.getVisitor().visitField(fieldElement, javaVisitorContext); + } + } + /** * Discovers the {@link TypeElementVisitor} instances that are available. * @@ -371,190 +402,4 @@ private void writeBeanDefinitionsToMetaInf() { // remove duplicate classes .collect(Collectors.toMap(Object::getClass, v -> v, (a, b) -> a)).values(); } - - /** - * The class to visit the type elements. - */ - private class ElementVisitor extends ElementScanner8 { - - private final TypeElement concreteClass; - private final List visitors; - private JavaClassElement javaClassElement; - - ElementVisitor(JavaClassElement javaClassElement, TypeElement concreteClass, List visitors) { - this.javaClassElement = javaClassElement; - this.concreteClass = concreteClass; - this.visitors = visitors; - } - - @Override - public Object visitUnknown(Element e, Object o) { - // ignore - return o; - } - - @Override - public Object visitType(TypeElement classElement, Object o) { - if (!classElement.equals(javaClassElement.getNativeType().element())) { - javaClassElement = javaVisitorContext.getElementFactory().newSourceClassElement( - classElement, - javaVisitorContext.getElementAnnotationMetadataFactory() - ); - } - for (LoadedVisitor visitor : visitors) { - visitor.getVisitor().visitClass(javaClassElement, javaVisitorContext); - } - - Element enclosingElement = classElement.getEnclosingElement(); - // don't process inner class unless this is the visitor for it - boolean shouldVisit = !JavaModelUtils.isClass(enclosingElement) || - concreteClass.getQualifiedName().equals(classElement.getQualifiedName()); - if (shouldVisit) { - if (javaClassElement.hasStereotype(Introduction.class) || (javaClassElement.hasStereotype(Introspected.class) && javaClassElement.isAbstract())) { - classElement.asType().accept(new PublicAbstractMethodVisitor(classElement, javaVisitorContext) { - @Override - protected void accept(DeclaredType type, Element element, Object o) { - if (element instanceof ExecutableElement) { - ElementVisitor.this.visitExecutable( - (ExecutableElement) element, - o - ); - } - } - }, null); - return null; - } else if (JavaModelUtils.isEnum(classElement)) { - return scan(classElement.getEnclosedElements(), o); - } else { - List elements = enclosedElements(classElement); - Object value = null; - for (Element element : elements) { - if (element instanceof TypeElement) { - // Ignore any inner classes, annotation processor will process them if needed - continue; - } else { - value = scan(element, o); - } - } - return value; - } - } else { - return null; - } - } - - private List enclosedElements(TypeElement classElement) { - List enclosedElements = new ArrayList<>(classElement.getEnclosedElements()); - TypeElement superClass = modelUtils.superClassFor(classElement); - // collect fields and methods, skip overrides - while (superClass != null && !modelUtils.isObjectClass(superClass)) { - List elements = superClass.getEnclosedElements(); - for (Element elt1 : elements) { - if (elt1 instanceof ExecutableElement) { - checkMethodOverride(enclosedElements, elt1); - } else if (elt1 instanceof VariableElement) { - checkFieldHide(enclosedElements, elt1); - } - } - superClass = modelUtils.superClassFor(superClass); - } - return enclosedElements; - } - - private void checkFieldHide(List enclosedElements, Element elt1) { - boolean hides = false; - for (Element elt2 : enclosedElements) { - if (elt1.equals(elt2) || !(elt2 instanceof VariableElement)) { - continue; - } - if (elementUtils.hides(elt2, elt1)) { - hides = true; - break; - } - } - if (!hides) { - enclosedElements.add(elt1); - } - } - - private void checkMethodOverride(List enclosedElements, Element elt1) { - boolean overrides = false; - for (Element elt2 : enclosedElements) { - if (elt1.equals(elt2) || !(elt2 instanceof ExecutableElement)) { - continue; - } - if (elementUtils.overrides((ExecutableElement) elt2, (ExecutableElement) elt1, modelUtils.classElementFor(elt2))) { - overrides = true; - break; - } - } - if (!overrides) { - enclosedElements.add(elt1); - } - } - - @Override - public Object visitExecutable(ExecutableElement executableElement, Object o) { - if (javaClassElement == null) { - return null; - } - if (executableElement.getSimpleName().toString().equals("")) { - ConstructorElement constructorElement = javaVisitorContext.getElementFactory().newConstructorElement( - javaClassElement, - executableElement, - javaVisitorContext.getElementAnnotationMetadataFactory() - ); - for (LoadedVisitor visitor : visitors) { - if (visitor.matchesElement(constructorElement)) { - visitor.getVisitor().visitConstructor(constructorElement, javaVisitorContext); - } - } - return constructorElement; - } - MethodElement methodElement = javaVisitorContext.getElementFactory().newSourceMethodElement( - javaClassElement, - executableElement, - javaVisitorContext.getElementAnnotationMetadataFactory() - ); - for (LoadedVisitor visitor : visitors) { - if (visitor.matchesElement(methodElement)) { - visitor.getVisitor().visitMethod(methodElement, javaVisitorContext); - } - } - return methodElement; - } - - @Override - public Object visitVariable(VariableElement variable, Object o) { - // assuming just fields, visitExecutable should be handling params for method calls - if (variable.getKind() != FIELD) { - return null; - } - if (variable.getKind() == ElementKind.ENUM_CONSTANT) { - EnumConstantElement constantElement = javaVisitorContext.getElementFactory().newEnumConstantElement( - javaClassElement, - variable, - javaVisitorContext.getElementAnnotationMetadataFactory() - ); - for (LoadedVisitor visitor : visitors) { - if (visitor.matchesElement(constantElement)) { - visitor.getVisitor().visitEnumConstant(constantElement, javaVisitorContext); - } - } - return constantElement; - } - FieldElement fieldElement = javaVisitorContext.getElementFactory() - .newFieldElement( - javaClassElement, - variable, - javaVisitorContext.getElementAnnotationMetadataFactory() - ); - for (LoadedVisitor visitor : visitors) { - if (visitor.matchesElement(fieldElement)) { - visitor.getVisitor().visitField(fieldElement, javaVisitorContext); - } - } - return fieldElement; - } - } } diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaClassElement.java b/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaClassElement.java index 0359444fe14..a6c3ff28e39 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaClassElement.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaClassElement.java @@ -97,7 +97,8 @@ public class JavaClassElement extends AbstractJavaElement implements ArrayableCl private Map> resolvedAllTypeArguments; @Nullable private ClassElement resolvedSuperType; - private final JavaEnclosedElementsQuery enclosedElementsQuery = new JavaEnclosedElementsQuery(); + private final JavaEnclosedElementsQuery enclosedElementsQuery = new JavaEnclosedElementsQuery(false); + private final JavaEnclosedElementsQuery sourceEnclosedElementsQuery = new JavaEnclosedElementsQuery(true); @Nullable private ElementAnnotationMetadata elementTypeAnnotationMetadata; @Nullable @@ -455,6 +456,19 @@ public List getEnclosedElements(@ return enclosedElementsQuery.getEnclosedElements(this, query); } + /** + * This method will produce the elements just like {@link #getEnclosedElements(ElementQuery)} + * but the elements are constructed as the source ones. + * {@link io.micronaut.inject.ast.ElementFactory#newSourceMethodElement(ClassElement, Object, ElementAnnotationMetadataFactory)}. + * + * @param query The query + * @param The element type + * @return The list of elements + */ + public final List getSourceEnclosedElements(@NonNull ElementQuery query) { + return sourceEnclosedElementsQuery.getEnclosedElements(this, query); + } + @Override public boolean isArray() { return arrayDimensions > 0; @@ -713,8 +727,13 @@ public ClassElement getType() { private final class JavaEnclosedElementsQuery extends EnclosedElementsQuery { + private final boolean isSource; private List enclosedElements; + private JavaEnclosedElementsQuery(boolean isSource) { + this.isSource = isSource; + } + @Override protected TypeElement getNativeClassType(ClassElement classElement) { return ((JavaClassElement) classElement).getNativeType().element(); @@ -791,11 +810,21 @@ protected boolean excludeClass(TypeElement classNode) { protected io.micronaut.inject.ast.Element toAstElement(Element nativeType, Class elementType) { final JavaElementFactory elementFactory = visitorContext.getElementFactory(); return switch (nativeType.getKind()) { - case METHOD -> elementFactory.newMethodElement( - JavaClassElement.this, - (ExecutableElement) nativeType, - elementAnnotationMetadataFactory - ); + case METHOD -> { + if (isSource) { + yield elementFactory.newSourceMethodElement( + JavaClassElement.this, + (ExecutableElement) nativeType, + elementAnnotationMetadataFactory + ); + } else { + yield elementFactory.newMethodElement( + JavaClassElement.this, + (ExecutableElement) nativeType, + elementAnnotationMetadataFactory + ); + } + } case FIELD -> elementFactory.newFieldElement( JavaClassElement.this, (VariableElement) nativeType, @@ -811,10 +840,19 @@ protected io.micronaut.inject.ast.Element toAstElement(Element nativeType, Class (ExecutableElement) nativeType, elementAnnotationMetadataFactory ); - case CLASS, ENUM -> elementFactory.newClassElement( - (TypeElement) nativeType, - elementAnnotationMetadataFactory - ); + case CLASS, ENUM -> { + if (isSource) { + yield elementFactory.newSourceClassElement( + (TypeElement) nativeType, + elementAnnotationMetadataFactory + ); + } else { + yield elementFactory.newClassElement( + (TypeElement) nativeType, + elementAnnotationMetadataFactory + ); + } + } default -> throw new IllegalStateException("Unknown element: " + nativeType); }; } diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/IntroducedBeanVisitorSpec.groovy b/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/IntroducedBeanVisitorSpec.groovy new file mode 100644 index 00000000000..32e03d7d7fa --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/IntroducedBeanVisitorSpec.groovy @@ -0,0 +1,218 @@ +/* + * Copyright 2017-2019 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.aop.introduction.beans + +import io.micronaut.annotation.processing.test.AbstractTypeElementSpec + +class IntroducedBeanVisitorSpec extends AbstractTypeElementSpec { + + void "test introduced bean visitor"() { + given: + def context = buildContext(""" +package introducedbeanspec; + +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.ElementType; +import java.lang.annotation.*; +import io.micronaut.aop.Introduction; +import io.micronaut.context.annotation.Type; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.aop.MethodInterceptor; +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.core.annotation.Nullable; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import java.util.Optional; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +@Inherited +@interface XMyDataMethod { +} + +@Introduction +@Type(MyRepoIntroducer.class) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Inherited +@interface RepoDef { +} + + +@Singleton +class MyRepoIntroducer implements MethodInterceptor { + + @Nullable + @Override + public Object intercept(MethodInvocationContext context) { + return null; + } +} + +interface Repo1 { + Publisher findAll(); + + Publisher method1(); +} + +interface Repo2 { + Publisher findAll(); + + Publisher method2(); +} + +@RepoDef +interface Repo3 extends Repo2, Repo1 { + + Publisher method3(); + +} + +class MyBean { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + + +""") + + when: + def beanDef1 = context.getBeanDefinition(context.classLoader.loadClass("introducedbeanspec.Repo3")) + def findAllMethod = beanDef1.getRequiredMethod("findAll") + def method1 = beanDef1.getRequiredMethod("method1") + def method2 = beanDef1.getRequiredMethod("method2") + def method3 = beanDef1.getRequiredMethod("method3") + then: + findAllMethod.hasAnnotation("introducedbeanspec.XMyDataMethod") + method1.hasAnnotation("introducedbeanspec.XMyDataMethod") + method2.hasAnnotation("introducedbeanspec.XMyDataMethod") + method3.hasAnnotation("introducedbeanspec.XMyDataMethod") + + cleanup: + context.close() + } + + void "test introduced bean visitor 2"() { + given: + def context = buildContext(""" +package introducedbeanspec2; + +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.ElementType; +import java.lang.annotation.*; +import io.micronaut.aop.InterceptorBean; +import io.micronaut.aop.Introduction; +import io.micronaut.context.annotation.Type; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.aop.MethodInterceptor; +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.core.annotation.Nullable; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import java.util.Optional; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +@Inherited +@interface XMyDataMethod { +} + +@Introduction +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Inherited +@interface RepoDef { +} + + +@Singleton +@InterceptorBean(RepoDef.class) +class MyRepoIntroducer implements MethodInterceptor { + + @Nullable + @Override + public Object intercept(MethodInvocationContext context) { + return null; + } +} + +interface Repo1 { + Publisher findAll(); + + Publisher method1(); +} + +interface Repo2 { + Publisher findAll(); + + Publisher method2(); +} + +@RepoDef +interface Repo3 extends Repo2, Repo1 { + + Publisher method3(); + +} + +class MyBean { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + + +""") + + when: + def beanDef1 = context.getBeanDefinition(context.classLoader.loadClass("introducedbeanspec2.Repo3")) + def findAllMethod = beanDef1.getRequiredMethod("findAll") + def method1 = beanDef1.getRequiredMethod("method1") + def method2 = beanDef1.getRequiredMethod("method2") + def method3 = beanDef1.getRequiredMethod("method3") + then: + findAllMethod.hasAnnotation("introducedbeanspec2.XMyDataMethod") + method1.hasAnnotation("introducedbeanspec2.XMyDataMethod") + method2.hasAnnotation("introducedbeanspec2.XMyDataMethod") + method3.hasAnnotation("introducedbeanspec2.XMyDataMethod") + + cleanup: + context.close() + } +} diff --git a/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/MyRepoVisitor2.java b/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/MyRepoVisitor2.java new file mode 100644 index 00000000000..bbc08e3d43e --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/aop/introduction/beans/MyRepoVisitor2.java @@ -0,0 +1,22 @@ +package io.micronaut.aop.introduction.beans; + +import io.micronaut.inject.ast.ClassElement; +import io.micronaut.inject.ast.MethodElement; +import io.micronaut.inject.visitor.TypeElementVisitor; +import io.micronaut.inject.visitor.VisitorContext; + +public class MyRepoVisitor2 implements TypeElementVisitor { + + @Override + public void visitClass(ClassElement element, VisitorContext context) { + } + + @Override + public void visitMethod(MethodElement element, VisitorContext context) { + if (element.getOwningType().getPackageName().equals("introducedbeanspec")) { + element.annotate("introducedbeanspec.XMyDataMethod"); + } else if (element.getOwningType().getPackageName().equals("introducedbeanspec2")) { + element.annotate("introducedbeanspec2.XMyDataMethod"); + } + } +} diff --git a/inject-java/src/test/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor b/inject-java/src/test/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor index 6f2fdc69e3a..71ade81eba6 100644 --- a/inject-java/src/test/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor +++ b/inject-java/src/test/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor @@ -18,3 +18,4 @@ io.micronaut.annotation.AnnotateMethodParameterSpec$AnnotateMethodParameterVisit io.micronaut.annotation.AnnotatePropertySpec$AnnotatePropertyVisitor io.micronaut.annotation.AnnotateClassSpec$AnnotateClassVisitor io.micronaut.annotation.AnnotateTypeArgSpec$AnnotateTypeArgVisitor +io.micronaut.aop.introduction.beans.MyRepoVisitor2