13 changed files with 18 additions and 931 deletions
@ -1,39 +0,0 @@
@@ -1,39 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.core.io.ResourceLoader; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations. |
||||
* For JDK < 24, the {@link SimpleMetadataReaderFactory} is being used. |
||||
* |
||||
* @author Brian Clozel |
||||
* @since 7.0 |
||||
* @see MetadataReaderFactory |
||||
*/ |
||||
abstract class MetadataReaderFactoryDelegate { |
||||
|
||||
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { |
||||
return new SimpleMetadataReaderFactory(resourceLoader); |
||||
} |
||||
|
||||
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { |
||||
return new SimpleMetadataReaderFactory(classLoader); |
||||
} |
||||
} |
||||
@ -1,129 +0,0 @@
@@ -1,129 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.classfile.Annotation; |
||||
import java.lang.classfile.AnnotationElement; |
||||
import java.lang.classfile.AnnotationValue; |
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; |
||||
import java.lang.reflect.Array; |
||||
import java.util.ArrayList; |
||||
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.core.annotation.AnnotationFilter; |
||||
import org.springframework.core.annotation.MergedAnnotation; |
||||
import org.springframework.core.annotation.MergedAnnotations; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
abstract class ClassFileAnnotationMetadata { |
||||
|
||||
static MergedAnnotations createMergedAnnotations(String entryName, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) { |
||||
Set<MergedAnnotation<?>> annotations = new LinkedHashSet<>(4); |
||||
annotationAttribute.annotations().forEach(ann -> { |
||||
MergedAnnotation<java.lang.annotation.Annotation> mergedAnnotation = createMergedAnnotation(entryName, ann, classLoader); |
||||
if (mergedAnnotation != null) { |
||||
annotations.add(mergedAnnotation); |
||||
} |
||||
}); |
||||
return MergedAnnotations.of(annotations); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Nullable |
||||
private static <A extends java.lang.annotation.Annotation> MergedAnnotation<A> createMergedAnnotation(String entryName, Annotation annotation, @Nullable ClassLoader classLoader) { |
||||
String typeName = fromTypeDescriptor(annotation.className().stringValue()); |
||||
if (AnnotationFilter.PLAIN.matches(typeName)) { |
||||
return null; |
||||
} |
||||
Map<String, Object> attributes = new LinkedHashMap<>(4); |
||||
try { |
||||
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader); |
||||
for (AnnotationElement element : annotation.elements()) { |
||||
attributes.put(element.name().stringValue(), readAnnotationValue(element.value(), classLoader)); |
||||
} |
||||
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes); |
||||
return MergedAnnotation.of(classLoader, new Source(entryName), annotationType, compactedAttributes); |
||||
} |
||||
catch (ClassNotFoundException | LinkageError ex) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private static Object readAnnotationValue(AnnotationValue elementValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { |
||||
switch (elementValue) { |
||||
case AnnotationValue.OfArray arrayValue -> { |
||||
List<AnnotationValue> rawValues = arrayValue.values(); |
||||
List<Object> values = new ArrayList<>(rawValues.size()); |
||||
for (AnnotationValue arrayEntry : rawValues) { |
||||
values.add(readAnnotationValue(arrayEntry, classLoader)); |
||||
} |
||||
Class<?> elementType = getArrayElementType(values); |
||||
return values.toArray((Object[]) Array.newInstance(elementType, rawValues.size())); |
||||
} |
||||
case AnnotationValue.OfAnnotation annotationValue -> { |
||||
return annotationValue.annotation(); |
||||
} |
||||
case AnnotationValue.OfClass classValue -> { |
||||
return fromTypeDescriptor(classValue.className().stringValue()); |
||||
} |
||||
case AnnotationValue.OfEnum enumValue -> { |
||||
return parseEnum(enumValue, classLoader); |
||||
} |
||||
case AnnotationValue.OfConstant constantValue -> { |
||||
return constantValue.resolvedValue(); |
||||
} |
||||
default -> { |
||||
return elementValue; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static Class<?> getArrayElementType(List<Object> values) { |
||||
if (values.isEmpty()) { |
||||
return Object.class; |
||||
} |
||||
Object firstElement = values.getFirst(); |
||||
if (firstElement instanceof Enum<?> enumeration) { |
||||
return enumeration.getDeclaringClass(); |
||||
} |
||||
return firstElement.getClass(); |
||||
} |
||||
|
||||
private static String fromTypeDescriptor(String descriptor) { |
||||
return descriptor.substring(1, descriptor.length() - 1) |
||||
.replace('/', '.'); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { |
||||
String enumClassName = fromTypeDescriptor(enumValue.className().stringValue()); |
||||
Class<E> enumClass = (Class<E>) ClassUtils.forName(enumClassName, classLoader); |
||||
return Enum.valueOf(enumClass, enumValue.constantName().stringValue()); |
||||
} |
||||
|
||||
record Source(String entryName) { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,315 +0,0 @@
@@ -1,315 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.classfile.AccessFlags; |
||||
import java.lang.classfile.ClassModel; |
||||
import java.lang.classfile.Interfaces; |
||||
import java.lang.classfile.MethodModel; |
||||
import java.lang.classfile.Superclass; |
||||
import java.lang.classfile.attribute.InnerClassInfo; |
||||
import java.lang.classfile.attribute.InnerClassesAttribute; |
||||
import java.lang.classfile.attribute.NestHostAttribute; |
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; |
||||
import java.lang.classfile.constantpool.ClassEntry; |
||||
import java.lang.reflect.AccessFlag; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Set; |
||||
|
||||
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.ClassUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* {@link AnnotationMetadata} implementation that leverages |
||||
* the {@link java.lang.classfile.ClassFile} API. |
||||
* @author Brian Clozel |
||||
*/ |
||||
class ClassFileClassMetadata implements AnnotationMetadata { |
||||
|
||||
private final String className; |
||||
|
||||
private final AccessFlags accessFlags; |
||||
|
||||
@Nullable |
||||
private final String enclosingClassName; |
||||
|
||||
@Nullable |
||||
private final String superClassName; |
||||
|
||||
private final boolean independentInnerClass; |
||||
|
||||
private final Set<String> interfaceNames; |
||||
|
||||
private final Set<String> memberClassNames; |
||||
|
||||
private final Set<MethodMetadata> declaredMethods; |
||||
|
||||
private final MergedAnnotations mergedAnnotations; |
||||
|
||||
@Nullable |
||||
private Set<String> annotationTypes; |
||||
|
||||
ClassFileClassMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName, |
||||
@Nullable String superClassName, boolean independentInnerClass, Set<String> interfaceNames, |
||||
Set<String> memberClassNames, Set<MethodMetadata> declaredMethods, MergedAnnotations mergedAnnotations) { |
||||
this.className = className; |
||||
this.accessFlags = accessFlags; |
||||
this.enclosingClassName = enclosingClassName; |
||||
this.superClassName = superClassName; |
||||
this.independentInnerClass = independentInnerClass; |
||||
this.interfaceNames = interfaceNames; |
||||
this.memberClassNames = memberClassNames; |
||||
this.declaredMethods = declaredMethods; |
||||
this.mergedAnnotations = mergedAnnotations; |
||||
} |
||||
|
||||
@Override |
||||
public String getClassName() { |
||||
return this.className; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isInterface() { |
||||
return this.accessFlags.has(AccessFlag.INTERFACE); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAnnotation() { |
||||
return this.accessFlags.has(AccessFlag.ANNOTATION); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAbstract() { |
||||
return this.accessFlags.has(AccessFlag.ABSTRACT); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isFinal() { |
||||
return this.accessFlags.has(AccessFlag.FINAL); |
||||
} |
||||
|
||||
@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 StringUtils.toStringArray(this.interfaceNames); |
||||
} |
||||
|
||||
@Override |
||||
public String[] getMemberClassNames() { |
||||
return StringUtils.toStringArray(this.memberClassNames); |
||||
} |
||||
|
||||
@Override |
||||
public MergedAnnotations getAnnotations() { |
||||
return this.mergedAnnotations; |
||||
} |
||||
|
||||
@Override |
||||
public Set<String> getAnnotationTypes() { |
||||
Set<String> annotationTypes = this.annotationTypes; |
||||
if (annotationTypes == null) { |
||||
annotationTypes = Collections.unmodifiableSet( |
||||
AnnotationMetadata.super.getAnnotationTypes()); |
||||
this.annotationTypes = annotationTypes; |
||||
} |
||||
return annotationTypes; |
||||
} |
||||
|
||||
@Override |
||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) { |
||||
Set<MethodMetadata> result = new LinkedHashSet<>(4); |
||||
for (MethodMetadata annotatedMethod : this.declaredMethods) { |
||||
if (annotatedMethod.isAnnotated(annotationName)) { |
||||
result.add(annotatedMethod); |
||||
} |
||||
} |
||||
return Collections.unmodifiableSet(result); |
||||
} |
||||
|
||||
@Override |
||||
public Set<MethodMetadata> getDeclaredMethods() { |
||||
return Collections.unmodifiableSet(this.declaredMethods); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean equals(@Nullable Object other) { |
||||
return (this == other || (other instanceof ClassFileClassMetadata that && this.className.equals(that.className))); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.className.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.className; |
||||
} |
||||
|
||||
|
||||
static ClassFileClassMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) { |
||||
Builder builder = new Builder(classLoader); |
||||
builder.classEntry(classModel.thisClass()); |
||||
String currentClassName = classModel.thisClass().name().stringValue(); |
||||
classModel.elementStream().forEach(classElement -> { |
||||
switch (classElement) { |
||||
case AccessFlags flags -> { |
||||
builder.accessFlags(flags); |
||||
} |
||||
case NestHostAttribute nestHost -> { |
||||
builder.enclosingClass(nestHost.nestHost()); |
||||
} |
||||
case InnerClassesAttribute innerClasses -> { |
||||
builder.nestMembers(currentClassName, innerClasses); |
||||
} |
||||
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> { |
||||
builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(currentClassName, annotationsAttribute, classLoader)); |
||||
} |
||||
case Superclass superclass -> { |
||||
builder.superClass(superclass); |
||||
} |
||||
case Interfaces interfaces -> { |
||||
builder.interfaces(interfaces); |
||||
} |
||||
case MethodModel method -> { |
||||
builder.method(method); |
||||
} |
||||
default -> { |
||||
// ignore class element
|
||||
} |
||||
} |
||||
}); |
||||
return builder.build(); |
||||
} |
||||
|
||||
static class Builder { |
||||
|
||||
private final ClassLoader clasLoader; |
||||
|
||||
private String className; |
||||
|
||||
private AccessFlags accessFlags; |
||||
|
||||
private Set<AccessFlag> innerAccessFlags; |
||||
|
||||
@Nullable |
||||
private String enclosingClassName; |
||||
|
||||
@Nullable |
||||
private String superClassName; |
||||
|
||||
private Set<String> interfaceNames = new HashSet<>(); |
||||
|
||||
private Set<String> memberClassNames = new HashSet<>(); |
||||
|
||||
private Set<MethodMetadata> declaredMethods = new HashSet<>(); |
||||
|
||||
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet()); |
||||
|
||||
public Builder(ClassLoader classLoader) { |
||||
this.clasLoader = classLoader; |
||||
} |
||||
|
||||
Builder classEntry(ClassEntry classEntry) { |
||||
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue()); |
||||
return this; |
||||
} |
||||
|
||||
Builder accessFlags(AccessFlags accessFlags) { |
||||
this.accessFlags = accessFlags; |
||||
return this; |
||||
} |
||||
|
||||
Builder innerAccessFlags(Set<AccessFlag> innerAccessFlags) { |
||||
this.innerAccessFlags = innerAccessFlags; |
||||
return this; |
||||
} |
||||
|
||||
Builder enclosingClass(ClassEntry enclosingClass) { |
||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(enclosingClass.name().stringValue()); |
||||
return this; |
||||
} |
||||
|
||||
Builder superClass(Superclass superClass) { |
||||
this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue()); |
||||
return this; |
||||
} |
||||
|
||||
Builder interfaces(Interfaces interfaces) { |
||||
for (ClassEntry entry : interfaces.interfaces()) { |
||||
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue())); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
Builder nestMembers(String currentClassName, InnerClassesAttribute innerClasses) { |
||||
for (InnerClassInfo classInfo : innerClasses.classes()) { |
||||
String innerClassName = classInfo.innerClass().name().stringValue(); |
||||
// the current class is an inner class
|
||||
if (currentClassName.equals(innerClassName)) { |
||||
this.innerAccessFlags = classInfo.flags(); |
||||
} |
||||
// collecting data about actual inner classes
|
||||
else { |
||||
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName)); |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
Builder mergedAnnotations(MergedAnnotations mergedAnnotations) { |
||||
this.mergedAnnotations = mergedAnnotations; |
||||
return this; |
||||
} |
||||
|
||||
Builder method(MethodModel method) { |
||||
this.declaredMethods.add(ClassFileMethodMetadata.of(method, this.clasLoader)); |
||||
return this; |
||||
} |
||||
|
||||
ClassFileClassMetadata build() { |
||||
boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC); |
||||
return new ClassFileClassMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName, |
||||
independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,68 +0,0 @@
@@ -1,68 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.lang.classfile.ClassFile; |
||||
import java.lang.classfile.ClassModel; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.type.AnnotationMetadata; |
||||
import org.springframework.core.type.ClassMetadata; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* {@link MetadataReader} implementation based on the {@link ClassFile} API. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
final class ClassFileMetadataReader implements MetadataReader { |
||||
|
||||
private final Resource resource; |
||||
|
||||
private final AnnotationMetadata annotationMetadata; |
||||
|
||||
|
||||
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { |
||||
this.resource = resource; |
||||
this.annotationMetadata = ClassFileClassMetadata.of(getClassModel(resource), classLoader); |
||||
} |
||||
|
||||
private static ClassModel getClassModel(Resource resource) throws IOException { |
||||
try (InputStream is = resource.getInputStream()) { |
||||
byte[] bytes = is.readAllBytes(); |
||||
return ClassFile.of().parse(bytes); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Resource getResource() { |
||||
return this.resource; |
||||
} |
||||
|
||||
@Override |
||||
public ClassMetadata getClassMetadata() { |
||||
return this.annotationMetadata; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationMetadata getAnnotationMetadata() { |
||||
return this.annotationMetadata; |
||||
} |
||||
|
||||
} |
||||
@ -1,104 +0,0 @@
@@ -1,104 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.io.FileNotFoundException; |
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.io.ResourceLoader; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Implementation of the {@link MetadataReaderFactory} interface, |
||||
* using the {@link java.lang.classfile.ClassFile} API for parsing the bytecode. |
||||
* |
||||
* @author Brian Clozel |
||||
* @since 7.0 |
||||
*/ |
||||
public class ClassFileMetadataReaderFactory implements MetadataReaderFactory { |
||||
|
||||
|
||||
private final ResourceLoader resourceLoader; |
||||
|
||||
|
||||
/** |
||||
* Create a new ClassFileMetadataReaderFactory for the default class loader. |
||||
*/ |
||||
public ClassFileMetadataReaderFactory() { |
||||
this.resourceLoader = new DefaultResourceLoader(); |
||||
} |
||||
|
||||
/** |
||||
* Create a new ClassFileMetadataReaderFactory for the given resource loader. |
||||
* @param resourceLoader the Spring ResourceLoader to use |
||||
* (also determines the ClassLoader to use) |
||||
*/ |
||||
public ClassFileMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { |
||||
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); |
||||
} |
||||
|
||||
/** |
||||
* Create a new ClassFileMetadataReaderFactory for the given class loader. |
||||
* @param classLoader the ClassLoader to use |
||||
*/ |
||||
public ClassFileMetadataReaderFactory(@Nullable ClassLoader classLoader) { |
||||
this.resourceLoader = |
||||
(classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader()); |
||||
} |
||||
|
||||
/** |
||||
* Return the ResourceLoader that this MetadataReaderFactory has been |
||||
* constructed with. |
||||
*/ |
||||
public final ResourceLoader getResourceLoader() { |
||||
return this.resourceLoader; |
||||
} |
||||
|
||||
@Override |
||||
public MetadataReader getMetadataReader(String className) throws IOException { |
||||
try { |
||||
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + |
||||
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; |
||||
Resource resource = this.resourceLoader.getResource(resourcePath); |
||||
return getMetadataReader(resource); |
||||
} |
||||
catch (FileNotFoundException ex) { |
||||
// Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here...
|
||||
// ClassUtils.forName has an equivalent check for resolution into Class references later on.
|
||||
int lastDotIndex = className.lastIndexOf('.'); |
||||
if (lastDotIndex != -1) { |
||||
String innerClassName = |
||||
className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1); |
||||
String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + |
||||
ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX; |
||||
Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath); |
||||
if (innerClassResource.exists()) { |
||||
return getMetadataReader(innerClassResource); |
||||
} |
||||
} |
||||
throw ex; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public MetadataReader getMetadataReader(Resource resource) throws IOException { |
||||
return new ClassFileMetadataReader(resource, this.resourceLoader.getClassLoader()); |
||||
} |
||||
} |
||||
@ -1,162 +0,0 @@
@@ -1,162 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.classfile.AccessFlags; |
||||
import java.lang.classfile.MethodModel; |
||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; |
||||
import java.lang.constant.ClassDesc; |
||||
import java.lang.constant.MethodTypeDesc; |
||||
import java.lang.reflect.AccessFlag; |
||||
import java.util.Collections; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
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} extracted from class bytecode using the |
||||
* {@link java.lang.classfile.ClassFile} API. |
||||
* @author Brian Clozel |
||||
*/ |
||||
class ClassFileMethodMetadata implements MethodMetadata { |
||||
|
||||
private final String methodName; |
||||
|
||||
private final AccessFlags accessFlags; |
||||
|
||||
@Nullable |
||||
private final String declaringClassName; |
||||
|
||||
private final String returnTypeName; |
||||
|
||||
// The source implements equals(), hashCode(), and toString() for the underlying method.
|
||||
private final Object source; |
||||
|
||||
private final MergedAnnotations annotations; |
||||
|
||||
ClassFileMethodMetadata(String methodName, AccessFlags accessFlags, String declaringClassName, String returnTypeName, Object source, MergedAnnotations annotations) { |
||||
this.methodName = methodName; |
||||
this.accessFlags = accessFlags; |
||||
this.declaringClassName = declaringClassName; |
||||
this.returnTypeName = returnTypeName; |
||||
this.source = source; |
||||
this.annotations = annotations; |
||||
} |
||||
|
||||
@Override |
||||
public String getMethodName() { |
||||
return this.methodName; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public String getDeclaringClassName() { |
||||
return this.declaringClassName; |
||||
} |
||||
|
||||
@Override |
||||
public String getReturnTypeName() { |
||||
return this.returnTypeName; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAbstract() { |
||||
return this.accessFlags.has(AccessFlag.ABSTRACT); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isStatic() { |
||||
return this.accessFlags.has(AccessFlag.STATIC); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isFinal() { |
||||
return this.accessFlags.has(AccessFlag.FINAL); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isOverridable() { |
||||
return !isStatic() && !isFinal() && !isPrivate(); |
||||
} |
||||
|
||||
private boolean isPrivate() { |
||||
return this.accessFlags.has(AccessFlag.PRIVATE); |
||||
} |
||||
|
||||
@Override |
||||
public MergedAnnotations getAnnotations() { |
||||
return this.annotations; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean equals(@Nullable Object other) { |
||||
return (this == other || (other instanceof ClassFileMethodMetadata that && this.source.equals(that.source))); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.source.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.source.toString(); |
||||
} |
||||
|
||||
static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoader) { |
||||
String methodName = methodModel.methodName().stringValue(); |
||||
AccessFlags flags = methodModel.flags(); |
||||
String declaringClassName = methodModel.parent().map(parent -> parent.thisClass().name().stringValue()).orElse(null); |
||||
String returnTypeName = methodModel.methodTypeSymbol().returnType().displayName(); |
||||
Source source = new Source(declaringClassName, methodName, methodModel.methodTypeSymbol()); |
||||
MergedAnnotations annotations = methodModel.elementStream() |
||||
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute) |
||||
.findFirst() |
||||
.map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader)) |
||||
.orElse(MergedAnnotations.of(Collections.emptyList())); |
||||
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations); |
||||
} |
||||
|
||||
/** |
||||
* {@link MergedAnnotation} source. |
||||
* @param declaringClassName the name of the declaring class
|
||||
* @param methodName the name of the method |
||||
* @param descriptor the bytecode descriptor for this method |
||||
*/ |
||||
record Source(String declaringClassName, String methodName, MethodTypeDesc descriptor) { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
StringBuilder builder = new StringBuilder(); |
||||
builder.append(this.declaringClassName); |
||||
builder.append('.'); |
||||
builder.append(this.methodName); |
||||
builder.append('('); |
||||
builder.append(Stream.of(this.descriptor.parameterArray()) |
||||
.map(ClassDesc::displayName) |
||||
.collect(Collectors.joining(","))); |
||||
builder.append(')'); |
||||
return builder.toString(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,38 +0,0 @@
@@ -1,38 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.core.io.ResourceLoader; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations. |
||||
* For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is being used. |
||||
* |
||||
* @author Brian Clozel |
||||
* @see MetadataReaderFactory |
||||
*/ |
||||
abstract class MetadataReaderFactoryDelegate { |
||||
|
||||
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { |
||||
return new ClassFileMetadataReaderFactory(resourceLoader); |
||||
} |
||||
|
||||
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { |
||||
return new ClassFileMetadataReaderFactory(classLoader); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue