diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java index 4bf19742dd..a4a9d0baa7 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -39,6 +39,7 @@ import org.springframework.util.ReflectionUtils; * @author Sam Brannen * @since 3.1.1 */ +@Deprecated abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { protected final Log logger = LogFactory.getLog(getClass()); diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 468486d6e9..5e9f357bcf 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -42,6 +42,7 @@ import org.springframework.util.ObjectUtils; * @author Sam Brannen * @since 3.0 */ +@Deprecated final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { private final MultiValueMap attributesMap; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 5564e83288..93ca1bbedc 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -49,6 +49,7 @@ import org.springframework.util.MultiValueMap; * @author Sam Brannen * @since 2.5 */ +@Deprecated public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { @Nullable diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java index db814e4a68..3c2ca2dc4c 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java @@ -42,6 +42,7 @@ import org.springframework.util.ObjectUtils; * @author Sam Brannen * @since 4.0 */ +@Deprecated abstract class AnnotationReadingVisitorUtils { public static AnnotationAttributes convertClassValues(Object annotatedElement, diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java index e9d760ef7f..a9df087633 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -43,6 +43,7 @@ import org.springframework.util.StringUtils; * @author Chris Beams * @since 2.5 */ +@Deprecated class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata { private String className = ""; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MergedAnnotationReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MergedAnnotationReadingVisitor.java new file mode 100644 index 0000000000..eff8d5a16d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MergedAnnotationReadingVisitor.java @@ -0,0 +1,204 @@ +/* + * Copyright 2002-2019 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.core.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationFilter; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * {@link AnnotationVisitor} that can be used to construct a + * {@link MergedAnnotation}. + * + * @author Phillip Webb + * @since 5.2 + * @param the annotation type + */ +class MergedAnnotationReadingVisitor extends AnnotationVisitor { + + @Nullable + private final ClassLoader classLoader; + + @Nullable + private final Object source; + + private final Class annotationType; + + private final Consumer> consumer; + + private final Map attributes = new LinkedHashMap<>(4); + + public MergedAnnotationReadingVisitor(ClassLoader classLoader, + @Nullable Object source, Class annotationType, + Consumer> consumer) { + super(SpringAsmInfo.ASM_VERSION); + this.classLoader = classLoader; + this.source = source; + this.annotationType = annotationType; + this.consumer = consumer; + } + + @Override + public void visit(String name, Object value) { + if (value instanceof Type) { + value = ((Type) value).getClassName(); + } + this.attributes.put(name, value); + } + + @Override + public void visitEnum(String name, String descriptor, String value) { + visitEnum(descriptor, value, enumValue -> this.attributes.put(name, enumValue)); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + return visitAnnotation(descriptor, + annotation -> this.attributes.put(name, annotation)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new ArrayVisitor(value -> this.attributes.put(name, value)); + } + + @Override + public void visitEnd() { + MergedAnnotation annotation = MergedAnnotation.of(this.classLoader, + this.source, this.annotationType, this.attributes); + this.consumer.accept(annotation); + } + + @SuppressWarnings("unchecked") + public > void visitEnum(String descriptor, String value, + Consumer consumer) { + + String className = Type.getType(descriptor).getClassName(); + Class type = (Class) ClassUtils.resolveClassName(className, this.classLoader); + E enumValue = Enum.valueOf(type, value); + if (enumValue != null) { + consumer.accept(enumValue); + } + } + + @SuppressWarnings("unchecked") + private AnnotationVisitor visitAnnotation(String descriptor, + Consumer> consumer) { + + String className = Type.getType(descriptor).getClassName(); + if (AnnotationFilter.PLAIN.matches(className)) { + return null; + } + Class type = (Class) ClassUtils.resolveClassName(className, + this.classLoader); + return new MergedAnnotationReadingVisitor<>(this.classLoader, this.source, type, + consumer); + } + + @Nullable + @SuppressWarnings("unchecked") + static AnnotationVisitor get(@Nullable ClassLoader classLoader, + @Nullable Supplier sourceSupplier, String descriptor, boolean visible, + Consumer> consumer) { + if (!visible) { + return null; + } + String typeName = Type.getType(descriptor).getClassName(); + if (AnnotationFilter.PLAIN.matches(typeName)) { + return null; + } + Object source = sourceSupplier != null ? sourceSupplier.get() : null; + try { + Class annotationType = (Class) ClassUtils.forName(typeName, + classLoader); + return new MergedAnnotationReadingVisitor<>(classLoader, source, + annotationType, consumer); + } + catch (ClassNotFoundException | LinkageError ex) { + return null; + } + } + + /** + * {@link AnnotationVisitor} to deal with array attributes. + */ + private class ArrayVisitor extends AnnotationVisitor { + + private final List elements = new ArrayList<>(); + + private final Consumer consumer; + + ArrayVisitor(Consumer consumer) { + super(SpringAsmInfo.ASM_VERSION); + this.consumer = consumer; + } + + @Override + public void visit(String name, Object value) { + if (value instanceof Type) { + value = ((Type) value).getClassName(); + } + this.elements.add(value); + } + + @Override + public void visitEnum(String name, String descriptor, String value) { + MergedAnnotationReadingVisitor.this.visitEnum(descriptor, value, + enumValue -> this.elements.add(enumValue)); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + return MergedAnnotationReadingVisitor.this.visitAnnotation(descriptor, + annotation -> this.elements.add(annotation)); + } + + @Override + public void visitEnd() { + Class componentType = getComponentType(); + Object[] array = (Object[]) Array.newInstance(componentType, + this.elements.size()); + this.consumer.accept(this.elements.toArray(array)); + } + + private Class getComponentType() { + if (this.elements.isEmpty()) { + return Object.class; + } + Object firstElement = this.elements.get(0); + if(firstElement instanceof Enum) { + return ((Enum) firstElement).getDeclaringClass(); + } + return firstElement.getClass(); + } + + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index dba25e94f4..fce67facd8 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -45,6 +45,7 @@ import org.springframework.util.MultiValueMap; * @author Phillip Webb * @since 3.0 */ +@Deprecated public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { protected final String methodName; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java index 4b9d90bb91..05efeb0d50 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java @@ -34,6 +34,7 @@ import org.springframework.util.ObjectUtils; * @author Juergen Hoeller * @since 3.1.1 */ +@Deprecated class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor { private final String attributeName; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java index 88c00c5a32..fcc30c89cc 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -28,6 +28,7 @@ import org.springframework.lang.Nullable; * @author Juergen Hoeller * @since 3.1.1 */ +@Deprecated class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor { protected final String annotationType; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java new file mode 100644 index 0000000000..923aaf0f37 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2019 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.core.type.classreading; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.asm.Opcodes; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; + +/** + * {@link AnnotationMetadata} created from a + * {@link SimpleAnnotationMetadataReadingVistor}. + * + * @author Phillip Webb + * @since 5.2 + */ +final class SimpleAnnotationMetadata implements AnnotationMetadata { + + private final String className; + + private final int access; + + private final String enclosingClassName; + + private final String superClassName; + + private final boolean independentInnerClass; + + private final String[] interfaceNames; + + private final String[] memberClassNames; + + private final MethodMetadata[] annotatedMethods; + + private final MergedAnnotations annotations; + + private Set annotationTypes; + + SimpleAnnotationMetadata(String className, int access, String enclosingClassName, + String superClassName, boolean independentInnerClass, String[] interfaceNames, + String[] memberClassNames, MethodMetadata[] annotatedMethods, + MergedAnnotations annotations) { + this.className = className; + this.access = access; + this.enclosingClassName = enclosingClassName; + this.superClassName = superClassName; + this.independentInnerClass = independentInnerClass; + this.interfaceNames = interfaceNames; + this.memberClassNames = memberClassNames; + this.annotatedMethods = annotatedMethods; + this.annotations = annotations; + } + + @Override + public String getClassName() { + return this.className; + } + + @Override + public boolean isInterface() { + return (this.access & Opcodes.ACC_INTERFACE) != 0; + } + + @Override + public boolean isAnnotation() { + return (this.access & Opcodes.ACC_ANNOTATION) != 0; + } + + @Override + public boolean isAbstract() { + return (this.access & Opcodes.ACC_ABSTRACT) != 0; + } + + @Override + public boolean isFinal() { + return (this.access & Opcodes.ACC_FINAL) != 0; + } + + @Override + public boolean isIndependent() { + return this.enclosingClassName == null || this.independentInnerClass; + } + + @Override + @Nullable + public String getEnclosingClassName() { + return this.enclosingClassName; + } + + @Override + @Nullable + public String getSuperClassName() { + return this.superClassName; + } + + @Override + public String[] getInterfaceNames() { + return this.interfaceNames.clone(); + } + + @Override + public String[] getMemberClassNames() { + return this.memberClassNames.clone(); + } + + @Override + public Set getAnnotationTypes() { + Set annotationTypes = this.annotationTypes; + if (annotationTypes == null) { + annotationTypes = Collections.unmodifiableSet( + AnnotationMetadata.super.getAnnotationTypes()); + this.annotationTypes = annotationTypes; + } + return annotationTypes; + } + + @Override + public Set getAnnotatedMethods(String annotationName) { + Set annotatedMethods = null; + for (int i = 0; i < this.annotatedMethods.length; i++) { + if (this.annotatedMethods[i].isAnnotated(annotationName)) { + if (annotatedMethods == null) { + annotatedMethods = new LinkedHashSet<>(4); + } + annotatedMethods.add(this.annotatedMethods[i]); + } + } + return annotatedMethods != null ? annotatedMethods : Collections.emptySet(); + } + + @Override + public MergedAnnotations getAnnotations() { + return this.annotations; + } + + + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVistor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVistor.java new file mode 100644 index 0000000000..6b0f2c4b60 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVistor.java @@ -0,0 +1,197 @@ +/* + * Copyright 2002-2019 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.core.type.classreading; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * ASM class visitor to create {@link SimpleAnnotationMetadata}. + * + * @author Phillip Webb + * @since 5.2 + */ +class SimpleAnnotationMetadataReadingVistor extends ClassVisitor { + + @Nullable + private final ClassLoader classLoader; + + private String className = ""; + + private int access; + + private String superClassName; + + private String[] interfaceNames; + + private String enclosingClassName; + + private boolean independentInnerClass; + + private Set memberClassNames = new LinkedHashSet<>(4); + + private List> annotations = new ArrayList<>(); + + private List annotatedMethods = new ArrayList<>(); + + private SimpleAnnotationMetadata metadata; + + private Source source; + + SimpleAnnotationMetadataReadingVistor(@Nullable ClassLoader classLoader) { + super(SpringAsmInfo.ASM_VERSION); + this.classLoader = classLoader; + } + + @Override + public void visit(int version, int access, String name, String signature, + @Nullable String supername, String[] interfaces) { + this.className = toClassName(name); + this.access = access; + if (supername != null && !isInterface(access)) { + this.superClassName = toClassName(supername); + } + this.interfaceNames = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + this.interfaceNames[i] = toClassName(interfaces[i]); + } + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + this.enclosingClassName = toClassName(owner); + } + + @Override + public void visitInnerClass(String name, @Nullable String outerName, String innerName, + int access) { + if (outerName != null) { + String className = toClassName(name); + String outerClassName = toClassName(outerName); + if (this.className.equals(className)) { + this.enclosingClassName = outerClassName; + this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0); + } + else if (this.className.equals(outerClassName)) { + this.memberClassNames.add(className); + } + } + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return MergedAnnotationReadingVisitor.get(this.classLoader, this::getSource, + descriptor, visible, this.annotations::add); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + 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 (isBridge(access)) { + return null; + } + return new SimpleMethodMetadataReadingVistor(this.classLoader, this.className, + access, name, descriptor, this.annotatedMethods::add); + } + + @Override + public void visitEnd() { + String[] memberClassNames = StringUtils.toStringArray(this.memberClassNames); + MethodMetadata[] annotatedMethods = this.annotatedMethods.toArray(new MethodMetadata[0]); + MergedAnnotations annotations = MergedAnnotations.of(this.annotations); + this.metadata = new SimpleAnnotationMetadata(this.className, this.access, + this.enclosingClassName, this.superClassName, this.independentInnerClass, + this.interfaceNames, memberClassNames, annotatedMethods, annotations); + } + + public SimpleAnnotationMetadata getMetadata() { + return this.metadata; + } + + private Source getSource() { + Source source = this.source; + if (source == null) { + source = new Source(this.className); + this.source = source; + } + return source; + } + + private String toClassName(String name) { + return ClassUtils.convertResourcePathToClassName(name); + } + + private boolean isBridge(int access) { + return (access & Opcodes.ACC_BRIDGE) != 0; + } + + private boolean isInterface(int access) { + return (access & Opcodes.ACC_INTERFACE) != 0; + } + + /** + * {@link MergedAnnotation} source. + */ + private static final class Source { + + private final String className; + + Source(String className) { + this.className = className; + } + + @Override + public int hashCode() { + return this.className.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return this.className.equals(((Source) obj).className); + } + + @Override + public String toString() { + return this.className; + } + + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index 62f41c2ccf..188fedeab0 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -31,45 +31,39 @@ import org.springframework.lang.Nullable; * {@link MetadataReader} implementation based on an ASM * {@link org.springframework.asm.ClassReader}. * - *

Package-visible in order to allow for repackaging the ASM library - * without effect on users of the {@code core.type} package. - * * @author Juergen Hoeller * @author Costin Leau * @since 2.5 */ final class SimpleMetadataReader implements MetadataReader { - private final Resource resource; + private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG + | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES; - private final ClassMetadata classMetadata; + private final Resource resource; private final AnnotationMetadata annotationMetadata; SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { - InputStream is = new BufferedInputStream(resource.getInputStream()); - ClassReader classReader; - try { - classReader = new ClassReader(is); - } - catch (IllegalArgumentException ex) { - throw new NestedIOException("ASM ClassReader failed to parse class file - " + - "probably due to a new Java class file version that isn't supported yet: " + resource, ex); - } - finally { - is.close(); - } - - AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader); - classReader.accept(visitor, ClassReader.SKIP_DEBUG); - - this.annotationMetadata = visitor; - // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor) - this.classMetadata = visitor; + SimpleAnnotationMetadataReadingVistor visitor = new SimpleAnnotationMetadataReadingVistor(classLoader); + getClassReader(resource).accept(visitor, PARSING_OPTIONS); this.resource = resource; + this.annotationMetadata = visitor.getMetadata(); } + private ClassReader getClassReader(Resource resource) + throws IOException, NestedIOException { + try (InputStream is = new BufferedInputStream(resource.getInputStream())) { + try { + return new ClassReader(is); + } + catch (IllegalArgumentException ex) { + throw new NestedIOException("ASM ClassReader failed to parse class file - " + + "probably due to a new Java class file version that isn't supported yet: " + resource, ex); + } + } + } @Override public Resource getResource() { @@ -78,7 +72,7 @@ final class SimpleMetadataReader implements MetadataReader { @Override public ClassMetadata getClassMetadata() { - return this.classMetadata; + return this.annotationMetadata; } @Override diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadata.java new file mode 100644 index 0000000000..3f165483c3 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadata.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2019 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.core.type.classreading; + +import org.springframework.asm.Opcodes; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; + +/** + * {@link MethodMetadata} created from a + * {@link SimpleMethodMetadataReadingVistor}. + * + * @author Phillip Webb + * @since 5.2 + */ +final class SimpleMethodMetadata implements MethodMetadata { + + private final String methodName; + + private final int access; + + private final String declaringClassName; + + private final String returnTypeName; + + private final MergedAnnotations annotations; + + public SimpleMethodMetadata(String methodName, int access, String declaringClassName, + String returnTypeName, MergedAnnotations annotations) { + this.methodName = methodName; + this.access = access; + this.declaringClassName = declaringClassName; + this.returnTypeName = returnTypeName; + this.annotations = annotations; + } + + @Override + public String getMethodName() { + return this.methodName; + } + + @Override + public String getDeclaringClassName() { + return this.declaringClassName; + } + + @Override + public String getReturnTypeName() { + return this.returnTypeName; + } + + @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() && !isPrivate(); + } + + public boolean isPrivate() { + return (this.access & Opcodes.ACC_PRIVATE) != 0; + } + + @Override + public MergedAnnotations getAnnotations() { + return this.annotations; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadataReadingVistor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadataReadingVistor.java new file mode 100644 index 0000000000..dcf9d38dc4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMethodMetadataReadingVistor.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-2019 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.core.type.classreading; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; + +/** + * {@link MethodMetadata} returned from a {@link SimpleMetadataReader}. + * + * @author Phillip Webb + * @since 5.2 + */ +class SimpleMethodMetadataReadingVistor extends MethodVisitor { + + @Nullable + private final ClassLoader classLoader; + + private final String declaringClassName; + + private final int access; + + private final String name; + + private final String descriptor; + + private final List> annotations = new ArrayList>( + 4); + + private final Consumer consumer; + + private Source source; + + SimpleMethodMetadataReadingVistor(@Nullable ClassLoader classLoader, + String declaringClassName, int access, String name, String descriptor, + Consumer consumer) { + super(SpringAsmInfo.ASM_VERSION); + this.classLoader = classLoader; + this.declaringClassName = declaringClassName; + this.access = access; + this.name = name; + this.descriptor = descriptor; + this.consumer = consumer; + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return MergedAnnotationReadingVisitor.get(this.classLoader, this::getSource, + descriptor, visible, this.annotations::add); + } + + @Override + public void visitEnd() { + if (!this.annotations.isEmpty()) { + String returnTypeName = Type.getReturnType(this.descriptor).getClassName(); + MergedAnnotations annotations = MergedAnnotations.of(this.annotations); + SimpleMethodMetadata metadata = new SimpleMethodMetadata(this.name, + this.access, this.declaringClassName, returnTypeName, annotations); + this.consumer.accept(metadata); + } + } + + private Object getSource() { + Source source = this.source; + if (source == null) { + source = new Source(this.declaringClassName, this.name, this.descriptor); + this.source = source; + } + return source; + } + + /** + * {@link MergedAnnotation} source. + */ + static final class Source { + + private final String declaringClassName; + + private final String name; + + private final String descriptor; + + private String string; + + Source(String declaringClassName, String name, String descriptor) { + this.declaringClassName = declaringClassName; + this.name = name; + this.descriptor = descriptor; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + this.declaringClassName.hashCode(); + result = 31 * result + this.name.hashCode(); + result = 31 * result + this.descriptor.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Source other = (Source) obj; + boolean result = true; + result = result &= this.declaringClassName.equals(other.declaringClassName); + result = result &= this.name.equals(other.name); + result = result &= this.descriptor.equals(other.descriptor); + return result; + } + + @Override + public String toString() { + String string = this.string; + if (string == null) { + StringBuilder builder = new StringBuilder(); + builder.append(this.declaringClassName); + builder.append("."); + builder.append(this.name); + Type[] argumentTypes = Type.getArgumentTypes(this.descriptor); + builder.append("("); + for (Type type : argumentTypes) { + builder.append(type.getClassName()); + } + builder.append(")"); + string = builder.toString(); + this.string = string; + } + return string; + } + + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitorTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitorTests.java index ef9832b13b..804ed66923 100644 --- a/spring-core/src/test/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitorTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitorTests.java @@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.*; * * @author Phillip Webb */ +@SuppressWarnings("deprecation") public class AnnotationMetadataReadingVisitorTests extends AbstractAnnotationMetadataTests { diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/MergedAnnotationMetadataVisitorTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/MergedAnnotationMetadataVisitorTests.java new file mode 100644 index 0000000000..39732cee77 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/MergedAnnotationMetadataVisitorTests.java @@ -0,0 +1,264 @@ +/* + * Copyright 2002-2019 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 + * + * 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 org.springframework.core.type.classreading; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.core.annotation.MergedAnnotation; + +import static org.assertj.core.api.Assertions.*; + +/** + * Tests for {@link MergedAnnotationReadingVisitor}. + * + * @author Phillip Webb + */ +public class MergedAnnotationMetadataVisitorTests { + + private MergedAnnotation annotation; + + @Test + public void visitWhenHasSimpleTypesCreatesAnnotation() { + loadFrom(WithSimpleTypesAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(SimpleTypesAnnotation.class); + assertThat(this.annotation.getValue("stringValue")).contains("string"); + assertThat(this.annotation.getValue("byteValue")).contains((byte) 1); + assertThat(this.annotation.getValue("shortValue")).contains((short) 2); + assertThat(this.annotation.getValue("intValue")).contains(3); + assertThat(this.annotation.getValue("longValue")).contains(4L); + assertThat(this.annotation.getValue("booleanValue")).contains(true); + assertThat(this.annotation.getValue("charValue")).contains('c'); + assertThat(this.annotation.getValue("doubleValue")).contains(5.0); + assertThat(this.annotation.getValue("floatValue")).contains(6.0f); + } + + @Test + public void visitWhenHasSimpleArrayTypesCreatesAnnotation() { + loadFrom(WithSimpleArrayTypesAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(SimpleArrayTypesAnnotation.class); + assertThat(this.annotation.getValue("stringValue")).contains( + new String[] { "string" }); + assertThat(this.annotation.getValue("byteValue")).contains(new byte[] { 1 }); + assertThat(this.annotation.getValue("shortValue")).contains(new short[] { 2 }); + assertThat(this.annotation.getValue("intValue")).contains(new int[] { 3 }); + assertThat(this.annotation.getValue("longValue")).contains(new long[] { 4 }); + assertThat(this.annotation.getValue("booleanValue")).contains( + new boolean[] { true }); + assertThat(this.annotation.getValue("charValue")).contains(new char[] { 'c' }); + assertThat(this.annotation.getValue("doubleValue")).contains( + new double[] { 5.0 }); + assertThat(this.annotation.getValue("floatValue")).contains(new float[] { 6.0f }); + } + + @Test + public void visitWhenHasEmptySimpleArrayTypesCreatesAnnotation() { + loadFrom(WithSimpleEmptyArrayTypesAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(SimpleArrayTypesAnnotation.class); + assertThat(this.annotation.getValue("stringValue")).contains(new String[] {}); + assertThat(this.annotation.getValue("byteValue")).contains(new byte[] {}); + assertThat(this.annotation.getValue("shortValue")).contains(new short[] {}); + assertThat(this.annotation.getValue("intValue")).contains(new int[] {}); + assertThat(this.annotation.getValue("longValue")).contains(new long[] {}); + assertThat(this.annotation.getValue("booleanValue")).contains(new boolean[] {}); + assertThat(this.annotation.getValue("charValue")).contains(new char[] {}); + assertThat(this.annotation.getValue("doubleValue")).contains(new double[] {}); + assertThat(this.annotation.getValue("floatValue")).contains(new float[] {}); + } + + @Test + public void visitWhenHasEnumAttributesCreatesAnnotation() { + loadFrom(WithEnumAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(EnumAnnotation.class); + assertThat(this.annotation.getValue("enumValue")).contains(ExampleEnum.ONE); + assertThat(this.annotation.getValue("enumArrayValue")).contains( + new ExampleEnum[] { ExampleEnum.ONE, ExampleEnum.TWO }); + } + + @Test + public void visitWhenHasAnnotationAttributesCreatesAnnotation() { + loadFrom(WithAnnotationAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(AnnotationAnnotation.class); + MergedAnnotation value = this.annotation.getAnnotation( + "annotationValue", NestedAnnotation.class); + assertThat(value.isPresent()).isTrue(); + assertThat(value.getString(MergedAnnotation.VALUE)).isEqualTo("a"); + MergedAnnotation[] arrayValue = this.annotation.getAnnotationArray( + "annotationArrayValue", NestedAnnotation.class); + assertThat(arrayValue).hasSize(2); + assertThat(arrayValue[0].getString(MergedAnnotation.VALUE)).isEqualTo("b"); + assertThat(arrayValue[1].getString(MergedAnnotation.VALUE)).isEqualTo("c"); + } + + @Test + public void visitWhenHasClassAttributesCreatesAnnotation() { + loadFrom(WithClassAnnotation.class); + assertThat(this.annotation.getType()).isEqualTo(ClassAnnotation.class); + assertThat(this.annotation.getString("classValue")).isEqualTo(InputStream.class.getName()); + assertThat(this.annotation.getClass("classValue")).isEqualTo(InputStream.class); + assertThat(this.annotation.getValue("classValue")).contains(InputStream.class); + assertThat(this.annotation.getStringArray("classArrayValue")).containsExactly(OutputStream.class.getName()); + assertThat(this.annotation.getValue("classArrayValue")).contains(new Class[] {OutputStream.class}); + } + + private void loadFrom(Class type) { + ClassVisitor visitor = new ClassVisitor(SpringAsmInfo.ASM_VERSION) { + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return MergedAnnotationReadingVisitor.get(getClass().getClassLoader(), + null, descriptor, visible, + annotation -> MergedAnnotationMetadataVisitorTests.this.annotation = annotation); + } + + }; + try { + new ClassReader(type.getName()).accept(visitor, ClassReader.SKIP_DEBUG + | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + @SimpleTypesAnnotation(stringValue = "string", byteValue = 1, shortValue = 2, intValue = 3, longValue = 4, booleanValue = true, charValue = 'c', doubleValue = 5.0, floatValue = 6.0f) + static class WithSimpleTypesAnnotation { + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface SimpleTypesAnnotation { + + String stringValue(); + + byte byteValue(); + + short shortValue(); + + int intValue(); + + long longValue(); + + boolean booleanValue(); + + char charValue(); + + double doubleValue(); + + float floatValue(); + + } + + @SimpleArrayTypesAnnotation(stringValue = "string", byteValue = 1, shortValue = 2, intValue = 3, longValue = 4, booleanValue = true, charValue = 'c', doubleValue = 5.0, floatValue = 6.0f) + static class WithSimpleArrayTypesAnnotation { + + } + + @SimpleArrayTypesAnnotation(stringValue = {}, byteValue = {}, shortValue = {}, intValue = {}, longValue = {}, booleanValue = {}, charValue = {}, doubleValue = {}, floatValue = {}) + static class WithSimpleEmptyArrayTypesAnnotation { + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface SimpleArrayTypesAnnotation { + + String[] stringValue(); + + byte[] byteValue(); + + short[] shortValue(); + + int[] intValue(); + + long[] longValue(); + + boolean[] booleanValue(); + + char[] charValue(); + + double[] doubleValue(); + + float[] floatValue(); + + } + + @EnumAnnotation(enumValue = ExampleEnum.ONE, enumArrayValue = { ExampleEnum.ONE, + ExampleEnum.TWO }) + static class WithEnumAnnotation { + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface EnumAnnotation { + + ExampleEnum enumValue(); + + ExampleEnum[] enumArrayValue(); + + } + + enum ExampleEnum { + ONE, TWO, THREE + } + + @AnnotationAnnotation(annotationValue = @NestedAnnotation("a"), annotationArrayValue = { + @NestedAnnotation("b"), @NestedAnnotation("c") }) + static class WithAnnotationAnnotation { + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface AnnotationAnnotation { + + NestedAnnotation annotationValue(); + + NestedAnnotation[] annotationArrayValue(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface NestedAnnotation { + + String value() default ""; + + } + + @ClassAnnotation(classValue = InputStream.class, classArrayValue = OutputStream.class) + static class WithClassAnnotation { + + } + + + @Retention(RetentionPolicy.RUNTIME) + @interface ClassAnnotation { + + Class classValue(); + + Class[] classArrayValue(); + + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java new file mode 100644 index 0000000000..06a692c4ce --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2019 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 + * + * 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 org.springframework.core.type.classreading; + +import org.springframework.core.type.AbstractAnnotationMetadataTests; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + +/** + * Tests for {@link SimpleAnnotationMetadata} and + * {@link SimpleAnnotationMetadataReadingVistor}. + * + * @author Phillip Webb + */ +public class SimpleAnnotationMetadataTests extends AbstractAnnotationMetadataTests { + + @Override + protected AnnotationMetadata get(Class source) { + try { + return new SimpleMetadataReaderFactory( + source.getClassLoader()).getMetadataReader( + source.getName()).getAnnotationMetadata(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java new file mode 100644 index 0000000000..f99c0bffc4 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2019 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 + * + * 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 org.springframework.core.type.classreading; + +import org.springframework.core.type.AbstractMethodMetadataTests; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + +/** + * Tests for {@link SimpleMethodMetadata} and + * {@link SimpleMethodMetadataReadingVistor}. + * + * @author Phillip Webb + */ +public class SimpleMethodMetadataTests extends AbstractMethodMetadataTests { + + @Override + protected AnnotationMetadata get(Class source) { + try { + return new SimpleMetadataReaderFactory( + source.getClassLoader()).getMetadataReader( + source.getName()).getAnnotationMetadata(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + +}