Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1061 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
30 changed files with 804 additions and 1929 deletions
@ -1,44 +0,0 @@
@@ -1,44 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.context.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
|
||||
/** |
||||
* Interface used when dynamically creating mutable instances of annotations associated |
||||
* with {@link Configuration} class processing. This functionality is necessary given |
||||
* that parsing of Configuration classes is done with ASM. Annotation metadata (including |
||||
* attributes) is parsed from the class files, and instances of those annotations are |
||||
* then created using this interface and its associated utilities. The annotation |
||||
* instances are attached to the configuration model objects at runtime, namely |
||||
* {@link ConfigurationClassMethod}. This approach is better than the alternative of |
||||
* creating fine-grained model representations of all annotations and attributes. |
||||
* It is better to simply attach annotation instances and read them as needed. |
||||
* |
||||
* @author Chris Beams |
||||
* @author Juergen Hoeller |
||||
* @since 3.0 |
||||
* @see ConfigurationClassAnnotationVisitor |
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation |
||||
*/ |
||||
public interface ConfigurationClassAnnotation extends Annotation { |
||||
|
||||
void setAttributeValue(String attribName, Object attribValue); |
||||
|
||||
Class<?> getAttributeType(String attributeName); |
||||
|
||||
} |
||||
@ -1,167 +0,0 @@
@@ -1,167 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.context.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Array; |
||||
import java.lang.reflect.Field; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.asm.AnnotationVisitor; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.asm.commons.EmptyVisitor; |
||||
|
||||
/** |
||||
* ASM {@link AnnotationVisitor} that populates a given {@link ConfigurationClassAnnotation} instance |
||||
* with its attributes. |
||||
* |
||||
* @author Chris Beams |
||||
* @author Juergen Hoeller |
||||
* @since 3.0 |
||||
* @see ConfigurationClassAnnotation |
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation |
||||
*/ |
||||
class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { |
||||
|
||||
protected final ConfigurationClassAnnotation mutableAnno; |
||||
|
||||
private final ClassLoader classLoader; |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@link ConfigurationClassAnnotationVisitor} instance that will populate the the |
||||
* attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation} instead of |
||||
* {@link ConfigurationClassAnnotation} to avoid the need for callers to typecast. |
||||
* @param mutableAnno {@link ConfigurationClassAnnotation} instance to visit and populate |
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation |
||||
*/ |
||||
public ConfigurationClassAnnotationVisitor(ConfigurationClassAnnotation mutableAnno, ClassLoader classLoader) { |
||||
this.mutableAnno = mutableAnno; |
||||
this.classLoader = classLoader; |
||||
} |
||||
|
||||
public void visit(String attribName, Object attribValue) { |
||||
Class<?> attribReturnType = mutableAnno.getAttributeType(attribName); |
||||
if (attribReturnType.equals(Class.class)) { |
||||
// the attribute type is Class -> load it and set it.
|
||||
String className = ((Type) attribValue).getClassName(); |
||||
try { |
||||
Class<?> classVal = classLoader.loadClass(className); |
||||
if (classVal != null) { |
||||
mutableAnno.setAttributeValue(attribName, classVal); |
||||
} |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
throw new IllegalStateException("Cannot resolve attribute type [" + className + "]", ex); |
||||
} |
||||
} |
||||
else { |
||||
// otherwise, assume the value can be set literally
|
||||
mutableAnno.setAttributeValue(attribName, attribValue); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) { |
||||
String enumTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor); |
||||
try { |
||||
Class<? extends Enum> enumType = (Class<? extends Enum>) classLoader.loadClass(enumTypeName); |
||||
if (enumType == null) { |
||||
return; |
||||
} |
||||
Enum enumValue = Enum.valueOf(enumType, strEnumValue); |
||||
mutableAnno.setAttributeValue(attribName, enumValue); |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
throw new IllegalStateException("Cannot resolve enum type [" + enumTypeName + "]", ex); |
||||
} |
||||
} |
||||
|
||||
public AnnotationVisitor visitAnnotation(String attribName, String annoTypeDesc) { |
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); |
||||
if (annoClass == null) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); |
||||
try { |
||||
Field attribute = mutableAnno.getClass().getField(attribName); |
||||
attribute.set(mutableAnno, anno); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Could not reflectively set annotation field", ex); |
||||
} |
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader); |
||||
} |
||||
|
||||
public AnnotationVisitor visitArray(final String attribName) { |
||||
return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader); |
||||
} |
||||
|
||||
public void visitEnd() { |
||||
} |
||||
|
||||
|
||||
/** |
||||
* ASM {@link AnnotationVisitor} that visits any annotation array values while populating |
||||
* a new {@link ConfigurationClassAnnotation} instance. |
||||
*/ |
||||
private static class MutableAnnotationArrayVisitor implements AnnotationVisitor { |
||||
|
||||
private final List<Object> values = new ArrayList<Object>(); |
||||
|
||||
private final ConfigurationClassAnnotation mutableAnno; |
||||
|
||||
private final String attribName; |
||||
|
||||
private final ClassLoader classLoader; |
||||
|
||||
public MutableAnnotationArrayVisitor(ConfigurationClassAnnotation mutableAnno, String attribName, ClassLoader classLoader) { |
||||
this.mutableAnno = mutableAnno; |
||||
this.attribName = attribName; |
||||
this.classLoader = classLoader; |
||||
} |
||||
|
||||
public void visit(String na, Object value) { |
||||
values.add(value); |
||||
} |
||||
|
||||
public void visitEnum(String s, String s1, String s2) { |
||||
} |
||||
|
||||
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { |
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); |
||||
if (annoClass == null) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); |
||||
values.add(anno); |
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader); |
||||
} |
||||
|
||||
public AnnotationVisitor visitArray(String s) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
|
||||
public void visitEnd() { |
||||
Class<?> arrayType = mutableAnno.getAttributeType(attribName); |
||||
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0); |
||||
mutableAnno.setAttributeValue(attribName, values.toArray(array)); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,282 +0,0 @@
@@ -1,282 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.context.annotation; |
||||
|
||||
import static java.lang.String.*; |
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Proxy; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Various utility methods commonly used when interacting with ASM, classloading |
||||
* and creating {@link ConfigurationClassAnnotation} instances. |
||||
* |
||||
* @author Chris Beams |
||||
* @since 3.0 |
||||
*/ |
||||
class ConfigurationClassReaderUtils { |
||||
|
||||
/** |
||||
* Convert a type descriptor to a classname suitable for classloading with |
||||
* Class.forName(). |
||||
* |
||||
* @param typeDescriptor see ASM guide section 2.1.3 |
||||
*/ |
||||
public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) { |
||||
final String internalName; // See ASM guide section 2.1.2
|
||||
|
||||
if ("V".equals(typeDescriptor)) |
||||
return Void.class.getName(); |
||||
if ("I".equals(typeDescriptor)) |
||||
return Integer.class.getName(); |
||||
if ("Z".equals(typeDescriptor)) |
||||
return Boolean.class.getName(); |
||||
|
||||
// strip the leading array/object/primitive identifier
|
||||
if (typeDescriptor.startsWith("[[")) |
||||
internalName = typeDescriptor.substring(3); |
||||
else if (typeDescriptor.startsWith("[")) |
||||
internalName = typeDescriptor.substring(2); |
||||
else |
||||
internalName = typeDescriptor.substring(1); |
||||
|
||||
// convert slashes to dots
|
||||
String className = internalName.replace('/', '.'); |
||||
|
||||
// and strip trailing semicolon (if present)
|
||||
if (className.endsWith(";")) |
||||
className = className.substring(0, internalName.length() - 1); |
||||
|
||||
return className; |
||||
} |
||||
|
||||
/** |
||||
* @param methodDescriptor see ASM guide section 2.1.4 |
||||
*/ |
||||
public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) { |
||||
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1); |
||||
return convertAsmTypeDescriptorToClassName(returnTypeDescriptor); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static Class<? extends Annotation> loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) { |
||||
String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); |
||||
try { |
||||
return (Class<? extends Annotation>) classLoader.loadClass(annoTypeName); |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
throw new IllegalStateException("Could not load annotation type [" + annoTypeName + "]", ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link ConfigurationClassAnnotation} for {@code annoType}. JDK dynamic proxies are used, |
||||
* and the returned proxy implements both {@link ConfigurationClassAnnotation} and the annotation type. |
||||
* @param annoType annotation type that must be supplied and returned |
||||
* @param annoType type of annotation to create |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static ConfigurationClassAnnotation createMutableAnnotation(Class<? extends Annotation> annoType, ClassLoader classLoader) { |
||||
MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType); |
||||
Class<?>[] interfaces = new Class<?>[] {annoType, ConfigurationClassAnnotation.class}; |
||||
return (ConfigurationClassAnnotation) Proxy.newProxyInstance(classLoader, interfaces, handler); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Handles calls to {@link ConfigurationClassAnnotation} attribute methods at runtime. Essentially |
||||
* emulates what JDK annotation dynamic proxies do. |
||||
*/ |
||||
private static final class MutableAnnotationInvocationHandler implements InvocationHandler { |
||||
|
||||
private final Class<? extends Annotation> annoType; |
||||
private final Map<String, Object> attributes = new HashMap<String, Object>(); |
||||
private final Map<String, Class<?>> attributeTypes = new HashMap<String, Class<?>>(); |
||||
|
||||
public MutableAnnotationInvocationHandler(Class<? extends Annotation> annoType) { |
||||
// pre-populate the attributes hash will all the names
|
||||
// and default values of the attributes defined in 'annoType'
|
||||
Method[] attribs = annoType.getDeclaredMethods(); |
||||
for (Method attrib : attribs) { |
||||
this.attributes.put(attrib.getName(), AnnotationUtils.getDefaultValue(annoType, attrib.getName())); |
||||
this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); |
||||
} |
||||
|
||||
this.annoType = annoType; |
||||
} |
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
||||
Assert.isInstanceOf(Annotation.class, proxy); |
||||
|
||||
String methodName = method.getName(); |
||||
|
||||
// first -> check to see if this method is an attribute on our annotation
|
||||
if (attributes.containsKey(methodName)) |
||||
return attributes.get(methodName); |
||||
|
||||
|
||||
// second -> is it a method from java.lang.annotation.Annotation?
|
||||
if (methodName.equals("annotationType")) |
||||
return annoType; |
||||
|
||||
|
||||
// third -> is it a method from java.lang.Object?
|
||||
if (methodName.equals("toString")) |
||||
return format("@%s(%s)", annoType.getName(), getAttribs()); |
||||
|
||||
if (methodName.equals("equals")) |
||||
return isEqualTo(proxy, args[0]); |
||||
|
||||
if (methodName.equals("hashCode")) |
||||
return calculateHashCode(proxy); |
||||
|
||||
|
||||
// finally -> is it a method specified by MutableAnno?
|
||||
if (methodName.equals("setAttributeValue")) { |
||||
attributes.put((String) args[0], args[1]); |
||||
return null; // setAttributeValue has a 'void' return type
|
||||
} |
||||
|
||||
if (methodName.equals("getAttributeType")) |
||||
return attributeTypes.get(args[0]); |
||||
|
||||
throw new UnsupportedOperationException("this proxy does not support method: " + methodName); |
||||
} |
||||
|
||||
/** |
||||
* Conforms to the hashCode() specification for Annotation. |
||||
* |
||||
* @see Annotation#hashCode() |
||||
*/ |
||||
private Object calculateHashCode(Object proxy) { |
||||
int sum = 0; |
||||
|
||||
for (String attribName : attributes.keySet()) { |
||||
Object attribValue = attributes.get(attribName); |
||||
|
||||
final int attribNameHashCode = attribName.hashCode(); |
||||
final int attribValueHashCode; |
||||
|
||||
if (attribValue == null) |
||||
// memberValue may be null when a mutable annotation is being added to a
|
||||
// collection
|
||||
// and before it has actually been visited (and populated) by
|
||||
// MutableAnnotationVisitor
|
||||
attribValueHashCode = 0; |
||||
else if (attribValue.getClass().isArray()) |
||||
attribValueHashCode = Arrays.hashCode((Object[]) attribValue); |
||||
else |
||||
attribValueHashCode = attribValue.hashCode(); |
||||
|
||||
sum += (127 * attribNameHashCode) ^ attribValueHashCode; |
||||
} |
||||
|
||||
return sum; |
||||
} |
||||
|
||||
/** |
||||
* Compares <var>proxy</var> object and <var>other</var> object by comparing the return |
||||
* values of the methods specified by their common {@link Annotation} ancestry. |
||||
* <p/> |
||||
* <var>other</var> must be the same type as or a subtype of <var>proxy</var>. Will |
||||
* return false otherwise. |
||||
* <p/> |
||||
* Eagerly returns true if {@code proxy} == <var>other</var> |
||||
* </p> |
||||
* <p/> |
||||
* Conforms strictly to the equals() specification for Annotation |
||||
* </p> |
||||
* |
||||
* @see Annotation#equals(Object) |
||||
*/ |
||||
private Object isEqualTo(Object proxy, Object other) { |
||||
if (proxy == other) |
||||
return true; |
||||
|
||||
if (other == null) |
||||
return false; |
||||
|
||||
if (!annoType.isAssignableFrom(other.getClass())) |
||||
return false; |
||||
|
||||
for (String attribName : attributes.keySet()) { |
||||
Object thisVal; |
||||
Object thatVal; |
||||
|
||||
try { |
||||
thisVal = attributes.get(attribName); |
||||
thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other); |
||||
} catch (Exception ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
|
||||
if ((thisVal == null) && (thatVal != null)) |
||||
return false; |
||||
|
||||
if ((thatVal == null) && (thisVal != null)) |
||||
return false; |
||||
|
||||
if (thatVal.getClass().isArray()) { |
||||
if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) { |
||||
return false; |
||||
} |
||||
} else if (thisVal instanceof Double) { |
||||
if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal))) |
||||
return false; |
||||
} else if (thisVal instanceof Float) { |
||||
if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal))) |
||||
return false; |
||||
} else if (!thisVal.equals(thatVal)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private String getAttribs() { |
||||
List<String> attribs = new ArrayList<String>(); |
||||
for (String attribName : attributes.keySet()) { |
||||
attribs.add(format("%s=%s", attribName, attributes.get(attribName))); |
||||
} |
||||
return StringUtils.collectionToDelimitedString(attribs, ", "); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the type of the given annotation attribute. |
||||
*/ |
||||
private static Class<?> getAttributeType(Class<? extends Annotation> annotationType, String attributeName) { |
||||
try { |
||||
return annotationType.getDeclaredMethod(attributeName).getReturnType(); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException("Could not introspect return type", ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,445 +0,0 @@
@@ -1,445 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.context.annotation; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.annotation.Annotation; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.Stack; |
||||
|
||||
import org.springframework.asm.AnnotationVisitor; |
||||
import org.springframework.asm.Attribute; |
||||
import org.springframework.asm.ClassAdapter; |
||||
import org.springframework.asm.ClassReader; |
||||
import org.springframework.asm.ClassVisitor; |
||||
import org.springframework.asm.FieldVisitor; |
||||
import org.springframework.asm.Label; |
||||
import org.springframework.asm.MethodAdapter; |
||||
import org.springframework.asm.MethodVisitor; |
||||
import org.springframework.asm.Opcodes; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.asm.commons.EmptyVisitor; |
||||
import org.springframework.beans.factory.BeanDefinitionStoreException; |
||||
import org.springframework.beans.factory.parsing.Location; |
||||
import org.springframework.beans.factory.parsing.Problem; |
||||
import org.springframework.beans.factory.parsing.ProblemReporter; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.core.type.classreading.SimpleMetadataReader; |
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a |
||||
* {@link ConfigurationClass} instance with information gleaned along the way. |
||||
* |
||||
* @author Chris Beams |
||||
* @author Juergen Hoeller |
||||
* @since 3.0 |
||||
* @see ConfigurationClassParser |
||||
* @see ConfigurationClass |
||||
*/ |
||||
class ConfigurationClassVisitor implements ClassVisitor { |
||||
|
||||
private static final String OBJECT_DESC = ClassUtils.convertClassNameToResourcePath(Object.class.getName()); |
||||
|
||||
private final ConfigurationClass configClass; |
||||
|
||||
private final Set<ConfigurationClass> model; |
||||
|
||||
private final ProblemReporter problemReporter; |
||||
|
||||
private final SimpleMetadataReaderFactory metadataReaderFactory; |
||||
|
||||
private final Stack<ConfigurationClass> importStack; |
||||
|
||||
|
||||
public ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model, |
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory) { |
||||
|
||||
this.configClass = configClass; |
||||
this.model = model; |
||||
this.problemReporter = problemReporter; |
||||
this.metadataReaderFactory = metadataReaderFactory; |
||||
this.importStack = new ImportStack(); |
||||
} |
||||
|
||||
private ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model, |
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory, |
||||
Stack<ConfigurationClass> importStack) { |
||||
this.configClass = configClass; |
||||
this.model = model; |
||||
this.problemReporter = problemReporter; |
||||
this.metadataReaderFactory = metadataReaderFactory; |
||||
this.importStack = importStack; |
||||
} |
||||
|
||||
|
||||
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, String superTypeDesc, String[] arg5) { |
||||
visitSuperType(superTypeDesc); |
||||
|
||||
configClass.setName(ClassUtils.convertResourcePathToClassName(classTypeDesc)); |
||||
|
||||
// ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions.
|
||||
// Unknown as to why (JavaDoc is silent on the matter), but it should be
|
||||
// eliminated in order to comply with java.lang.reflect.Modifier values.
|
||||
configClass.setModifiers(modifiers - Opcodes.ACC_SUPER); |
||||
} |
||||
|
||||
private void visitSuperType(String superTypeDesc) { |
||||
// traverse up the type hierarchy unless the next ancestor is java.lang.Object
|
||||
if (OBJECT_DESC.equals(superTypeDesc)) { |
||||
return; |
||||
} |
||||
ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack); |
||||
String superClassName = ClassUtils.convertResourcePathToClassName(superTypeDesc); |
||||
try { |
||||
SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); |
||||
reader.getClassReader().accept(visitor, false); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new BeanDefinitionStoreException("Failed to load bean super class [" + superClassName + "]", ex); |
||||
} |
||||
} |
||||
|
||||
public void visitSource(String sourceFile, String debug) { |
||||
String resourcePath = |
||||
ClassUtils.convertClassNameToResourcePath(configClass.getName()) |
||||
.substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile); |
||||
|
||||
configClass.setSource(resourcePath); |
||||
} |
||||
|
||||
public void visitOuterClass(String s, String s1, String s2) { |
||||
} |
||||
|
||||
/** |
||||
* Visits a class level annotation on a {@link Configuration @Configuration} class. |
||||
* <p>Upon encountering such an annotation, updates the {@link #configClass} model |
||||
* object appropriately, and then returns an {@link AnnotationVisitor} implementation |
||||
* that can populate the annotation appropriately with its attribute data as parsed |
||||
* by ASM. |
||||
* @see ConfigurationClassAnnotation |
||||
* @see Configuration |
||||
* @see Lazy |
||||
* @see Import |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { |
||||
ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); |
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); |
||||
if (annoClass == null) { |
||||
// annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation
|
||||
return new EmptyVisitor(); |
||||
} |
||||
if (Import.class.equals(annoClass)) { |
||||
if (!importStack.contains(configClass)) { |
||||
importStack.push(configClass); |
||||
return new ImportAnnotationVisitor(model, problemReporter, classLoader); |
||||
} |
||||
problemReporter.error(new CircularImportProblem(configClass, importStack)); |
||||
} |
||||
|
||||
ConfigurationClassAnnotation mutableAnnotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); |
||||
configClass.addAnnotation(mutableAnnotation); |
||||
return new ConfigurationClassAnnotationVisitor(mutableAnnotation, classLoader); |
||||
} |
||||
|
||||
public void visitAttribute(Attribute attribute) { |
||||
} |
||||
|
||||
public void visitInnerClass(String s, String s1, String s2, int i) { |
||||
} |
||||
|
||||
public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
|
||||
/** |
||||
* Delegates all {@link Configuration @Configuration} class method parsing to |
||||
* {@link ConfigurationClassMethodVisitor}. |
||||
*/ |
||||
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3, String[] arg4) { |
||||
ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); |
||||
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader); |
||||
} |
||||
|
||||
public void visitEnd() { |
||||
} |
||||
|
||||
|
||||
/** |
||||
* ASM {@link MethodVisitor} that visits a single method declared in a given |
||||
* {@link Configuration} class. Determines whether the method is a {@link Bean} |
||||
* method and if so, adds it to the {@link ConfigurationClass}. |
||||
*/ |
||||
private class ConfigurationClassMethodVisitor extends MethodAdapter { |
||||
|
||||
private final ConfigurationClass configClass; |
||||
private final String methodName; |
||||
private final int modifiers; |
||||
private final ConfigurationClassMethod.ReturnType returnType; |
||||
private final List<Annotation> annotations = new ArrayList<Annotation>(); |
||||
private final ClassLoader classLoader; |
||||
|
||||
private int lineNumber; |
||||
|
||||
/** |
||||
* Create a new {@link ConfigurationClassMethodVisitor} instance. |
||||
* @param configClass model object to which this method will be added |
||||
* @param methodName name of the method declared in the {@link Configuration} class
|
||||
* @param methodDescriptor ASM representation of the method signature |
||||
* @param modifiers modifiers for this method |
||||
*/ |
||||
public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName, |
||||
String methodDescriptor, int modifiers, ClassLoader classLoader) { |
||||
super(new EmptyVisitor()); |
||||
this.configClass = configClass; |
||||
this.methodName = methodName; |
||||
this.classLoader = classLoader; |
||||
this.modifiers = modifiers; |
||||
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor); |
||||
} |
||||
|
||||
/** |
||||
* Visits a single annotation on this method. Will be called once for each annotation |
||||
* present (regardless of its RetentionPolicy). |
||||
*/ |
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { |
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); |
||||
if (annoClass == null) { |
||||
return super.visitAnnotation(annoTypeDesc, visible); |
||||
} |
||||
ConfigurationClassAnnotation annotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); |
||||
annotations.add(annotation); |
||||
return new ConfigurationClassAnnotationVisitor(annotation, classLoader); |
||||
} |
||||
|
||||
/** |
||||
* Provides the line number of this method within its declaring class. In reality, this |
||||
* number is always inaccurate - <var>lineNo</var> represents the line number of the |
||||
* first instruction in this method. Method declaration line numbers are not in any way |
||||
* tracked in the bytecode. Any tooling or output that reads this value will have to |
||||
* compensate and estimate where the actual method declaration is. |
||||
*/ |
||||
@Override |
||||
public void visitLineNumber(int lineNo, Label start) { |
||||
this.lineNumber = lineNo; |
||||
} |
||||
|
||||
/** |
||||
* Parses through all {@link #annotations} on this method in order to determine whether |
||||
* it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}. |
||||
*/ |
||||
@Override |
||||
public void visitEnd() { |
||||
for (Annotation anno : annotations) { |
||||
if (Bean.class.equals(anno.annotationType())) { |
||||
// this method is annotated with @Bean -> add it to the ConfigurationClass model
|
||||
Annotation[] annoArray = annotations.toArray(new Annotation[annotations.size()]); |
||||
ConfigurationClassMethod method = new ConfigurationClassMethod(methodName, modifiers, returnType, annoArray); |
||||
method.setSource(lineNumber); |
||||
configClass.addMethod(method); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Determines return type from ASM <var>methodDescriptor</var> and determines whether |
||||
* that type is an interface. |
||||
*/ |
||||
private ConfigurationClassMethod.ReturnType initReturnTypeFromMethodDescriptor(String methodDescriptor) { |
||||
final ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType(ConfigurationClassReaderUtils.getReturnTypeFromAsmMethodDescriptor(methodDescriptor)); |
||||
// detect whether the return type is an interface
|
||||
try { |
||||
metadataReaderFactory.getMetadataReader(returnType.getName()).getClassReader().accept( |
||||
new ClassAdapter(new EmptyVisitor()) { |
||||
@Override |
||||
public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) { |
||||
returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE); |
||||
} |
||||
}, false); |
||||
return returnType; |
||||
} |
||||
catch (IOException ex) { |
||||
throw new BeanDefinitionStoreException("Failed to load bean return type [" + returnType.getName() + "]", ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation |
||||
* for all its specified classes and then one by one processes each class with a new |
||||
* {@link ConfigurationClassVisitor}. |
||||
*/ |
||||
private class ImportAnnotationVisitor implements AnnotationVisitor{ |
||||
|
||||
private final Set<ConfigurationClass> model; |
||||
|
||||
private final ProblemReporter problemReporter; |
||||
|
||||
private final List<String> classesToImport = new ArrayList<String>(); |
||||
|
||||
public ImportAnnotationVisitor( |
||||
Set<ConfigurationClass> model, ProblemReporter problemReporter, ClassLoader classLoader) { |
||||
|
||||
this.model = model; |
||||
this.problemReporter = problemReporter; |
||||
} |
||||
|
||||
public void visit(String s, Object o) { |
||||
} |
||||
|
||||
public void visitEnum(String s, String s1, String s2) { |
||||
} |
||||
|
||||
public AnnotationVisitor visitAnnotation(String s, String s1) { |
||||
return null; |
||||
} |
||||
|
||||
public AnnotationVisitor visitArray(String attribName) { |
||||
Assert.isTrue("value".equals(attribName)); |
||||
return new AnnotationVisitor() { |
||||
public void visit(String na, Object type) { |
||||
Assert.isInstanceOf(Type.class, type); |
||||
classesToImport.add(((Type) type).getClassName()); |
||||
} |
||||
public void visitEnum(String s, String s1, String s2) { |
||||
} |
||||
public AnnotationVisitor visitAnnotation(String s, String s1) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
public AnnotationVisitor visitArray(String s) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
public void visitEnd() { |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public void visitEnd() { |
||||
for (String classToImport : classesToImport) { |
||||
processClassToImport(classToImport); |
||||
} |
||||
importStack.pop(); |
||||
} |
||||
|
||||
private void processClassToImport(String classToImport) { |
||||
ConfigurationClass configClass = new ConfigurationClass(); |
||||
try { |
||||
ClassReader reader = metadataReaderFactory.getMetadataReader(classToImport).getClassReader(); |
||||
reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack), false); |
||||
if (configClass.getAnnotation(Configuration.class) == null) { |
||||
problemReporter.error(new NonAnnotatedConfigurationProblem(configClass.getName(), configClass.getLocation())); |
||||
} |
||||
else { |
||||
model.add(configClass); |
||||
} |
||||
} |
||||
catch (IOException ex) { |
||||
throw new BeanDefinitionStoreException("Failed to load imported configuration class [" + classToImport + "]", ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("serial") |
||||
private static class ImportStack extends Stack<ConfigurationClass> { |
||||
|
||||
/** |
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass} |
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of |
||||
* type ConfigurationClass. |
||||
*/ |
||||
@Override |
||||
public boolean contains(Object elem) { |
||||
if (!(elem instanceof ConfigurationClass)) { |
||||
return false; |
||||
} |
||||
ConfigurationClass configClass = (ConfigurationClass) elem; |
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() { |
||||
public int compare(ConfigurationClass first, ConfigurationClass second) { |
||||
return first.getName().equals(second.getName()) ? 0 : 1; |
||||
} |
||||
}; |
||||
return (Collections.binarySearch(this, configClass, comparator) != -1); |
||||
} |
||||
|
||||
/** |
||||
* Given a stack containing (in order) |
||||
* <ol> |
||||
* <li>com.acme.Foo</li> |
||||
* <li>com.acme.Bar</li> |
||||
* <li>com.acme.Baz</li> |
||||
* </ol> |
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. |
||||
*/ |
||||
@Override |
||||
public synchronized String toString() { |
||||
StringBuilder builder = new StringBuilder(); |
||||
Iterator<ConfigurationClass> iterator = iterator(); |
||||
while (iterator.hasNext()) { |
||||
builder.append(iterator.next().getSimpleName()); |
||||
if (iterator.hasNext()) { |
||||
builder.append("->"); |
||||
} |
||||
} |
||||
return builder.toString(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */ |
||||
private static class NonAnnotatedConfigurationProblem extends Problem { |
||||
|
||||
public NonAnnotatedConfigurationProblem(String className, Location location) { |
||||
super(String.format("%s was imported as a @Configuration class but was not actually annotated " + |
||||
"with @Configuration. Annotate the class or do not attempt to process it.", className), location); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* {@link Problem} registered upon detection of a circular {@link Import}. |
||||
* @see Import |
||||
*/ |
||||
private static class CircularImportProblem extends Problem { |
||||
|
||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) { |
||||
super(String.format("A circular @Import has been detected: " + |
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + |
||||
"already present in the current import stack [%s]", |
||||
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(), |
||||
attemptedImport.getSimpleName(), importStack), |
||||
new Location(new ClassPathResource( |
||||
ClassUtils.convertClassNameToResourcePath(importStack.peek().getName())), |
||||
importStack.peek().getSource()) |
||||
); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,137 +0,0 @@
@@ -1,137 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.context.annotation; |
||||
|
||||
import java.lang.reflect.Modifier; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; |
||||
import org.springframework.beans.factory.parsing.FailFastProblemReporter; |
||||
import org.springframework.beans.factory.parsing.Location; |
||||
import org.springframework.beans.factory.parsing.ProblemReporter; |
||||
import static org.springframework.context.annotation.ConfigurationClassReaderUtils.*; |
||||
import static org.springframework.context.annotation.ScopedProxyMode.*; |
||||
import static org.springframework.context.annotation.StandardScopes.*; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* @author Chris Beams |
||||
*/ |
||||
public class BeanMethodTests { |
||||
|
||||
private ProblemReporter problemReporter = new FailFastProblemReporter(); |
||||
private String beanName = "foo"; |
||||
private Bean beanAnno = (Bean) createMutableAnnotation(Bean.class, ClassUtils.getDefaultClassLoader()); |
||||
private ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType("FooType"); |
||||
private ConfigurationClass declaringClass = new ConfigurationClass(); |
||||
{ declaringClass.setName("test.Config"); } |
||||
|
||||
@Test |
||||
public void testWellFormedMethod() { |
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno); |
||||
|
||||
assertThat(beanMethod.getName(), sameInstance(beanName)); |
||||
assertThat(beanMethod.getModifiers(), equalTo(0)); |
||||
assertThat(beanMethod.getReturnType(), sameInstance(returnType)); |
||||
assertThat(beanMethod.getAnnotation(Bean.class), sameInstance(beanAnno)); |
||||
assertThat(beanMethod.getAnnotation(Override.class), nullValue()); |
||||
assertThat(beanMethod.getRequiredAnnotation(Bean.class), sameInstance(beanAnno)); |
||||
try { |
||||
beanMethod.getRequiredAnnotation(Override.class); |
||||
fail("expected IllegalStateException ex"); |
||||
} catch (IllegalStateException ex) { /* expected */ } |
||||
|
||||
// must call setDeclaringClass() before calling getLocation()
|
||||
try { |
||||
beanMethod.getLocation(); |
||||
fail("expected IllegalStateException ex"); |
||||
} catch (IllegalStateException ex) { /* expected */ } |
||||
|
||||
|
||||
beanMethod.setDeclaringClass(declaringClass); |
||||
assertThat(beanMethod.getDeclaringClass(), sameInstance(declaringClass)); |
||||
|
||||
beanMethod.setSource(12); // indicating a line number
|
||||
assertEquals(beanMethod.getSource(), 12); |
||||
|
||||
Location location = beanMethod.getLocation(); |
||||
assertEquals(location.getResource(), new ClassPathResource("test/Config")); |
||||
assertEquals(location.getSource(), 12); |
||||
|
||||
// should validate without throwing as this is a well-formed method
|
||||
beanMethod.validate(problemReporter); |
||||
} |
||||
|
||||
@Test |
||||
public void finalMethodsAreIllegal() { |
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.FINAL, returnType, beanAnno); |
||||
beanMethod.setDeclaringClass(declaringClass); |
||||
try { |
||||
beanMethod.validate(problemReporter); |
||||
fail("should have failed due to final bean method"); |
||||
} catch (BeanDefinitionParsingException ex) { |
||||
assertTrue(ex.getMessage().contains("remove the final modifier")); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void privateMethodsAreIllegal() { |
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.PRIVATE, returnType, beanAnno); |
||||
beanMethod.setDeclaringClass(declaringClass); |
||||
try { |
||||
beanMethod.validate(problemReporter); |
||||
fail("should have failed due to private bean method"); |
||||
} catch (BeanDefinitionParsingException ex) { |
||||
assertTrue(ex.getMessage().contains("increase the method's visibility")); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void singletonsSansProxyAreLegal() { |
||||
Scope scope = SingletonNoProxy.class.getAnnotation(Scope.class); |
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); |
||||
beanMethod.setDeclaringClass(declaringClass); |
||||
beanMethod.validate(problemReporter); // should validate without problems - it's legal
|
||||
} |
||||
|
||||
@Test |
||||
public void sessionInterfaceScopedProxiesAreLegal() { |
||||
Scope scope = SessionInterfaceProxy.class.getAnnotation(Scope.class); |
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); |
||||
beanMethod.setDeclaringClass(declaringClass); |
||||
beanMethod.validate(problemReporter); // should validate without problems - it's legal
|
||||
} |
||||
|
||||
@Scope(value=SINGLETON, proxyMode=INTERFACES) |
||||
private class SingletonInterfaceProxy { } |
||||
|
||||
@Scope(value=SINGLETON, proxyMode=TARGET_CLASS) |
||||
private class SingletonTargetClassProxy { } |
||||
|
||||
@Scope(value=SINGLETON, proxyMode=NO) |
||||
private class SingletonNoProxy { } |
||||
|
||||
@Scope(value=PROTOTYPE, proxyMode=INTERFACES) |
||||
private class PrototypeInterfaceProxy { } |
||||
|
||||
@Scope(value=SESSION, proxyMode=INTERFACES) |
||||
private class SessionInterfaceProxy { } |
||||
} |
||||
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
/* |
||||
* Copyright 2002-2009 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.lang.annotation.Annotation; |
||||
import java.lang.reflect.Array; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.util.HashSet; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.asm.AnnotationVisitor; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.asm.commons.EmptyVisitor; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* ASM visitor which looks for the annotations defined on a class or method. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 3.0 |
||||
*/ |
||||
final class AnnotationAttributesReadingVisitor implements AnnotationVisitor { |
||||
|
||||
private final String annotationType; |
||||
|
||||
private final Map<String, Map<String, Object>> annotationMap; |
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap; |
||||
|
||||
private final ClassLoader classLoader; |
||||
|
||||
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); |
||||
|
||||
|
||||
public AnnotationAttributesReadingVisitor( |
||||
String annotationType, Map<String, Map<String, Object>> attributesMap, |
||||
Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) { |
||||
|
||||
this.annotationType = annotationType; |
||||
this.annotationMap = attributesMap; |
||||
this.metaAnnotationMap = metaAnnotationMap; |
||||
this.classLoader = classLoader; |
||||
} |
||||
|
||||
|
||||
public void visit(String name, Object value) { |
||||
Object valueToUse = value; |
||||
if (valueToUse instanceof Type) { |
||||
valueToUse = ((Type) value).getClassName(); |
||||
} |
||||
this.attributes.put(name, valueToUse); |
||||
} |
||||
|
||||
public void visitEnum(String name, String desc, String value) { |
||||
Object valueToUse = value; |
||||
try { |
||||
Class<?> enumType = this.classLoader.loadClass(Type.getType(desc).getClassName()); |
||||
Field enumConstant = ReflectionUtils.findField(enumType, value); |
||||
if (enumConstant != null) { |
||||
valueToUse = enumConstant.get(null); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
} |
||||
this.attributes.put(name, valueToUse); |
||||
} |
||||
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
|
||||
public AnnotationVisitor visitArray(final String attrName) { |
||||
return new AnnotationVisitor() { |
||||
public void visit(String name, Object value) { |
||||
Object newValue = value; |
||||
if (newValue instanceof Type) { |
||||
newValue = ((Type) value).getClassName(); |
||||
} |
||||
Object existingValue = attributes.get(attrName); |
||||
if (existingValue != null) { |
||||
newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); |
||||
} |
||||
else { |
||||
Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); |
||||
newArray[0] = newValue; |
||||
newValue = newArray; |
||||
} |
||||
attributes.put(attrName, newValue); |
||||
} |
||||
public void visitEnum(String name, String desc, String value) { |
||||
} |
||||
public AnnotationVisitor visitAnnotation(String name, String desc) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
public AnnotationVisitor visitArray(String name) { |
||||
return new EmptyVisitor(); |
||||
} |
||||
public void visitEnd() { |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public void visitEnd() { |
||||
try { |
||||
Class<?> annotationClass = this.classLoader.loadClass(this.annotationType); |
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods(); |
||||
for (Method annotationAttribute : annotationAttributes) { |
||||
String attributeName = annotationAttribute.getName(); |
||||
Object defaultValue = annotationAttribute.getDefaultValue(); |
||||
if (defaultValue != null && !this.attributes.containsKey(attributeName)) { |
||||
this.attributes.put(attributeName, defaultValue); |
||||
} |
||||
} |
||||
// Register annotations that the annotation type is annotated with.
|
||||
if (this.metaAnnotationMap != null) { |
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations(); |
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>(); |
||||
for (Annotation metaAnnotation : metaAnnotations) { |
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); |
||||
} |
||||
this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames); |
||||
} |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
// Class not found - can't determine meta-annotations.
|
||||
} |
||||
this.annotationMap.put(this.annotationType, this.attributes); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue