diff --git a/org.springframework.core/ivy.xml b/org.springframework.core/ivy.xml index 87d47b61d2c..8532a4dcc1e 100644 --- a/org.springframework.core/ivy.xml +++ b/org.springframework.core/ivy.xml @@ -23,6 +23,8 @@ + + diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java new file mode 100644 index 00000000000..817c3ce3091 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2006 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.annotation; + +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; + +/** + * {@link java.util.Comparator} implementation that checks + * {@link org.springframework.core.Ordered} as well as the + * {@link Order} annotation, with an order value provided by an + * Ordered instance overriding a statically defined + * annotation value (if any). + * + * @author Juergen Hoeller + * @since 2.0.1 + * @see org.springframework.core.Ordered + * @see Order + */ +public class AnnotationAwareOrderComparator extends OrderComparator { + + protected int getOrder(Object obj) { + if (obj instanceof Ordered) { + return ((Ordered) obj).getOrder(); + } + if (obj != null) { + Order order = obj.getClass().getAnnotation(Order.class); + if (order != null) { + return order.value(); + } + } + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java new file mode 100644 index 00000000000..5e38149b444 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -0,0 +1,345 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.BridgeMethodResolver; +import org.springframework.util.Assert; + +/** + * General utility methods for working with annotations, handling bridge methods + * (which the compiler generates for generic declarations) as well as super + * methods (for optional "annotation inheritance"). Note that none of + * this is provided by the JDK's introspection facilities themselves. + * + *

As a general rule for runtime-retained annotations (e.g. for transaction + * control, authorization or service exposure), always use the lookup methods on + * this class (e.g., {@link #findAnnotation(Method, Class)}, + * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) + * instead of the plain annotation lookup methods in the JDK. You can still + * explicitly choose between lookup on the given class level only + * ({@link #getAnnotation(Method, Class)}) and lookup in the entire inheritance + * hierarchy of the given method ({@link #findAnnotation(Method, Class)}). + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Sam Brannen + * @author Mark Fisher + * @since 2.0 + * @see java.lang.reflect.Method#getAnnotations() + * @see java.lang.reflect.Method#getAnnotation(Class) + */ +public abstract class AnnotationUtils { + + /** The attribute name for annotations with a single element */ + static final String VALUE = "value"; + + + /** + * Get all {@link Annotation Annotations} from the supplied {@link Method}. + *

Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param method the method to look for annotations on + * @return the annotations found + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static Annotation[] getAnnotations(Method method) { + return BridgeMethodResolver.findBridgedMethod(method).getAnnotations(); + } + + /** + * Get a single {@link Annotation} of annotationType from the + * supplied {@link Method}. + *

Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param method the method to look for annotations on + * @param annotationType the annotation class to look for + * @return the annotations found + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static A getAnnotation(Method method, Class annotationType) { + return BridgeMethodResolver.findBridgedMethod(method).getAnnotation(annotationType); + } + + /** + * Get a single {@link Annotation} of annotationType from the + * supplied {@link Method}, traversing its super methods if no annotation + * can be found on the given method itself. + *

Annotations on methods are not inherited by default, so we need to handle + * this explicitly. Tge + * @param method the method to look for annotations on + * @param annotationType the annotation class to look for + * @return the annotation found, or null if none found + */ + public static A findAnnotation(Method method, Class annotationType) { + A annotation = getAnnotation(method, annotationType); + Class cl = method.getDeclaringClass(); + while (annotation == null) { + cl = cl.getSuperclass(); + if (cl == null || cl == Object.class) { + break; + } + try { + Method equivalentMethod = cl.getDeclaredMethod(method.getName(), method.getParameterTypes()); + annotation = getAnnotation(equivalentMethod, annotationType); + } + catch (NoSuchMethodException ex) { + // We're done... + } + } + return annotation; + } + + /** + * Find a single {@link Annotation} of annotationType from the + * supplied {@link Class}, traversing its interfaces and super classes + * if no annotation can be found on the given class itself. + *

This method explicitly handles class-level annotations which are + * not declared as {@link java.lang.annotation.Inherited inherited} + * as well as annotations on interfaces. + *

The algorithm operates as follows: Searches for an annotation on the given + * class and returns it if found. Else searches all interfaces that the given + * class declares, returning the annotation from the first matching candidate, + * if any. Else proceeds with introspection of the superclass of the given class, + * checking the superclass itself; if no annotation found there, proceeds with + * the interfaces that the superclass declares. Recursing up through the entire + * superclass hierarchy if no match is found. + * @param clazz the class to look for annotations on + * @param annotationType the annotation class to look for + * @return the annotation found, or null if none found + */ + public static A findAnnotation(Class clazz, Class annotationType) { + Assert.notNull(clazz, "Class must not be null"); + A annotation = clazz.getAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + for (Class ifc : clazz.getInterfaces()) { + annotation = findAnnotation(ifc, annotationType); + if (annotation != null) { + return annotation; + } + } + Class superClass = clazz.getSuperclass(); + if (superClass == null || superClass == Object.class) { + return null; + } + return findAnnotation(superClass, annotationType); + } + + /** + * Find the first {@link Class} in the inheritance hierarchy of the + * specified clazz (including the specified + * clazz itself) which declares an annotation for the + * specified annotationType, or null if not + * found. If the supplied clazz is null, + * null will be returned. + *

If the supplied clazz is an interface, only the interface + * itself will be checked; the inheritance hierarchy for interfaces will not + * be traversed. + *

The standard {@link Class} API does not provide a mechanism for + * determining which class in an inheritance hierarchy actually declares an + * {@link Annotation}, so we need to handle this explicitly. + * @param annotationType the Class object corresponding to the annotation type + * @param clazz the Class object corresponding to the class on which to + * check for the annotation, or null. + * @return the first {@link Class} in the inheritance hierarchy of the + * specified clazz which declares an annotation for the specified + * annotationType, or null if not found. + * @see Class#isAnnotationPresent(Class) + * @see Class#getDeclaredAnnotations() + */ + public static Class findAnnotationDeclaringClass(Class annotationType, Class clazz) { + Assert.notNull(annotationType, "Annotation type must not be null"); + if (clazz == null || clazz.equals(Object.class)) { + return null; + } + return (isAnnotationDeclaredLocally(annotationType, clazz)) ? + clazz : findAnnotationDeclaringClass(annotationType, clazz.getSuperclass()); + } + + /** + * Determine whether an annotation for the specified annotationType + * is declared locally on the supplied clazz. + * The supplied {@link Class} object may represent any type. + *

Note: This method does not determine if the annotation + * is {@link java.lang.annotation.Inherited inherited}. For greater clarity + * regarding inherited annotations, consider using + * {@link #isAnnotationInherited(Class, Class)} instead. + * @param annotationType the Class object corresponding to the annotation type + * @param clazz the Class object corresponding to the class on which to + * check for the annotation + * @return true if an annotation for the specified + * annotationType is declared locally on the supplied clazz + * @see Class#getDeclaredAnnotations() + * @see #isAnnotationInherited(Class, Class) + */ + public static boolean isAnnotationDeclaredLocally(Class annotationType, Class clazz) { + Assert.notNull(annotationType, "Annotation type must not be null"); + Assert.notNull(clazz, "Class must not be null"); + boolean declaredLocally = false; + for (Annotation annotation : Arrays.asList(clazz.getDeclaredAnnotations())) { + if (annotation.annotationType().equals(annotationType)) { + declaredLocally = true; + break; + } + } + return declaredLocally; + } + + /** + * Determine whether an annotation for the specified annotationType + * is present on the supplied clazz and is + * {@link java.lang.annotation.Inherited inherited} + * (i.e., not declared locally for the class). + *

If the supplied clazz is an interface, only the interface + * itself will be checked. In accord with standard meta-annotation + * semantics, the inheritance hierarchy for interfaces will not be + * traversed. See the {@link java.lang.annotation.Inherited JavaDoc} for the + * @Inherited meta-annotation for further details regarding annotation + * inheritance. + * @param annotationType the Class object corresponding to the annotation type + * @param clazz the Class object corresponding to the class on which to + * check for the annotation + * @return true if an annotation for the specified + * annotationType is present on the supplied clazz + * and is {@link java.lang.annotation.Inherited inherited} + * @see Class#isAnnotationPresent(Class) + * @see #isAnnotationDeclaredLocally(Class, Class) + */ + public static boolean isAnnotationInherited(Class annotationType, Class clazz) { + Assert.notNull(annotationType, "Annotation type must not be null"); + Assert.notNull(clazz, "Class must not be null"); + return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz)); + } + + /** + * Retrieve the given annotation's attributes as a Map. + * @param annotation the annotation to retrieve the attributes for + * @return the Map of annotation attributes, with attribute names as keys + * and corresponding attribute values as values + */ + public static Map getAnnotationAttributes(Annotation annotation) { + Map attrs = new HashMap(); + Method[] methods = annotation.annotationType().getDeclaredMethods(); + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { + try { + attrs.put(method.getName(), method.invoke(annotation)); + } + catch (Exception ex) { + throw new IllegalStateException("Could not obtain annotation attribute values", ex); + } + } + } + return attrs; + } + + /** + * Retrieve the value of the "value" + * attribute of a single-element Annotation, given an annotation instance. + * @param annotation the annotation instance from which to retrieve the value + * @return the attribute value, or null if not found + * @see #getValue(Annotation, String) + */ + public static Object getValue(Annotation annotation) { + return getValue(annotation, VALUE); + } + + /** + * Retrieve the value of a named Annotation attribute, given an + * annotation instance. + * @see #getValue(Annotation) + * @param annotation the annotation instance from which to retrieve the value + * @param attributeName the name of the attribute value to retrieve + * @return the attribute value, or null if not found + */ + public static Object getValue(Annotation annotation, String attributeName) { + try { + Method method = annotation.annotationType().getDeclaredMethod(attributeName, new Class[0]); + return method.invoke(annotation); + } + catch (Exception ex) { + return null; + } + } + + /** + * Retrieve the default value of the + * "value" attribute of a single-element + * Annotation, given an annotation instance. + * @param annotation the annotation instance from which to retrieve + * the default value + * @return the default value, or null if not found + * @see #getDefaultValue(Annotation, String) + */ + public static Object getDefaultValue(Annotation annotation) { + return getDefaultValue(annotation, VALUE); + } + + /** + * Retrieve the default value of a named Annotation attribute, + * given an annotation instance. + * @param annotation the annotation instance from which to retrieve + * the default value + * @param attributeName the name of the attribute value to retrieve + * @return the default value of the named attribute, or null + * if not found. + * @see #getDefaultValue(Class, String) + */ + public static Object getDefaultValue(Annotation annotation, String attributeName) { + return getDefaultValue(annotation.annotationType(), attributeName); + } + + /** + * Retrieve the default value of the + * "value" attribute of a single-element + * Annotation, given the {@link Class annotation type}. + * @param annotationType the annotation type for which the + * default value should be retrieved + * @return the default value, or null if not found + * @see #getDefaultValue(Class, String) + */ + public static Object getDefaultValue(Class annotationType) { + return getDefaultValue(annotationType, VALUE); + } + + /** + * Retrieve the default value of a named Annotation attribute, + * given the {@link Class annotation type}. + * @param annotationType the annotation type for which the + * default value should be retrieved + * @param attributeName the name of the attribute value to retrieve. + * @return the default value of the named attribute, or null + * if not found + * @see #getDefaultValue(Annotation, String) + */ + public static Object getDefaultValue(Class annotationType, String attributeName) { + try { + Method method = annotationType.getDeclaredMethod(attributeName, new Class[0]); + return method.getDefaultValue(); + } + catch (Exception ex) { + return null; + } + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/Order.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/Order.java new file mode 100644 index 00000000000..917e7b8eebb --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/Order.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2006 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.Ordered; + +/** + * Annotation to define ordering. + * + *

Value is optional, and represents order value as defined + * in the Ordered interface. Lower values have higher priority. + * Default value is Integer.MAX_VALUE, indicating lowest + * priority (losing to any other specified order value). + * + * @author Rod Johnson + * @since 2.0 + * @see org.springframework.core.Ordered + * @see AnnotationAwareOrderComparator + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface Order { + + int value() default Ordered.LOWEST_PRECEDENCE; + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/package.html b/org.springframework.core/src/main/java/org/springframework/core/annotation/package.html new file mode 100644 index 00000000000..c9139bffb60 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/package.html @@ -0,0 +1,7 @@ + + + +Core support package for Java 5 annotations. + + + diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java new file mode 100644 index 00000000000..2252cc5ae25 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2007 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; + +import java.util.Map; +import java.util.Set; + +/** + * Interface that defines abstract access to the annotations of a specific + * class, in a form that does not require that class to be loaded yet. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + * @see StandardAnnotationMetadata + * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() + */ +public interface AnnotationMetadata extends ClassMetadata { + + /** + * Return the names of all annotation types defined on the underlying class. + * @return the annotation type names + */ + Set getAnnotationTypes(); + + /** + * Determine whether the underlying class has an annotation of the given + * type defined. + * @param annotationType the annotation type to look for + * @return whether a matching annotation is defined + */ + boolean hasAnnotation(String annotationType); + + /** + * Return the names of all meta-annotation types defined on the + * given annotation type of the underlying class. + * @return the meta-annotation type names + */ + Set getMetaAnnotationTypes(String annotationType); + + /** + * Determine whether the underlying class has an annotation that + * is itself annotated with the meta-annotation of the given type. + * @param metaAnnotationType the meta-annotation type to look for + * @return whether a matching meta-annotation is defined + */ + boolean hasMetaAnnotation(String metaAnnotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class). + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key + * (e.g. "value") and the defined attribute value as Map value. + * This return value will be null if no matching + * annotation is defined. + */ + Map getAnnotationAttributes(String annotationType); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java new file mode 100644 index 00000000000..c3fb972548f --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/ClassMetadata.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2008 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; + +/** + * Interface that defines abstract metadata of a specific class, + * in a form that does not require that class to be loaded yet. + * + * @author Juergen Hoeller + * @since 2.5 + * @see StandardClassMetadata + * @see org.springframework.core.type.classreading.MetadataReader#getClassMetadata() + * @see AnnotationMetadata + */ +public interface ClassMetadata { + + /** + * Return the name of the underlying class. + */ + String getClassName(); + + /** + * Return whether the underlying class represents an interface. + */ + boolean isInterface(); + + /** + * Return whether the underlying class is marked as abstract. + */ + boolean isAbstract(); + + /** + * Return whether the underlying class represents a concrete class, + * i.e. neither an interface nor an abstract class. + */ + boolean isConcrete(); + + /** + * Determine whether the underlying class is independent, + * i.e. whether it is a top-level class or a nested class + * (static inner class) that can be constructed independent + * from an enclosing class. + */ + boolean isIndependent(); + + /** + * Return whether the underlying class has an enclosing class + * (i.e. the underlying class is an inner/nested class or + * a local class within a method). + *

If this method returns false, then the + * underlying class is a top-level class. + */ + boolean hasEnclosingClass(); + + /** + * Return the name of the enclosing class of the underlying class, + * or null if the underlying class is a top-level class. + */ + String getEnclosingClassName(); + + /** + * Return whether the underlying class has a super class. + */ + boolean hasSuperClass(); + + /** + * Return the name of the super class of the underlying class, + * or null if there is no super class defined. + */ + String getSuperClassName(); + + /** + * Return the name of all interfaces that the underlying class + * implements, or an empty array if there are none. + */ + String[] getInterfaceNames(); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java new file mode 100644 index 00000000000..471cd55cb0c --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 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; + +import java.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationUtils; + +/** + * {@link AnnotationMetadata} implementation that uses standard reflection + * to introspect a given Class. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + */ +public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { + + public StandardAnnotationMetadata(Class introspectedClass) { + super(introspectedClass); + } + + + public Set getAnnotationTypes() { + Set types = new HashSet(); + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + types.add(anns[i].annotationType().getName()); + } + return types; + } + + public boolean hasAnnotation(String annotationType) { + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + if (anns[i].annotationType().getName().equals(annotationType)) { + return true; + } + } + return false; + } + + public Set getMetaAnnotationTypes(String annotationType) { + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + if (anns[i].annotationType().getName().equals(annotationType)) { + Set types = new HashSet(); + Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + for (Annotation meta : metaAnns) { + types.add(meta.annotationType().getName()); + } + return types; + } + } + return null; + } + + public boolean hasMetaAnnotation(String annotationType) { + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + for (Annotation meta : metaAnns) { + if (meta.annotationType().getName().equals(annotationType)) { + return true; + } + } + } + return false; + } + + public Map getAnnotationAttributes(String annotationType) { + Annotation[] anns = getIntrospectedClass().getAnnotations(); + for (int i = 0; i < anns.length; i++) { + Annotation ann = anns[i]; + if (ann.annotationType().getName().equals(annotationType)) { + return AnnotationUtils.getAnnotationAttributes(ann); + } + } + return null; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java new file mode 100644 index 00000000000..162cae63b4c --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardClassMetadata.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2008 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; + +import java.lang.reflect.Modifier; + +/** + * {@link ClassMetadata} implementation that uses standard reflection + * to introspect a given Class. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class StandardClassMetadata implements ClassMetadata { + + private final Class introspectedClass; + + + public StandardClassMetadata(Class introspectedClass) { + this.introspectedClass = introspectedClass; + } + + public final Class getIntrospectedClass() { + return this.introspectedClass; + } + + + public String getClassName() { + return getIntrospectedClass().getName(); + } + + public boolean isInterface() { + return getIntrospectedClass().isInterface(); + } + + public boolean isAbstract() { + return Modifier.isAbstract(getIntrospectedClass().getModifiers()); + } + + public boolean isConcrete() { + return !(isInterface() || isAbstract()); + } + + public boolean isIndependent() { + return (!hasEnclosingClass() || + (getIntrospectedClass().getDeclaringClass() != null && + Modifier.isStatic(getIntrospectedClass().getModifiers()))); + } + + public boolean hasEnclosingClass() { + return (getIntrospectedClass().getEnclosingClass() != null); + } + + public String getEnclosingClassName() { + Class enclosingClass = getIntrospectedClass().getEnclosingClass(); + return (enclosingClass != null ? enclosingClass.getName() : null); + } + + public boolean hasSuperClass() { + return (getIntrospectedClass().getSuperclass() != null); + } + + public String getSuperClassName() { + Class superClass = getIntrospectedClass().getSuperclass(); + return (superClass != null ? superClass.getName() : null); + } + + public String[] getInterfaceNames() { + Class[] ifcs = getIntrospectedClass().getInterfaces(); + String[] ifcNames = new String[ifcs.length]; + for (int i = 0; i < ifcs.length; i++) { + ifcNames[i] = ifcs[i].getName(); + } + return ifcNames; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java new file mode 100644 index 00000000000..9236bf38fa9 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2008 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.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.EmptyVisitor; + +import org.springframework.core.type.AnnotationMetadata; + +/** + * ASM class visitor which looks for the class name and implemented types as + * well as for the annotations defined on the class, exposing them through + * the {@link org.springframework.core.type.AnnotationMetadata} interface. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + */ +class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { + + private final Map> attributesMap = new LinkedHashMap>(); + + private final Map> metaAnnotationMap = new LinkedHashMap>(); + + + private final ClassLoader classLoader; + + + public AnnotationMetadataReadingVisitor(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + + public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { + final String className = Type.getType(desc).getClassName(); + final Map attributes = new LinkedHashMap(); + return new EmptyVisitor() { + public void visit(String name, Object value) { + // Explicitly defined annotation attribute value. + attributes.put(name, value); + } + public void visitEnd() { + try { + Class annotationClass = classLoader.loadClass(className); + // Check declared default values of attributes in the annotation type. + Method[] annotationAttributes = annotationClass.getMethods(); + for (int i = 0; i < annotationAttributes.length; i++) { + Method annotationAttribute = annotationAttributes[i]; + String attributeName = annotationAttribute.getName(); + Object defaultValue = annotationAttribute.getDefaultValue(); + if (defaultValue != null && !attributes.containsKey(attributeName)) { + attributes.put(attributeName, defaultValue); + } + } + // Register annotations that the annotation type is annotated with. + Annotation[] metaAnnotations = annotationClass.getAnnotations(); + Set metaAnnotationTypeNames = new HashSet(); + for (Annotation metaAnnotation : metaAnnotations) { + metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); + } + metaAnnotationMap.put(className, metaAnnotationTypeNames); + } + catch (ClassNotFoundException ex) { + // Class not found - can't determine meta-annotations. + } + attributesMap.put(className, attributes); + } + }; + } + + + public Set getAnnotationTypes() { + return this.attributesMap.keySet(); + } + + public boolean hasAnnotation(String annotationType) { + return this.attributesMap.containsKey(annotationType); + } + + public Set getMetaAnnotationTypes(String annotationType) { + return this.metaAnnotationMap.get(annotationType); + } + + public boolean hasMetaAnnotation(String metaAnnotationType) { + Collection> allMetaTypes = this.metaAnnotationMap.values(); + for (Set metaTypes : allMetaTypes) { + if (metaTypes.contains(metaAnnotationType)) { + return true; + } + } + return false; + } + + public Map getAnnotationAttributes(String annotationType) { + return this.attributesMap.get(annotationType); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java new file mode 100644 index 00000000000..ea1843966ac --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2007 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.util.HashMap; +import java.util.Map; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * Caching implementation of the {@link MetadataReaderFactory} interface, + * caching an ASM {@link org.objectweb.asm.ClassReader} per Spring Resource handle + * (i.e. per ".class" file). + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { + + private final Map classReaderCache = new HashMap(); + + + /** + * Create a new CachingMetadataReaderFactory for the default class loader. + */ + public CachingMetadataReaderFactory() { + super(); + } + + /** + * Create a new CachingMetadataReaderFactory for the given resource loader. + * @param resourceLoader the Spring ResourceLoader to use + * (also determines the ClassLoader to use) + */ + public CachingMetadataReaderFactory(ResourceLoader resourceLoader) { + super(resourceLoader); + } + + /** + * Create a new CachingMetadataReaderFactory for the given class loader. + * @param classLoader the ClassLoader to use + */ + public CachingMetadataReaderFactory(ClassLoader classLoader) { + super(classLoader); + } + + + public MetadataReader getMetadataReader(Resource resource) throws IOException { + synchronized (this.classReaderCache) { + MetadataReader metadataReader = this.classReaderCache.get(resource); + if (metadataReader == null) { + metadataReader = super.getMetadataReader(resource); + this.classReaderCache.put(resource, metadataReader); + } + return metadataReader; + } + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java new file mode 100644 index 00000000000..892bc6a0d8b --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2008 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.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.EmptyVisitor; + +import org.springframework.core.type.ClassMetadata; +import org.springframework.util.ClassUtils; + +/** + * ASM class visitor which looks only for the class name and implemented types, + * exposing them through the {@link org.springframework.core.type.ClassMetadata} + * interface. + * + * @author Rod Johnson + * @author Costin Leau + * @author Mark Fisher + * @author Ramnivas Laddad + * @since 2.5 + */ +class ClassMetadataReadingVisitor extends EmptyVisitor implements ClassMetadata { + + private String className; + + private boolean isInterface; + + private boolean isAbstract; + + private String enclosingClassName; + + private boolean independentInnerClass; + + private String superClassName; + + private String[] interfaces; + + + public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { + this.className = ClassUtils.convertResourcePathToClassName(name); + this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); + this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0); + if (supername != null) { + this.superClassName = ClassUtils.convertResourcePathToClassName(supername); + } + this.interfaces = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + this.interfaces[i] = ClassUtils.convertResourcePathToClassName(interfaces[i]); + } + } + + public void visitOuterClass(String owner, String name, String desc) { + this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner); + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + if (outerName != null && this.className.equals(ClassUtils.convertResourcePathToClassName(name))) { + this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerName); + this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0); + } + } + + + public String getClassName() { + return this.className; + } + + public boolean isInterface() { + return this.isInterface; + } + + public boolean isAbstract() { + return this.isAbstract; + } + + public boolean isConcrete() { + return !(this.isInterface || this.isAbstract); + } + + public boolean isIndependent() { + return (this.enclosingClassName == null || this.independentInnerClass); + } + + public boolean hasEnclosingClass() { + return (this.enclosingClassName != null); + } + + public String getEnclosingClassName() { + return this.enclosingClassName; + } + + public boolean hasSuperClass() { + return (this.superClassName != null); + } + + public String getSuperClassName() { + return this.superClassName; + } + + public String[] getInterfaceNames() { + return this.interfaces; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java new file mode 100644 index 00000000000..b63bb7e0c7c --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 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.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; + +/** + * Simple facade for accessing class metadata, + * as read by an ASM {@link org.objectweb.asm.ClassReader}. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public interface MetadataReader { + + /** + * Read basic class metadata for the underlying class. + */ + ClassMetadata getClassMetadata(); + + /** + * Read full annotation metadata for the underlying class. + */ + AnnotationMetadata getAnnotationMetadata(); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java new file mode 100644 index 00000000000..d8f50427ec2 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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 org.springframework.core.io.Resource; + +/** + * Factory interface for {@link MetadataReader} instances. + * Allows for caching a MetadataReader per original resource. + * + * @author Juergen Hoeller + * @since 2.5 + * @see SimpleMetadataReaderFactory + * @see CachingMetadataReaderFactory + */ +public interface MetadataReaderFactory { + + /** + * Obtain a MetadataReader for the given class name. + * @param className the class name (to be resolved to a ".class" file) + * @return a holder for the ClassReader instance (never null) + * @throws IOException in case of I/O failure + */ + MetadataReader getMetadataReader(String className) throws IOException; + + /** + * Obtain a MetadataReader for the given resource. + * @param resource the resource (pointing to a ".class" file) + * @return a holder for the ClassReader instance (never null) + * @throws IOException in case of I/O failure + */ + MetadataReader getMetadataReader(Resource resource) throws IOException; + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java new file mode 100644 index 00000000000..d4b711fe814 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2008 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.objectweb.asm.ClassReader; + +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; + +/** + * {@link MetadataReader} implementation based on an ASM + * {@link org.objectweb.asm.ClassReader}. + * + *

Package-visible in order to allow for repackaging the ASM library + * without effect on users of the core.type package. + * + * @author Juergen Hoeller + * @since 2.5 + */ +class SimpleMetadataReader implements MetadataReader { + + private final ClassReader classReader; + + private final ClassLoader classLoader; + + + public SimpleMetadataReader(ClassReader classReader, ClassLoader classLoader) { + this.classReader = classReader; + this.classLoader = classLoader; + } + + + public ClassMetadata getClassMetadata() { + ClassMetadataReadingVisitor visitor = new ClassMetadataReadingVisitor(); + this.classReader.accept(visitor, true); + return visitor; + } + + public AnnotationMetadata getAnnotationMetadata() { + AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(this.classLoader); + this.classReader.accept(visitor, true); + return visitor; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java new file mode 100644 index 00000000000..90b7b2ce9b6 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2008 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 org.objectweb.asm.ClassReader; + +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ClassUtils; + +/** + * Simple implementation of the {@link MetadataReaderFactory} interface, + * creating a new ASM {@link org.objectweb.asm.ClassReader} for every request. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public class SimpleMetadataReaderFactory implements MetadataReaderFactory { + + private final ResourceLoader resourceLoader; + + + /** + * Create a new SimpleMetadataReaderFactory for the default class loader. + */ + public SimpleMetadataReaderFactory() { + this.resourceLoader = new DefaultResourceLoader(); + } + + /** + * Create a new SimpleMetadataReaderFactory for the given resource loader. + * @param resourceLoader the Spring ResourceLoader to use + * (also determines the ClassLoader to use) + */ + public SimpleMetadataReaderFactory(ResourceLoader resourceLoader) { + this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); + } + + /** + * Create a new SimpleMetadataReaderFactory for the given class loader. + * @param classLoader the ClassLoader to use + */ + public SimpleMetadataReaderFactory(ClassLoader classLoader) { + this.resourceLoader = + (classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader()); + } + + + public MetadataReader getMetadataReader(String className) throws IOException { + String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; + return getMetadataReader(this.resourceLoader.getResource(resourcePath)); + } + + public MetadataReader getMetadataReader(Resource resource) throws IOException { + InputStream is = resource.getInputStream(); + try { + return new SimpleMetadataReader(new ClassReader(is), this.resourceLoader.getClassLoader()); + } + finally { + is.close(); + } + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/package.html b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/package.html new file mode 100644 index 00000000000..4af765c4ffe --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/package.html @@ -0,0 +1,7 @@ + + + +Core support package for type introspection through ASM-based class reading. + + + diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java new file mode 100644 index 00000000000..3d4bbdd744e --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.io.IOException; + +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; + +/** + * Type filter that exposes a + * {@link org.springframework.core.type.ClassMetadata} object + * to subclasses, for class testing purposes. + * + * @author Rod Johnson + * @author Costin Leau + * @author Juergen Hoeller + * @since 2.5 + * @see #match(org.springframework.core.type.ClassMetadata) + */ +public abstract class AbstractClassTestingTypeFilter implements TypeFilter { + + public final boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + + return match(metadataReader.getClassMetadata()); + } + + /** + * Determine a match based on the given ClassMetadata object. + * @param metadata the ClassMetadata object + * @return whether this filter matches on the specified type + */ + protected abstract boolean match(ClassMetadata metadata); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java new file mode 100644 index 00000000000..87d1411a0b3 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.io.IOException; + +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; + +/** + * Type filter that is aware of traversing over hierarchy. + * + *

This filter is useful when matching needs to be made based on potentially the + * whole class/interface hierarchy. The algorithm employed uses succeed-fast + * strategy i.e. if at anytime a match is declared, no further processing is + * carried out. + * + * @author Ramnivas Laddad + * @author Mark Fisher + * @since 2.5 + */ +public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilter { + + private final boolean considerInherited; + + private final boolean considerInterfaces; + + + protected AbstractTypeHierarchyTraversingFilter(boolean considerInherited, boolean considerInterfaces) { + this.considerInherited = considerInherited; + this.considerInterfaces = considerInterfaces; + } + + + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + + // This method optimizes avoiding unnecessary creation of ClassReaders + // as well as visiting over those readers. + if (matchSelf(metadataReader)) { + return true; + } + ClassMetadata metadata = metadataReader.getClassMetadata(); + if (matchClassName(metadata.getClassName())) { + return true; + } + + if (!this.considerInherited) { + return false; + } + if (metadata.hasSuperClass()) { + // Optimization to avoid creating ClassReader for super class. + Boolean superClassMatch = matchSuperClass(metadata.getSuperClassName()); + if (superClassMatch != null) { + if (superClassMatch.booleanValue()) { + return true; + } + } + else { + // Need to read super class to determine a match... + if (match(metadata.getSuperClassName(), metadataReaderFactory)) { + return true; + } + } + } + + if (!this.considerInterfaces) { + return false; + } + for (String ifc : metadata.getInterfaceNames()) { + // Optimization to avoid creating ClassReader for super class + Boolean interfaceMatch = matchInterface(ifc); + if (interfaceMatch != null) { + if (interfaceMatch.booleanValue()) { + return true; + } + } + else { + // Need to read interface to determine a match... + if (match(ifc, metadataReaderFactory)) { + return true; + } + } + } + + return false; + } + + private boolean match(String className, MetadataReaderFactory metadataReaderFactory) throws IOException { + return match(metadataReaderFactory.getMetadataReader(className), metadataReaderFactory); + } + + /** + * Override this to match self characteristics alone. Typically, + * the implementation will use a visitor to extract information + * to perform matching. + */ + protected boolean matchSelf(MetadataReader metadataReader) { + return false; + } + + /** + * Override this to match on type name. + */ + protected boolean matchClassName(String className) { + return false; + } + + /** + * Override this to match on super type name. + */ + protected Boolean matchSuperClass(String superClassName) { + return null; + } + + /** + * Override this to match on interface type name. + */ + protected Boolean matchInterface(String interfaceNames) { + return null; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java new file mode 100644 index 00000000000..777ead9157a --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; + +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; + +/** + * A simple filter which matches classes with a given annotation, + * checking inherited annotations as well. + * + *

The matching logic mirrors that of Class.isAnnotationPresent(). + * + * @author Mark Fisher + * @author Ramnivas Laddad + * @author Juergen Hoeller + * @since 2.5 + */ +public class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter { + + private final Class annotationType; + + private final boolean considerMetaAnnotations; + + + /** + * Create a new AnnotationTypeFilter for the given annotation type. + * This filter will also match meta-annotations. To disable the + * meta-annotation matching, use the constructor that accepts a + * 'considerMetaAnnotations' argument. + * @param annotationType the annotation type to match + */ + public AnnotationTypeFilter(Class annotationType) { + this(annotationType, true); + } + + /** + * Create a new AnnotationTypeFilter for the given annotation type. + * @param annotationType the annotation type to match + * @param considerMetaAnnotations whether to also match on meta-annotations + */ + public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations) { + super(annotationType.isAnnotationPresent(Inherited.class), false); + this.annotationType = annotationType; + this.considerMetaAnnotations = considerMetaAnnotations; + } + + + @Override + protected boolean matchSelf(MetadataReader metadataReader) { + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + return metadata.hasAnnotation(this.annotationType.getName()) || + (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); + } + + @Override + protected Boolean matchSuperClass(String superClassName) { + if (Object.class.getName().equals(superClassName)) { + return Boolean.FALSE; + } + else if (superClassName.startsWith("java.")) { + try { + Class clazz = getClass().getClassLoader().loadClass(superClassName); + return Boolean.valueOf(clazz.getAnnotation(this.annotationType) != null); + } + catch (ClassNotFoundException ex) { + // Class not found - can't determine a match that way. + } + } + return null; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java new file mode 100644 index 00000000000..262338d9124 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.io.IOException; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.weaver.ResolvedType; +import org.aspectj.weaver.World; +import org.aspectj.weaver.bcel.BcelWorld; +import org.aspectj.weaver.patterns.Bindings; +import org.aspectj.weaver.patterns.FormalBinding; +import org.aspectj.weaver.patterns.IScope; +import org.aspectj.weaver.patterns.PatternParser; +import org.aspectj.weaver.patterns.SimpleScope; +import org.aspectj.weaver.patterns.TypePattern; + +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; + +/** + * Type filter that uses AspectJ type pattern for matching. + * + *

A critical implementation details of this type filter is that it does not + * load the class being examined to match with a type pattern. + * + * @author Ramnivas Laddad + * @author Juergen Hoeller + * @since 2.5 + */ +public class AspectJTypeFilter implements TypeFilter { + + private final World world; + + private final TypePattern typePattern; + + + public AspectJTypeFilter(String typePatternExpression, ClassLoader classLoader) { + this.world = new BcelWorld(classLoader, IMessageHandler.THROW, null); + this.world.setBehaveInJava5Way(true); + PatternParser patternParser = new PatternParser(typePatternExpression); + TypePattern typePattern = patternParser.parseTypePattern(); + typePattern.resolve(this.world); + IScope scope = new SimpleScope(this.world, new FormalBinding[0]); + this.typePattern = typePattern.resolveBindings(scope, Bindings.NONE, false, false); + } + + + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + + String className = metadataReader.getClassMetadata().getClassName(); + ResolvedType resolvedType = this.world.resolve(className); + return this.typePattern.matchesStatically(resolvedType); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java new file mode 100644 index 00000000000..8fb8ecd3f7d --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2007 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.filter; + +/** + * A simple filter which matches classes that are assignable to a given type. + * + * @author Rod Johnson + * @author Mark Fisher + * @author Ramnivas Laddad + * @since 2.5 + */ +public class AssignableTypeFilter extends AbstractTypeHierarchyTraversingFilter { + + private final Class targetType; + + + /** + * Create a new AssignableTypeFilter for the given type. + * @param targetType the type to match + */ + public AssignableTypeFilter(Class targetType) { + super(true, true); + this.targetType = targetType; + } + + + @Override + protected boolean matchClassName(String className) { + return this.targetType.getName().equals(className); + } + + @Override + protected Boolean matchSuperClass(String superClassName) { + return matchTargetType(superClassName); + } + + @Override + protected Boolean matchInterface(String interfaceName) { + return matchTargetType(interfaceName); + } + + protected Boolean matchTargetType(String typeName) { + if (this.targetType.getName().equals(typeName)) { + return true; + } + else if (Object.class.getName().equals(typeName)) { + return Boolean.FALSE; + } + else if (typeName.startsWith("java.")) { + try { + Class clazz = getClass().getClassLoader().loadClass(typeName); + return Boolean.valueOf(this.targetType.isAssignableFrom(clazz)); + } + catch (ClassNotFoundException ex) { + // Class not found - can't determine a match that way. + } + } + return null; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java new file mode 100644 index 00000000000..16605d64b2f --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.util.regex.Pattern; + +import org.springframework.core.type.ClassMetadata; +import org.springframework.util.Assert; + +/** + * A simple filter for matching a fully-qualified class name with a regex {@link Pattern}. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.5 + */ +public class RegexPatternTypeFilter extends AbstractClassTestingTypeFilter { + + private final Pattern pattern; + + + public RegexPatternTypeFilter(Pattern pattern) { + Assert.notNull(pattern, "Pattern must not be null"); + this.pattern = pattern; + } + + + @Override + protected boolean match(ClassMetadata metadata) { + return this.pattern.matcher(metadata.getClassName()).matches(); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/TypeFilter.java b/org.springframework.core/src/main/java/org/springframework/core/type/filter/TypeFilter.java new file mode 100644 index 00000000000..bdcf33524d5 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/TypeFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 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.filter; + +import java.io.IOException; + +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +/** + * Base interface for type filters using a + * {@link org.springframework.core.type.classreading.MetadataReader}. + * + * @author Costin Leau + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + */ +public interface TypeFilter { + + /** + * Determine whether this filter matches for the class described by + * the given metadata. + * @param metadataReader the metadata reader for the target class + * @param metadataReaderFactory a factory for obtaining metadata readers + * for other classes (such as superclasses and interfaces) + * @return whether this filter matches + * @throws IOException in case of I/O failure when reading metadata + */ + boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException; + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/filter/package.html b/org.springframework.core/src/main/java/org/springframework/core/type/filter/package.html new file mode 100644 index 00000000000..fad995c6bd7 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/filter/package.html @@ -0,0 +1,7 @@ + + + +Core support package for type filtering (e.g. for classpath scanning). + + + diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/package.html b/org.springframework.core/src/main/java/org/springframework/core/type/package.html new file mode 100644 index 00000000000..45b8e1cb524 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/type/package.html @@ -0,0 +1,7 @@ + + + +Core support package for type introspection. + + + diff --git a/org.springframework.core/template.mf b/org.springframework.core/template.mf index 0a4613d1164..4a15825dd92 100644 --- a/org.springframework.core/template.mf +++ b/org.springframework.core/template.mf @@ -8,6 +8,7 @@ Import-Template: org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", org.objectweb.asm.*;version="[2.2.3, 3.0.0)";resolution:=optional, org.apache.log4j.*;version="[1.2.15, 2.0.0)";resolution:=optional, + org.aspectj.*;version="[1.5.4, 2.0.0)";resolution:=optional Unversioned-Imports: javax.xml.transform.*, org.w3c.dom.*,