diff --git a/core-processor/src/main/java/io/micronaut/inject/ast/DefaultElementQuery.java b/core-processor/src/main/java/io/micronaut/inject/ast/DefaultElementQuery.java index 269a0793ac6..4b4cde2f3c4 100644 --- a/core-processor/src/main/java/io/micronaut/inject/ast/DefaultElementQuery.java +++ b/core-processor/src/main/java/io/micronaut/inject/ast/DefaultElementQuery.java @@ -49,6 +49,7 @@ final class DefaultElementQuery implements ElementQuery, E private final boolean includeOverriddenMethods; private final boolean includeHiddenElements; private final boolean excludePropertyElements; + private final int hashcode; DefaultElementQuery(Class elementType) { this(elementType, null, false, false, false, false, false, false, false, false, false, false, null, null, null, null, null); @@ -89,6 +90,7 @@ final class DefaultElementQuery implements ElementQuery, E this.includeHiddenElements = includeHiddenElements; this.excludePropertyElements = excludePropertyElements; this.typePredicates = typePredicates; + this.hashcode = Objects.hash(elementType, onlyAccessibleType, onlyDeclared, onlyAbstract, onlyConcrete, onlyInjected, namePredicates, annotationPredicates, modifiersPredicates, elementPredicates, typePredicates, onlyInstance, onlyStatic, includeEnumConstants, includeOverriddenMethods, includeHiddenElements, excludePropertyElements); } @Override @@ -561,4 +563,21 @@ public ElementQuery filter(@NonNull Predicate predicate) { public Result result() { return this; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultElementQuery that = (DefaultElementQuery) o; + return onlyDeclared == that.onlyDeclared && onlyAbstract == that.onlyAbstract && onlyConcrete == that.onlyConcrete && onlyInjected == that.onlyInjected && onlyInstance == that.onlyInstance && onlyStatic == that.onlyStatic && includeEnumConstants == that.includeEnumConstants && includeOverriddenMethods == that.includeOverriddenMethods && includeHiddenElements == that.includeHiddenElements && excludePropertyElements == that.excludePropertyElements && Objects.equals(elementType, that.elementType) && Objects.equals(onlyAccessibleType, that.onlyAccessibleType) && Objects.equals(namePredicates, that.namePredicates) && Objects.equals(annotationPredicates, that.annotationPredicates) && Objects.equals(modifiersPredicates, that.modifiersPredicates) && Objects.equals(elementPredicates, that.elementPredicates) && Objects.equals(typePredicates, that.typePredicates); + } + + @Override + public int hashCode() { + return hashcode; + } } diff --git a/core-processor/src/main/java/io/micronaut/inject/ast/MethodElement.java b/core-processor/src/main/java/io/micronaut/inject/ast/MethodElement.java index ae1c5cad5e6..c4764c8bad3 100644 --- a/core-processor/src/main/java/io/micronaut/inject/ast/MethodElement.java +++ b/core-processor/src/main/java/io/micronaut/inject/ast/MethodElement.java @@ -329,6 +329,10 @@ default boolean hides(@NonNull MemberElement memberElement) { return false; } } + if (!getDeclaringType().isAssignable(memberElement.getDeclaringType())) { + // not a parent class + return false; + } ClassElement existingReturnType = hidden.getReturnType().getGenericType(); ClassElement newTypeReturn = newMethod.getReturnType().getGenericType(); if (!newTypeReturn.isAssignable(existingReturnType)) { @@ -337,7 +341,7 @@ default boolean hides(@NonNull MemberElement memberElement) { if (hidden.isPackagePrivate()) { return newMethod.getDeclaringType().getPackageName().equals(hidden.getDeclaringType().getPackageName()); } - return true; + return memberElement.isAccessible(getDeclaringType()); } return false; } diff --git a/core-processor/src/main/java/io/micronaut/inject/ast/utils/EnclosedElementsQuery.java b/core-processor/src/main/java/io/micronaut/inject/ast/utils/EnclosedElementsQuery.java index ffb63c2c2ab..07c2aac1028 100644 --- a/core-processor/src/main/java/io/micronaut/inject/ast/utils/EnclosedElementsQuery.java +++ b/core-processor/src/main/java/io/micronaut/inject/ast/utils/EnclosedElementsQuery.java @@ -34,7 +34,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -54,7 +54,9 @@ public abstract class EnclosedElementsQuery { private static final int MAX_ITEMS_IN_CACHE = 200; + private static final int MAX_RESULTS = 20; private final Map elementsCache = new LinkedHashMap<>(); + private final Map> resultsCache = new LinkedHashMap<>(); /** * Get native class element. @@ -87,6 +89,13 @@ protected N getNativeType(Element element) { public List getEnclosedElements(ClassElement classElement, @NonNull ElementQuery query) { Objects.requireNonNull(query, "Query cannot be null"); ElementQuery.Result result = query.result(); + + QueryResultKey queryResultKey = new QueryResultKey(result, classElement.getNativeType()); + List values = (List) resultsCache.get(queryResultKey); + if (values != null) { + return values; + } + Set excludeElements = getExcludedNativeElements(result); Predicate filter = element -> { if (excludeElements.contains(getNativeType(element))) { @@ -153,19 +162,8 @@ public List getEnclosedElements(C } } if (!result.getTypePredicates().isEmpty()) { + ClassElement ce = memberType(classElement, element); for (Predicate typePredicate : result.getTypePredicates()) { - ClassElement ce; - if (element instanceof ConstructorElement) { - ce = classElement; - } else if (element instanceof MethodElement methodElement) { - ce = methodElement.getGenericReturnType(); - } else if (element instanceof ClassElement theClass) { - ce = theClass; - } else if (element instanceof FieldElement fieldElement) { - ce = fieldElement.getGenericField(); - } else { - throw new IllegalStateException("Unknown element: " + element); - } if (!typePredicate.test(ce)) { return false; } @@ -173,11 +171,31 @@ public List getEnclosedElements(C } return true; }; - Collection allElements = getAllElements(getNativeClassType(classElement), result.isOnlyDeclared(), (t1, t2) -> reduceElements(t1, t2, result), result); - return allElements - .stream() - .filter(filter) - .toList(); + + C nativeClassType = getNativeClassType(classElement); + List elements; + if (result.isOnlyDeclared() || classElement.getSuperType().isEmpty() && classElement.getInterfaces().isEmpty()) { + elements = getElements(nativeClassType, result, filter); + } else { + elements = getAllElements(nativeClassType, (t1, t2) -> reduceElements(t1, t2, result), result, filter); + } + resultsCache.put(queryResultKey, elements); + adjustMapCapacity(resultsCache, MAX_RESULTS); + return elements; + } + + private ClassElement memberType(ClassElement classElement, T element) { + if (element instanceof ConstructorElement) { + return classElement; + } else if (element instanceof MethodElement methodElement) { + return methodElement.getGenericReturnType(); + } else if (element instanceof ClassElement theClass) { + return theClass; + } else if (element instanceof FieldElement fieldElement) { + return fieldElement.getGenericField(); + } else { + throw new IllegalStateException("Unknown element: " + element); + } } private boolean reduceElements(io.micronaut.inject.ast.Element newElement, @@ -203,76 +221,171 @@ private boolean reduceElements(io.micronaut.inject.ast.Element newElement, return false; } - private Collection getAllElements(C classNode, - boolean onlyDeclared, - BiPredicate reduce, - ElementQuery.Result result) { - Set elements = new LinkedHashSet<>(); - List> hierarchy = new ArrayList<>(); - collectHierarchy(classNode, onlyDeclared, hierarchy, result); - for (List classElements : hierarchy) { - Set addedFromClassElements = new LinkedHashSet<>(); - classElements: - for (N element : classElements) { - List> namePredicates = result.getNamePredicates(); - if (!namePredicates.isEmpty()) { - String elementName = getElementName(element); - for (Predicate namePredicate : namePredicates) { - if (!namePredicate.test(elementName)) { - continue classElements; - } - } - } + private List getAllElements(C classNode, + BiPredicate reduce, + ElementQuery.Result result, + Predicate filter) { + + List elements = new LinkedList<>(); + List addedFromClassElements = new ArrayList<>(20); + if (isInterface(classNode)) { + processInterfaceHierarchy(classNode, reduce, result, addedFromClassElements, elements, true); + } else { + boolean includeAbstract = isAbstractClass(classNode) || result.isIncludeOverriddenMethods(); + processClassHierarchy(classNode, reduce, result, addedFromClassElements, elements, includeAbstract); + } + elements.removeIf(element -> !filter.test(element)); + return elements; + } + + private void processClassHierarchy(C classNode, + BiPredicate reduce, + ElementQuery.Result result, + List addedFromClassElements, + List collectedElements, + boolean includeAbstract) { + if (excludeClass(classNode)) { + return; + } + C superClass = getSuperClass(classNode); + if (superClass != null) { + processClassHierarchy(superClass, reduce, result, addedFromClassElements, collectedElements, includeAbstract); + } + reduce(collectedElements, getEnclosedElements(classNode, result, includeAbstract), reduce, result, addedFromClassElements, false, false); + for (C anInterface : getInterfaces(classNode)) { + processInterfaceHierarchy(anInterface, reduce, result, addedFromClassElements, collectedElements, includeAbstract); + } + } + + private void processInterfaceHierarchy(C classNode, + BiPredicate reduce, + ElementQuery.Result result, + List addedFromClassElements, + Collection collectedElements, + boolean includeAbstract) { + if (excludeClass(classNode)) { + return; + } + for (C anInterface : getInterfaces(classNode)) { + processInterfaceHierarchy(anInterface, reduce, result, addedFromClassElements, collectedElements, includeAbstract); + } + reduce(collectedElements, getEnclosedElements(classNode, result, includeAbstract), reduce, result, addedFromClassElements, true, includeAbstract); + } - N nativeType = getCacheKey(element); - CacheKey cacheKey = new CacheKey(result.getElementType(), nativeType); - T newElement = (T) elementsCache.computeIfAbsent(cacheKey, ck -> toAstElement(nativeType, result.getElementType())); - if (result.getElementType() == MemberElement.class) { - // Also cache members query results as it's original element type - if (newElement instanceof FieldElement) { - elementsCache.putIfAbsent(new CacheKey(FieldElement.class, nativeType), newElement); - } else if (newElement instanceof ConstructorElement) { - elementsCache.putIfAbsent(new CacheKey(ConstructorElement.class, nativeType), newElement); - elementsCache.putIfAbsent(new CacheKey(MethodElement.class, nativeType), newElement); - } else if (newElement instanceof MethodElement) { - elementsCache.putIfAbsent(new CacheKey(MethodElement.class, nativeType), newElement); - } else if (newElement instanceof PropertyElement) { - elementsCache.putIfAbsent(new CacheKey(PropertyElement.class, nativeType), newElement); + private void reduce(Collection collectedElements, + List classElements, + BiPredicate reduce, + ElementQuery.Result result, + List addedFromClassElements, + boolean isInterface, + boolean includesAbstract) { + List> namePredicates = result.getNamePredicates(); + boolean hasNamePredicates = !namePredicates.isEmpty(); + addedFromClassElements.clear(); // Reusing this collection for all the calls + classElements: + for (N element : classElements) { + if (hasNamePredicates) { + String elementName = getElementName(element); + for (Predicate namePredicate : namePredicates) { + if (!namePredicate.test(elementName)) { + continue classElements; } - } else if (MemberElement.class.isAssignableFrom(result.getElementType())) { - elementsCache.putIfAbsent(new CacheKey(MemberElement.class, nativeType), newElement); } - if (elementsCache.size() == MAX_ITEMS_IN_CACHE) { - Iterator> iterator = elementsCache.entrySet().iterator(); - iterator.next(); - iterator.remove(); - } - if (!result.getElementType().isInstance(newElement)) { - // dirty cache - elementsCache.remove(cacheKey); - newElement = (T) elementsCache.computeIfAbsent(cacheKey, ck -> toAstElement(nativeType, result.getElementType())); + } + T newElement = convertElement(result, element); + + for (Iterator iterator = collectedElements.iterator(); iterator.hasNext(); ) { + T existingElement = iterator.next(); + if (!existingElement.getName().equals(newElement.getName())) { + continue; } - for (Iterator iterator = elements.iterator(); iterator.hasNext(); ) { - T existingElement = iterator.next(); - if (newElement.equals(existingElement)) { - continue; + if (isInterface) { + if (existingElement == newElement) { + continue classElements; } - if (reduce.test(newElement, existingElement)) { + if (reduce.test(existingElement, newElement)) { + continue classElements; + } else if (includesAbstract && reduce.test(newElement, existingElement)) { iterator.remove(); addedFromClassElements.add(newElement); - } else if (reduce.test(existingElement, newElement)) { continue classElements; } + } else if (reduce.test(newElement, existingElement)) { + iterator.remove(); + addedFromClassElements.add(newElement); + continue classElements; + } + + } + addedFromClassElements.add(newElement); + } + collectedElements.addAll(addedFromClassElements); + } + + private List getElements(C classNode, + ElementQuery.Result result, + Predicate filter) { + List enclosedElements = getEnclosedElements(classNode, result, true); + List elements = new ArrayList<>(enclosedElements.size()); + List> namePredicates = result.getNamePredicates(); + boolean hasNamePredicates = !namePredicates.isEmpty(); + enclosedElementsLoop: + for (N enclosedElement : enclosedElements) { + if (hasNamePredicates) { + String elementName = getElementName(enclosedElement); + for (Predicate namePredicate : namePredicates) { + if (!namePredicate.test(elementName)) { + continue enclosedElementsLoop; + } } - addedFromClassElements.add(newElement); } - elements.addAll(addedFromClassElements); + T element = convertElement(result, enclosedElement); + if (filter.test(element)) { + elements.add(element); + } } return elements; } + private T convertElement(ElementQuery.Result result, N element) { + N nativeType = getCacheKey(element); + CacheKey cacheKey = new CacheKey(result.getElementType(), nativeType); + T newElement = (T) elementsCache.computeIfAbsent(cacheKey, ck -> toAstElement(nativeType, result.getElementType())); + if (result.getElementType() == MemberElement.class) { + // Also cache members query results as it's original element type + if (newElement instanceof FieldElement) { + elementsCache.putIfAbsent(new CacheKey(FieldElement.class, nativeType), newElement); + } else if (newElement instanceof ConstructorElement) { + elementsCache.putIfAbsent(new CacheKey(ConstructorElement.class, nativeType), newElement); + elementsCache.putIfAbsent(new CacheKey(MethodElement.class, nativeType), newElement); + } else if (newElement instanceof MethodElement) { + elementsCache.putIfAbsent(new CacheKey(MethodElement.class, nativeType), newElement); + } else if (newElement instanceof PropertyElement) { + elementsCache.putIfAbsent(new CacheKey(PropertyElement.class, nativeType), newElement); + } + } else if (MemberElement.class.isAssignableFrom(result.getElementType())) { + elementsCache.putIfAbsent(new CacheKey(MemberElement.class, nativeType), newElement); + } + adjustMapCapacity(elementsCache, MAX_ITEMS_IN_CACHE); + if (!result.getElementType().isInstance(newElement)) { + // dirty cache + elementsCache.remove(cacheKey); + newElement = (T) elementsCache.computeIfAbsent(cacheKey, ck -> toAstElement(nativeType, result.getElementType())); + } + return newElement; + } + + private void adjustMapCapacity(Map map, int size) { + if (map.size() == size) { + Iterator iterator = map.entrySet().iterator(); + iterator.next(); + iterator.remove(); + } + } + /** * Gets the element name. + * * @param element The element * @return The name */ @@ -288,27 +401,6 @@ protected N getCacheKey(N element) { return element; } - private void collectHierarchy(C classNode, - boolean onlyDeclared, - List> hierarchy, - ElementQuery.Result result) { - if (excludeClass(classNode)) { - return; - } - if (!onlyDeclared) { - C superclass = getSuperClass(classNode); - if (superclass != null) { - collectHierarchy(superclass, false, hierarchy, result); - } - for (C interfaceNode : getInterfaces(classNode)) { - List> interfaceElements = new ArrayList<>(); - collectHierarchy(interfaceNode, false, interfaceElements, result); - hierarchy.addAll(interfaceElements); - } - } - hierarchy.add(getEnclosedElements(classNode, result)); - } - /** * Provides a collection of the native elements to exclude. * @@ -337,6 +429,17 @@ protected Set getExcludedNativeElements(@NonNull ElementQuery.Result resul @NonNull protected abstract Collection getInterfaces(C classNode); + /** + * Extracts the enclosed elements of the class. + * + * @param classNode The class + * @param result The query result + * @param includeAbstract If abstract/non-default elements should be included + * @return The enclosed elements + */ + @NonNull + protected abstract List getEnclosedElements(C classNode, ElementQuery.Result result, boolean includeAbstract); + /** * Extracts the enclosed elements of the class. * @@ -345,7 +448,9 @@ protected Set getExcludedNativeElements(@NonNull ElementQuery.Result resul * @return The enclosed elements */ @NonNull - protected abstract List getEnclosedElements(C classNode, ElementQuery.Result result); + protected List getEnclosedElements(C classNode, ElementQuery.Result result) { + return getEnclosedElements(classNode, result, true); + } /** * Checks if the class needs to be excluded. @@ -355,11 +460,29 @@ protected Set getExcludedNativeElements(@NonNull ElementQuery.Result resul */ protected abstract boolean excludeClass(C classNode); + /** + * Is abstract class. + * + * @param classNode The class node + * @return true if abstract + * @since 4.3.0 + */ + protected abstract boolean isAbstractClass(C classNode); + + /** + * Is interface. + * + * @param classNode The class node + * @return true if interface + * @since 4.3.0 + */ + protected abstract boolean isInterface(C classNode); + /** * Converts the native element to the AST element. * - * @param nativeType The native element. - * @param elementType The result type + * @param nativeType The native element. + * @param elementType The result type * @return The AST element */ @NonNull @@ -367,4 +490,7 @@ protected Set getExcludedNativeElements(@NonNull ElementQuery.Result resul private record CacheKey(Class elementType, Object nativeType) { } + + private record QueryResultKey(ElementQuery.Result result, Object nativeType) { + } } diff --git a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/visitor/GroovyClassElement.java b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/visitor/GroovyClassElement.java index 5941eb650b2..c3c7ebe09a5 100644 --- a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/visitor/GroovyClassElement.java +++ b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/visitor/GroovyClassElement.java @@ -51,6 +51,7 @@ import io.micronaut.inject.ast.utils.AstBeanPropertiesUtils; import io.micronaut.inject.ast.utils.EnclosedElementsQuery; import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ConstructorNode; @@ -720,21 +721,25 @@ protected Collection getInterfaces(ClassNode classNode) { @Override protected List getEnclosedElements(ClassNode classNode, - ElementQuery.Result result) { + ElementQuery.Result result, + boolean includeAbstract) { Class elementType = result.getElementType(); - return getEnclosedElements(classNode, result, elementType); + return getEnclosedElements(classNode, result, elementType, includeAbstract); } - private List getEnclosedElements(ClassNode classNode, ElementQuery.Result result, Class elementType) { + private List getEnclosedElements(ClassNode classNode, + ElementQuery.Result result, + Class elementType, + boolean includeAbstract) { if (elementType == MemberElement.class) { return Stream.concat( - getEnclosedElements(classNode, result, FieldElement.class).stream(), - getEnclosedElements(classNode, result, MethodElement.class).stream() + getEnclosedElements(classNode, result, FieldElement.class, includeAbstract).stream(), + getEnclosedElements(classNode, result, MethodElement.class, includeAbstract).stream() ).toList(); } else if (elementType == MethodElement.class) { return classNode.getMethods() .stream() - .filter(methodNode -> !JUNK_METHOD_FILTER.test(methodNode) && (methodNode.getModifiers() & Opcodes.ACC_SYNTHETIC) == 0) + .filter(methodNode -> !JUNK_METHOD_FILTER.test(methodNode) && (methodNode.getModifiers() & Opcodes.ACC_SYNTHETIC) == 0 && (includeAbstract || isNonAbstract(classNode, methodNode))) .map(m -> m) .toList(); } else if (elementType == FieldElement.class) { @@ -761,6 +766,19 @@ private List getEnclosedElements(ClassNode classNode, ElementQuer } } + private boolean isNonAbstract(ClassNode classNode, MethodNode methodNode) { + if (methodNode.isDefault()) { + return false; + } + if (methodNode.isPrivate() && classNode.isInterface()) { + return true; + } + if (!methodNode.isAbstract() && classNode.isInterface()) { + return false; + } + return !methodNode.isAbstract(); + } + @Override protected boolean excludeClass(ClassNode classNode) { String packageName = Objects.requireNonNullElse(classNode.getPackageName(), ""); @@ -775,6 +793,23 @@ protected boolean excludeClass(ClassNode classNode) { || Script.class.getName().equals(className); } + @Override + protected boolean isAbstractClass(ClassNode classNode) { + return classNode.isAbstract(); + } + + @Override + protected boolean isInterface(ClassNode classNode) { + if (classNode.isInterface()) { + return true; + } + List annotations = classNode.getAnnotations(); + if (annotations != null) { + return annotations.stream().anyMatch(a -> a.getClassNode().getName().equals(groovy.transform.Trait.class.getName())); + } + return false; + } + @Override protected Element toAstElement(AnnotatedNode nativeType, Class elementType) { final GroovyElementFactory elementFactory = visitorContext.getElementFactory(); diff --git a/inject-groovy/src/test/groovy/io/micronaut/aop/introduction/IntroductionAdviceWithNewInterfaceSpec.groovy b/inject-groovy/src/test/groovy/io/micronaut/aop/introduction/IntroductionAdviceWithNewInterfaceSpec.groovy index 24c145131b6..7374c690127 100644 --- a/inject-groovy/src/test/groovy/io/micronaut/aop/introduction/IntroductionAdviceWithNewInterfaceSpec.groovy +++ b/inject-groovy/src/test/groovy/io/micronaut/aop/introduction/IntroductionAdviceWithNewInterfaceSpec.groovy @@ -17,7 +17,6 @@ package io.micronaut.aop.introduction import io.micronaut.ast.transform.test.AbstractBeanDefinitionSpec import io.micronaut.context.ApplicationContext -import io.micronaut.context.DefaultBeanContext import io.micronaut.context.event.ApplicationEventListener import io.micronaut.inject.BeanDefinition import io.micronaut.inject.InstantiatableBeanDefinition @@ -193,7 +192,7 @@ interface SpecificInterface { then: //I ended up going this route because actually calling the methods here would be relying on //having the target interface in the bytecode of the test - instance.$proxyMethods.length == 1 + instance.$proxyMethods.length == 2 cleanup: context.close() diff --git a/inject-groovy/src/test/groovy/io/micronaut/inject/visitor/ClassElementSpec.groovy b/inject-groovy/src/test/groovy/io/micronaut/inject/visitor/ClassElementSpec.groovy index ee49674e77d..f64b383c1e4 100644 --- a/inject-groovy/src/test/groovy/io/micronaut/inject/visitor/ClassElementSpec.groovy +++ b/inject-groovy/src/test/groovy/io/micronaut/inject/visitor/ClassElementSpec.groovy @@ -815,7 +815,7 @@ interface MyBean extends GenericInterface, SpecificInterface { when: def allMethods = classElement.getEnclosedElements(ElementQuery.ALL_METHODS) then: - allMethods.size() == 1 + allMethods.size() == 2 when: def declaredMethods = classElement.getEnclosedElements(ElementQuery.ALL_METHODS.onlyDeclared()) then: 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 602e011fd7f..3c183b66320 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 @@ -103,6 +103,8 @@ public class JavaClassElement extends AbstractJavaElement implements ArrayableCl private Map> resolvedAllTypeArguments; @Nullable private ClassElement resolvedSuperType; + @Nullable + private List resolvedInterfaces; private final JavaEnclosedElementsQuery enclosedElementsQuery = new JavaEnclosedElementsQuery(false); private final JavaEnclosedElementsQuery sourceEnclosedElementsQuery = new JavaEnclosedElementsQuery(true); @Nullable @@ -256,7 +258,10 @@ public boolean isPrimitive() { @Override public Collection getInterfaces() { - return classElement.getInterfaces().stream().map(mirror -> newClassElement(mirror, getTypeArguments())).toList(); + if (resolvedInterfaces == null) { + resolvedInterfaces = classElement.getInterfaces().stream().map(mirror -> newClassElement(mirror, getTypeArguments())).toList(); + } + return resolvedInterfaces; } @Override @@ -796,7 +801,7 @@ protected Collection getInterfaces(TypeElement classNode) { } @Override - protected List getEnclosedElements(TypeElement classNode, ElementQuery.Result result) { + protected List getEnclosedElements(TypeElement classNode, ElementQuery.Result result, boolean includeAbstract) { List ee; if (classNode == classElement) { ee = getEnclosedElements(); @@ -804,7 +809,24 @@ protected List getEnclosedElements(TypeElement classNode, ElementQuery. ee = classNode.getEnclosedElements(); } EnumSet elementKinds = getElementKind(result); - return ee.stream().filter(element -> elementKinds.contains(element.getKind())).collect(Collectors.toList()); + List list = new ArrayList<>(ee.size()); + for (Element element : ee) { + Set modifiers = element.getModifiers(); + if (elementKinds.contains(element.getKind()) && (includeAbstract || isNonAbstractMethod(modifiers))) { + list.add(element); + } + } + return list; + } + + private boolean isNonAbstractMethod(Set modifiers) { + if (modifiers.contains(Modifier.DEFAULT)) { + return true; + } + if (modifiers.contains(Modifier.PRIVATE)) { + return true; + } + return !modifiers.contains(Modifier.ABSTRACT); } @Override @@ -813,6 +835,16 @@ protected boolean excludeClass(TypeElement classNode) { || classNode.getQualifiedName().toString().equals(Enum.class.getName()); } + @Override + protected boolean isAbstractClass(TypeElement classNode) { + return classNode.getModifiers().contains(Modifier.ABSTRACT); + } + + @Override + protected boolean isInterface(TypeElement classNode) { + return classNode.getKind() == ElementKind.INTERFACE; + } + @Override protected io.micronaut.inject.ast.Element toAstElement(Element nativeType, Class elementType) { final JavaElementFactory elementFactory = visitorContext.getElementFactory(); @@ -892,4 +924,4 @@ private EnumSet getElementKind(ElementQuery.Result result) { } -} \ No newline at end of file +} diff --git a/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy b/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy index d7f5c406a76..cf3f4b790b6 100644 --- a/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy @@ -3319,6 +3319,42 @@ interface MyInterface { methods.size() == 4 } + void "test unrecognized default method"() { + given: + ClassElement classElement = buildClassElement(''' +package elementquery; + +interface MyBean extends GenericInterface, SpecificInterface { + + default Specific getObject() { + return null; + } + +} + +class Generic { +} +class Specific extends Generic { +} +interface GenericInterface { + Generic getObject(); +} +interface SpecificInterface { + Specific getObject(); +} + +''') + when: + def allMethods = classElement.getEnclosedElements(ElementQuery.ALL_METHODS) + then: + allMethods.size() == 2 + when: + def declaredMethods = classElement.getEnclosedElements(ElementQuery.ALL_METHODS.onlyDeclared()) + then: + declaredMethods.size() == 1 + declaredMethods.get(0).isDefault() == true + } + private void assertListGenericArgument(ClassElement type, Closure cl) { def arg1 = type.getAllTypeArguments().get(List.class.name).get("E") def arg2 = type.getAllTypeArguments().get(Collection.class.name).get("E") diff --git a/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt b/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt index 199af8adf76..6d10821c8fa 100644 --- a/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt +++ b/inject-kotlin/src/main/kotlin/io/micronaut/kotlin/processing/visitor/KotlinClassElement.kt @@ -700,22 +700,24 @@ internal open class KotlinClassElement( override fun getEnclosedElements( classNode: KSClassDeclaration, - result: ElementQuery.Result<*> + result: ElementQuery.Result<*>, + includeAbstract: Boolean ): List { val elementType: Class<*> = result.elementType - return getEnclosedElements(classNode, result, elementType) + return getEnclosedElements(classNode, result, elementType, includeAbstract) } private fun getEnclosedElements( classNode: KSClassDeclaration, result: ElementQuery.Result<*>, - elementType: Class<*> + elementType: Class<*>, + includeAbstract: Boolean ): List { return when (elementType) { MemberElement::class.java -> { Stream.concat( - getEnclosedElements(classNode, result, FieldElement::class.java).stream(), - getEnclosedElements(classNode, result, MethodElement::class.java).stream() + getEnclosedElements(classNode, result, FieldElement::class.java, includeAbstract).stream(), + getEnclosedElements(classNode, result, MethodElement::class.java, includeAbstract).stream() ).toList() } @@ -729,7 +731,7 @@ internal open class KotlinClassElement( "hashCode", "toString", "equals" - ).contains(func.simpleName.asString()) + ).contains(func.simpleName.asString()) && (includeAbstract || !func.isAbstract || !classNode.isAbstract()) } .toList() } @@ -772,6 +774,10 @@ internal open class KotlinClassElement( classNode.qualifiedName.toString() == Enum::class.java.name } + override fun isAbstractClass(classNode: KSClassDeclaration) = classNode.isAbstract() + + override fun isInterface(classNode: KSClassDeclaration) = classNode.classKind == ClassKind.INTERFACE + override fun toAstElement( nativeType: KSNode, elementType: Class<*>