diff --git a/src/main/java/org/springframework/data/type/classreading/AbstractRecursiveAnnotationVisitor.java b/src/main/java/org/springframework/data/type/classreading/AbstractRecursiveAnnotationVisitor.java new file mode 100644 index 000000000..90616a9f9 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/AbstractRecursiveAnnotationVisitor.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.lang.reflect.Field; +import java.security.AccessControlException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@link AnnotationVisitor} to recursively visit annotations. + * + * @author Chris Beams + * @author Juergen Hoeller + * @author Phillip Webb + * @author Sam Brannen + * @since 3.1.1 + * @deprecated As of Spring Framework 5.2, this class and related classes in this package have been replaced by + * {@link SimpleAnnotationMetadataReadingVisitor} and related classes for internal use within the framework. + */ +@Deprecated +abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { + + protected final Log logger = LogFactory.getLog(getClass()); + + protected final AnnotationAttributes attributes; + + @Nullable protected final ClassLoader classLoader; + + public AbstractRecursiveAnnotationVisitor(@Nullable ClassLoader classLoader, AnnotationAttributes attributes) { + super(SpringAsmInfo.ASM_VERSION); + this.classLoader = classLoader; + this.attributes = attributes; + } + + @Override + public void visit(String attributeName, Object attributeValue) { + this.attributes.put(attributeName, attributeValue); + } + + @Override + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader); + this.attributes.put(attributeName, nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); + } + + @Override + public AnnotationVisitor visitArray(String attributeName) { + return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader); + } + + @Override + public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) { + Object newValue = getEnumValue(asmTypeDescriptor, attributeValue); + visit(attributeName, newValue); + } + + protected Object getEnumValue(String asmTypeDescriptor, String attributeValue) { + Object valueToUse = attributeValue; + try { + Class enumType = ClassUtils.forName(Type.getType(asmTypeDescriptor).getClassName(), this.classLoader); + Field enumConstant = ReflectionUtils.findField(enumType, attributeValue); + if (enumConstant != null) { + ReflectionUtils.makeAccessible(enumConstant); + valueToUse = enumConstant.get(null); + } + } catch (ClassNotFoundException | NoClassDefFoundError ex) { + logger.debug("Failed to classload enum type while reading annotation metadata", ex); + } catch (IllegalAccessException | AccessControlException ex) { + logger.debug("Could not access enum value while reading annotation metadata", ex); + } + return valueToUse; + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/AnnotationAttributesReadingVisitor.java b/src/main/java/org/springframework/data/type/classreading/AnnotationAttributesReadingVisitor.java new file mode 100644 index 000000000..b93013c36 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/AnnotationAttributesReadingVisitor.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor; +import org.springframework.lang.Nullable; +import org.springframework.util.MultiValueMap; +import org.springframework.util.ObjectUtils; + +/** + * ASM visitor which looks for annotations defined on a class or method, including meta-annotations. + *

+ * This visitor is fully recursive, taking into account any nested annotations or nested annotation arrays. + * + * @author Juergen Hoeller + * @author Chris Beams + * @author Phillip Webb + * @author Sam Brannen + * @since 3.0 + * @deprecated As of Spring Framework 5.2, this class and related classes in this package have been replaced by + * {@link SimpleAnnotationMetadataReadingVisitor} and related classes for internal use within the framework. + */ +@Deprecated +final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { + + private final MultiValueMap attributesMap; + + private final Map> metaAnnotationMap; + + public AnnotationAttributesReadingVisitor(String annotationType, + MultiValueMap attributesMap, Map> metaAnnotationMap, + @Nullable ClassLoader classLoader) { + + super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader); + this.attributesMap = attributesMap; + this.metaAnnotationMap = metaAnnotationMap; + } + + @Override + public void visitEnd() { + super.visitEnd(); + + Class annotationClass = this.attributes.annotationType(); + if (annotationClass != null) { + List attributeList = this.attributesMap.get(this.annotationType); + if (attributeList == null) { + this.attributesMap.add(this.annotationType, this.attributes); + } else { + attributeList.add(0, this.attributes); + } + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) { + try { + Annotation[] metaAnnotations = annotationClass.getAnnotations(); + if (!ObjectUtils.isEmpty(metaAnnotations)) { + Set visited = new LinkedHashSet<>(); + for (Annotation metaAnnotation : metaAnnotations) { + recursivelyCollectMetaAnnotations(visited, metaAnnotation); + } + if (!visited.isEmpty()) { + Set metaAnnotationTypeNames = new LinkedHashSet<>(visited.size()); + for (Annotation ann : visited) { + metaAnnotationTypeNames.add(ann.annotationType().getName()); + } + this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); + } + } + } catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex); + } + } + } + } + } + + private void recursivelyCollectMetaAnnotations(Set visited, Annotation annotation) { + Class annotationType = annotation.annotationType(); + String annotationName = annotationType.getName(); + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) { + try { + // Only do attribute scanning for public annotations; we'd run into + // IllegalAccessExceptions otherwise, and we don't want to mess with + // accessibility in a SecurityManager environment. + if (Modifier.isPublic(annotationType.getModifiers())) { + this.attributesMap.add(annotationName, AnnotationUtils.getAnnotationAttributes(annotation, false, true)); + } + for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) { + recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation); + } + } catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to introspect meta-annotations on " + annotation + ": " + ex); + } + } + } + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/AnnotationMetadataReadingVisitor.java b/src/main/java/org/springframework/data/type/classreading/AnnotationMetadataReadingVisitor.java new file mode 100644 index 000000000..a51aa48fd --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/AnnotationMetadataReadingVisitor.java @@ -0,0 +1,190 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * ASM class visitor which looks for the class name and implemented types as well as for the annotations defined on the + * class, exposing them through the {@link org.springframework.core.type.AnnotationMetadata} interface. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Costin Leau + * @author Phillip Webb + * @author Sam Brannen + */ +@Deprecated +class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { + + @Nullable protected final ClassLoader classLoader; + + protected final Set annotationSet = new LinkedHashSet<>(4); + + protected final Map> metaAnnotationMap = new LinkedHashMap<>(4); + + /** + * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap} to ensure that the hierarchical + * ordering of the entries is preserved. + * + * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes + */ + protected final LinkedMultiValueMap attributesMap = new LinkedMultiValueMap<>(3); + + protected final Set methodMetadataSet = new LinkedHashSet<>(4); + + public AnnotationMetadataReadingVisitor(@Nullable ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public MergedAnnotations getAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // Skip bridge methods - we're only interested in original annotation-defining user methods. + // On JDK 8, we'd otherwise run into double detection of the same annotated method... + if ((access & Opcodes.ACC_BRIDGE) != 0) { + return super.visitMethod(access, name, desc, signature, exceptions); + } + return new MethodMetadataReadingVisitor(name, access, getClassName(), Type.getReturnType(desc).getClassName(), + this.classLoader, this.methodMetadataSet); + } + + @Override + @Nullable + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (!visible) { + return null; + } + String className = Type.getType(desc).getClassName(); + if (AnnotationUtils.isInJavaLangAnnotationPackage(className)) { + return null; + } + this.annotationSet.add(className); + return new AnnotationAttributesReadingVisitor(className, this.attributesMap, this.metaAnnotationMap, + this.classLoader); + } + + @Override + public Set getAnnotationTypes() { + return this.annotationSet; + } + + @Override + public Set getMetaAnnotationTypes(String annotationName) { + Set metaAnnotationTypes = this.metaAnnotationMap.get(annotationName); + return (metaAnnotationTypes != null ? metaAnnotationTypes : Collections.emptySet()); + } + + @Override + public boolean hasMetaAnnotation(String metaAnnotationType) { + if (AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotationType)) { + return false; + } + Collection> allMetaTypes = this.metaAnnotationMap.values(); + for (Set metaTypes : allMetaTypes) { + if (metaTypes.contains(metaAnnotationType)) { + return true; + } + } + return false; + } + + @Override + public boolean isAnnotated(String annotationName) { + return (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) + && this.attributesMap.containsKey(annotationName)); + } + + @Override + public boolean hasAnnotation(String annotationName) { + return getAnnotationTypes().contains(annotationName); + } + + @Override + @Nullable + public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(this.attributesMap, + this.metaAnnotationMap, annotationName); + if (raw == null) { + return null; + } + return AnnotationReadingVisitorUtils.convertClassValues("class '" + getClassName() + "'", this.classLoader, raw, + classValuesAsString); + } + + @Override + @Nullable + public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { + MultiValueMap allAttributes = new LinkedMultiValueMap<>(); + List attributes = this.attributesMap.get(annotationName); + if (attributes == null) { + return null; + } + String annotatedElement = "class '" + getClassName() + "'"; + for (AnnotationAttributes raw : attributes) { + for (Map.Entry entry : AnnotationReadingVisitorUtils + .convertClassValues(annotatedElement, this.classLoader, raw, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); + } + } + return allAttributes; + } + + @Override + public boolean hasAnnotatedMethods(String annotationName) { + for (MethodMetadata methodMetadata : this.methodMetadataSet) { + if (methodMetadata.isAnnotated(annotationName)) { + return true; + } + } + return false; + } + + @Override + public Set getAnnotatedMethods(String annotationName) { + Set annotatedMethods = new LinkedHashSet<>(4); + for (MethodMetadata methodMetadata : this.methodMetadataSet) { + if (methodMetadata.isAnnotated(annotationName)) { + annotatedMethods.add(methodMetadata); + } + } + return annotatedMethods; + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/AnnotationReadingVisitorUtils.java b/src/main/java/org/springframework/data/type/classreading/AnnotationReadingVisitorUtils.java new file mode 100644 index 000000000..eb000ec6a --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/AnnotationReadingVisitorUtils.java @@ -0,0 +1,160 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.ObjectUtils; + +/** + * Internal utility class used when reading annotations via ASM. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Costin Leau + * @author Phillip Webb + * @author Sam Brannen + * @since 4.0 + */ +@Deprecated +abstract class AnnotationReadingVisitorUtils { + + public static AnnotationAttributes convertClassValues(Object annotatedElement, @Nullable ClassLoader classLoader, + AnnotationAttributes original, boolean classValuesAsString) { + + AnnotationAttributes result = new AnnotationAttributes(original); + AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString); + + for (Map.Entry entry : result.entrySet()) { + try { + Object value = entry.getValue(); + if (value instanceof AnnotationAttributes) { + value = convertClassValues(annotatedElement, classLoader, (AnnotationAttributes) value, classValuesAsString); + } else if (value instanceof AnnotationAttributes[]) { + AnnotationAttributes[] values = (AnnotationAttributes[]) value; + for (int i = 0; i < values.length; i++) { + values[i] = convertClassValues(annotatedElement, classLoader, values[i], classValuesAsString); + } + value = values; + } else if (value instanceof Type) { + value = (classValuesAsString ? ((Type) value).getClassName() + : ClassUtils.forName(((Type) value).getClassName(), classLoader)); + } else if (value instanceof Type[]) { + Type[] array = (Type[]) value; + Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); + for (int i = 0; i < array.length; i++) { + convArray[i] = (classValuesAsString ? array[i].getClassName() + : ClassUtils.forName(array[i].getClassName(), classLoader)); + } + value = convArray; + } else if (classValuesAsString) { + if (value instanceof Class) { + value = ((Class) value).getName(); + } else if (value instanceof Class[]) { + Class[] clazzArray = (Class[]) value; + String[] newValue = new String[clazzArray.length]; + for (int i = 0; i < clazzArray.length; i++) { + newValue[i] = clazzArray[i].getName(); + } + value = newValue; + } + } + entry.setValue(value); + } catch (Throwable ex) { + // Class not found - can't resolve class reference in annotation attribute. + result.put(entry.getKey(), ex); + } + } + + return result; + } + + /** + * Retrieve the merged attributes of the annotation of the given type, if any, from the supplied + * {@code attributesMap}. + *

+ * Annotation attribute values appearing lower in the annotation hierarchy (i.e., closer to the declaring + * class) will override those defined higher in the annotation hierarchy. + * + * @param attributesMap the map of annotation attribute lists, keyed by annotation type name + * @param metaAnnotationMap the map of meta annotation relationships, keyed by annotation type name + * @param annotationName the fully qualified class name of the annotation type to look for + * @return the merged annotation attributes, or {@code null} if no matching annotation is present in the + * {@code attributesMap} + * @since 4.0.3 + */ + @Nullable + public static AnnotationAttributes getMergedAnnotationAttributes( + LinkedMultiValueMap attributesMap, Map> metaAnnotationMap, + String annotationName) { + + // Get the unmerged list of attributes for the target annotation. + List attributesList = attributesMap.get(annotationName); + if (CollectionUtils.isEmpty(attributesList)) { + return null; + } + + // To start with, we populate the result with a copy of all attribute values + // from the target annotation. A copy is necessary so that we do not + // inadvertently mutate the state of the metadata passed to this method. + AnnotationAttributes result = new AnnotationAttributes(attributesList.get(0)); + + Set overridableAttributeNames = new HashSet<>(result.keySet()); + overridableAttributeNames.remove(AnnotationUtils.VALUE); + + // Since the map is a LinkedMultiValueMap, we depend on the ordering of + // elements in the map and reverse the order of the keys in order to traverse + // "down" the annotation hierarchy. + List annotationTypes = new ArrayList<>(attributesMap.keySet()); + Collections.reverse(annotationTypes); + + // No need to revisit the target annotation type: + annotationTypes.remove(annotationName); + + for (String currentAnnotationType : annotationTypes) { + List currentAttributesList = attributesMap.get(currentAnnotationType); + if (!ObjectUtils.isEmpty(currentAttributesList)) { + Set metaAnns = metaAnnotationMap.get(currentAnnotationType); + if (metaAnns != null && metaAnns.contains(annotationName)) { + AnnotationAttributes currentAttributes = currentAttributesList.get(0); + for (String overridableAttributeName : overridableAttributeNames) { + Object value = currentAttributes.get(overridableAttributeName); + if (value != null) { + // Store the value, potentially overriding a value from an attribute + // of the same name found higher in the annotation hierarchy. + result.put(overridableAttributeName, value); + } + } + } + } + } + + return result; + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/ClassMetadataReadingVisitor.java b/src/main/java/org/springframework/data/type/classreading/ClassMetadataReadingVisitor.java new file mode 100644 index 000000000..6dfeee7e8 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/ClassMetadataReadingVisitor.java @@ -0,0 +1,230 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Attribute; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.core.type.ClassMetadata; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * ASM class visitor which looks only for the class name and implemented types, exposing them through the + * {@link org.springframework.core.type.ClassMetadata} interface. + * + * @author Rod Johnson + * @author Costin Leau + * @author Mark Fisher + * @author Ramnivas Laddad + * @author Chris Beams + * @since 2.5 + */ +@Deprecated +class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata { + + private String className = ""; + + private boolean isInterface; + + private boolean isAnnotation; + + private boolean isAbstract; + + private boolean isFinal; + + @Nullable private String enclosingClassName; + + private boolean independentInnerClass; + + @Nullable private String superClassName; + + private String[] interfaces = new String[0]; + + private Set memberClassNames = new LinkedHashSet<>(4); + + public ClassMetadataReadingVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + + @Override + public void visit(int version, int access, String name, String signature, @Nullable String supername, + String[] interfaces) { + + this.className = ClassUtils.convertResourcePathToClassName(name); + this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); + this.isAnnotation = ((access & Opcodes.ACC_ANNOTATION) != 0); + this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0); + this.isFinal = ((access & Opcodes.ACC_FINAL) != 0); + if (supername != null && !this.isInterface) { + this.superClassName = ClassUtils.convertResourcePathToClassName(supername); + } + this.interfaces = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + this.interfaces[i] = ClassUtils.convertResourcePathToClassName(interfaces[i]); + } + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner); + } + + @Override + public void visitInnerClass(String name, @Nullable String outerName, String innerName, int access) { + if (outerName != null) { + String fqName = ClassUtils.convertResourcePathToClassName(name); + String fqOuterName = ClassUtils.convertResourcePathToClassName(outerName); + if (this.className.equals(fqName)) { + this.enclosingClassName = fqOuterName; + this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0); + } else if (this.className.equals(fqOuterName)) { + this.memberClassNames.add(fqName); + } + } + } + + @Override + public void visitSource(String source, String debug) { + // no-op + } + + @Override + @Nullable + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // no-op + return new EmptyAnnotationVisitor(); + } + + @Override + public void visitAttribute(Attribute attr) { + // no-op + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + // no-op + return new EmptyFieldVisitor(); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // no-op + return new EmptyMethodVisitor(); + } + + @Override + public void visitEnd() { + // no-op + } + + @Override + public String getClassName() { + return this.className; + } + + @Override + public boolean isInterface() { + return this.isInterface; + } + + @Override + public boolean isAnnotation() { + return this.isAnnotation; + } + + @Override + public boolean isAbstract() { + return this.isAbstract; + } + + @Override + public boolean isFinal() { + return this.isFinal; + } + + @Override + public boolean isIndependent() { + return (this.enclosingClassName == null || this.independentInnerClass); + } + + @Override + public boolean hasEnclosingClass() { + return (this.enclosingClassName != null); + } + + @Override + @Nullable + public String getEnclosingClassName() { + return this.enclosingClassName; + } + + @Override + @Nullable + public String getSuperClassName() { + return this.superClassName; + } + + @Override + public String[] getInterfaceNames() { + return this.interfaces; + } + + @Override + public String[] getMemberClassNames() { + return StringUtils.toStringArray(this.memberClassNames); + } + + private static class EmptyAnnotationVisitor extends AnnotationVisitor { + + public EmptyAnnotationVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + return this; + } + + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } + } + + private static class EmptyMethodVisitor extends MethodVisitor { + + public EmptyMethodVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + } + + private static class EmptyFieldVisitor extends FieldVisitor { + + public EmptyFieldVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java b/src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java index b431aaaeb..782aa913e 100644 --- a/src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java +++ b/src/main/java/org/springframework/data/type/classreading/DefaultMethodsMetadataReader.java @@ -30,8 +30,6 @@ import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.MethodMetadata; -import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor; -import org.springframework.core.type.classreading.MethodMetadataReadingVisitor; import org.springframework.data.type.MethodsMetadata; import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; diff --git a/src/main/java/org/springframework/data/type/classreading/MethodMetadataReadingVisitor.java b/src/main/java/org/springframework/data/type/classreading/MethodMetadataReadingVisitor.java new file mode 100644 index 000000000..20ad05957 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/MethodMetadataReadingVisitor.java @@ -0,0 +1,165 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * ASM method visitor which looks for the annotations defined on a method, exposing them through the + * {@link org.springframework.core.type.MethodMetadata} interface. + * + * @author Juergen Hoeller + * @author Mark Pollack + * @author Costin Leau + * @author Chris Beams + * @author Phillip Webb + * @since 3.0 + */ +@Deprecated +class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { + + protected final String methodName; + + protected final int access; + + protected final String declaringClassName; + + protected final String returnTypeName; + + @Nullable protected final ClassLoader classLoader; + + protected final Set methodMetadataSet; + + protected final Map> metaAnnotationMap = new LinkedHashMap<>(4); + + protected final LinkedMultiValueMap attributesMap = new LinkedMultiValueMap<>(3); + + public MethodMetadataReadingVisitor(String methodName, int access, String declaringClassName, String returnTypeName, + @Nullable ClassLoader classLoader, Set methodMetadataSet) { + + super(SpringAsmInfo.ASM_VERSION); + this.methodName = methodName; + this.access = access; + this.declaringClassName = declaringClassName; + this.returnTypeName = returnTypeName; + this.classLoader = classLoader; + this.methodMetadataSet = methodMetadataSet; + } + + @Override + public MergedAnnotations getAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override + @Nullable + public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { + if (!visible) { + return null; + } + this.methodMetadataSet.add(this); + String className = Type.getType(desc).getClassName(); + return new AnnotationAttributesReadingVisitor(className, this.attributesMap, this.metaAnnotationMap, + this.classLoader); + } + + @Override + public String getMethodName() { + return this.methodName; + } + + @Override + public boolean isAbstract() { + return ((this.access & Opcodes.ACC_ABSTRACT) != 0); + } + + @Override + public boolean isStatic() { + return ((this.access & Opcodes.ACC_STATIC) != 0); + } + + @Override + public boolean isFinal() { + return ((this.access & Opcodes.ACC_FINAL) != 0); + } + + @Override + public boolean isOverridable() { + return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0)); + } + + @Override + public boolean isAnnotated(String annotationName) { + return this.attributesMap.containsKey(annotationName); + } + + @Override + @Nullable + public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(this.attributesMap, + this.metaAnnotationMap, annotationName); + if (raw == null) { + return null; + } + return AnnotationReadingVisitorUtils.convertClassValues("method '" + getMethodName() + "'", this.classLoader, raw, + classValuesAsString); + } + + @Override + @Nullable + public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { + if (!this.attributesMap.containsKey(annotationName)) { + return null; + } + MultiValueMap allAttributes = new LinkedMultiValueMap<>(); + List attributesList = this.attributesMap.get(annotationName); + if (attributesList != null) { + String annotatedElement = "method '" + getMethodName() + '\''; + for (AnnotationAttributes annotationAttributes : attributesList) { + AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(annotatedElement, + this.classLoader, annotationAttributes, classValuesAsString); + convertedAttributes.forEach(allAttributes::add); + } + } + return allAttributes; + } + + @Override + public String getDeclaringClassName() { + return this.declaringClassName; + } + + @Override + public String getReturnTypeName() { + return this.returnTypeName; + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationArrayVisitor.java b/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationArrayVisitor.java new file mode 100644 index 000000000..f28fd2b02 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationArrayVisitor.java @@ -0,0 +1,104 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * {@link AnnotationVisitor} to recursively visit annotation arrays. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + * @deprecated As of Spring Framework 5.2, this class and related classes in this package have been replaced by + * {@link SimpleAnnotationMetadataReadingVisitor} and related classes for internal use within the framework. + */ +@Deprecated +class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor { + + private final String attributeName; + + private final List allNestedAttributes = new ArrayList<>(); + + public RecursiveAnnotationArrayVisitor(String attributeName, AnnotationAttributes attributes, + @Nullable ClassLoader classLoader) { + + super(classLoader, attributes); + this.attributeName = attributeName; + } + + @Override + public void visit(String attributeName, Object attributeValue) { + Object newValue = attributeValue; + Object existingValue = this.attributes.get(this.attributeName); + if (existingValue != null) { + newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); + } else { + Class arrayClass = newValue.getClass(); + if (Enum.class.isAssignableFrom(arrayClass)) { + while (arrayClass.getSuperclass() != null && !arrayClass.isEnum()) { + arrayClass = arrayClass.getSuperclass(); + } + } + Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1); + newArray[0] = newValue; + newValue = newArray; + } + this.attributes.put(this.attributeName, newValue); + } + + @Override + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader); + this.allNestedAttributes.add(nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); + } + + @Override + public void visitEnd() { + if (!this.allNestedAttributes.isEmpty()) { + this.attributes.put(this.attributeName, this.allNestedAttributes.toArray(new AnnotationAttributes[0])); + } else if (!this.attributes.containsKey(this.attributeName)) { + Class annotationType = this.attributes.annotationType(); + if (annotationType != null) { + try { + Class attributeType = annotationType.getMethod(this.attributeName).getReturnType(); + if (attributeType.isArray()) { + Class elementType = attributeType.getComponentType(); + if (elementType.isAnnotation()) { + elementType = AnnotationAttributes.class; + } + this.attributes.put(this.attributeName, Array.newInstance(elementType, 0)); + } + } catch (NoSuchMethodException ex) { + // Corresponding attribute method not found: cannot expose empty array. + } + } + } + } + +} diff --git a/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationAttributesVisitor.java b/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationAttributesVisitor.java new file mode 100644 index 000000000..e57cf8672 --- /dev/null +++ b/src/main/java/org/springframework/data/type/classreading/RecursiveAnnotationAttributesVisitor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021 the original author or 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 + * + * https://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 org.springframework.data.type.classreading; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor; +import org.springframework.lang.Nullable; + +/** + * {@link AnnotationVisitor} to recursively visit annotation attributes. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + * @deprecated As of Spring Framework 5.2, this class and related classes in this package have been replaced by + * {@link SimpleAnnotationMetadataReadingVisitor} and related classes for internal use within the framework. + */ +@Deprecated +class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor { + + protected final String annotationType; + + public RecursiveAnnotationAttributesVisitor(String annotationType, AnnotationAttributes attributes, + @Nullable ClassLoader classLoader) { + + super(classLoader, attributes); + this.annotationType = annotationType; + } + + @Override + public void visitEnd() { + AnnotationUtils.registerDefaultValues(this.attributes); + } + +}