提交 f9918f9b 编写于 作者: C Chris Beams

Formatting pass, primarily to align with Spring's convention of hard tab indentation.

上级 f8270428
......@@ -29,70 +29,74 @@ import org.springframework.stereotype.Component;
/**
* Annotation indicating that a class is a "Java Configuration" class, meaning that it exposes one
* or more {@link Bean} methods. Holds similar information to that held in the default values of a
* bean factory; can generally be thought of as the JavaConfig equivalent of XML's 'beans' root
* element.
*
* <p>Note however that the information here is not used to populate the defaults of the owning bean
* factory, which would affect other configurations. In the style of the Java configuration
* mechanism generally, each Java configuration class is kept isolated.</p>
*
* <p>Configuration-annotated classes are also candidates for component scanning thanks to the fact
* that this annotation is meta-annotated with {@link Component @Component}.</p>
*
* @author Rod Johnson
* @author Chris Beams
* Annotation indicating that a class is a "Java Configuration" class, meaning that it
* exposes one or more {@link Bean} methods. Holds similar information to that held in the
* default values of a bean factory; can generally be thought of as the JavaConfig
* equivalent of XML's 'beans' root element.
*
* <p>
* Note however that the information here is not used to populate the defaults of the owning
* bean factory, which would affect other configurations. In the style of the Java
* configuration mechanism generally, each Java configuration class is kept isolated.
* </p>
*
* <p>
* Configuration-annotated classes are also candidates for component scanning thanks to the
* fact that this annotation is meta-annotated with {@link Component @Component}.
* </p>
*
* @author Rod Johnson
* @author Chris Beams
*/
@Component
@Target({ ElementType.TYPE })
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Configuration {
/**
* Configuration name. Allow different variants, such as test, production
* etc. Default will always match.
* @return
*/
String[] names() default "";
/**
* Configuration name. Allow different variants, such as test, production etc. Default
* will always match.
*
* @return
*/
String[] names() default "";
/**
* Specifies the default autowiring strategy.
*
* @see Autowire
* @return
*/
Autowire defaultAutowire() default Autowire.INHERITED;
/**
* Specifies the default autowiring strategy.
*
* @see Autowire
* @return
*/
Autowire defaultAutowire() default Autowire.INHERITED;
// /**
// * Dependency check strategy. By default, the dependency check is
// * unspecified, that is the default Spring option will apply. In most cases,
// * it means no dependency check will be done.
// *
// * @see DependencyCheck
// * @return
// */
// DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED;
//
// /**
// * Bean instantiation strategy. By default, it is unspecified.
// *
// * @see Lazy
// * @return
// */
// Lazy defaultLazy() default Lazy.UNSPECIFIED;
// /**
// * Dependency check strategy. By default, the dependency check is
// * unspecified, that is the default Spring option will apply. In most cases,
// * it means no dependency check will be done.
// *
// * @see DependencyCheck
// * @return
// */
// DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED;
//
// /**
// * Bean instantiation strategy. By default, it is unspecified.
// *
// * @see Lazy
// * @return
// */
// Lazy defaultLazy() default Lazy.UNSPECIFIED;
/**
* Do we autowire with aspects from the enclosing factory scope?
*/
boolean useFactoryAspects() default false;
/**
* Do we autowire with aspects from the enclosing factory scope?
*/
boolean useFactoryAspects() default false;
/**
* Do we check {@link Required @Required} methods to make sure they've been
* called?
*/
boolean checkRequired() default false;
/**
* Do we check {@link Required @Required} methods to make sure they've been called?
*/
boolean checkRequired() default false;
}
......@@ -21,154 +21,157 @@ import java.util.ArrayList;
import java.util.List;
/**
* An abstract representation of a set of user-provided "Configuration classes", usually but not
* necessarily annotated with {@link Configuration @Configuration}. The model is populated with a
* {@link org.springframework.config.java.internal.parsing.ConfigurationParser} implementation which
* may be reflection-based or ASM-based. Once a model has been populated, it can then be rendered
* out to a set of BeanDefinitions. The model provides an important layer of indirection between the
* complexity of parsing a set of classes and the complexity of representing the contents of those
* classes as BeanDefinitions.
*
* <p>Interface follows the builder pattern for method chaining.</p>
*
* @author Chris Beams
* @see org.springframework.config.java.internal.parsing.ConfigurationParser
* An abstract representation of a set of user-provided "Configuration classes", usually but
* not necessarily annotated with {@link Configuration @Configuration}. The model is
* populated with a
* {@link org.springframework.config.java.internal.parsing.ConfigurationParser}
* implementation which may be reflection-based or ASM-based. Once a model has been
* populated, it can then be rendered out to a set of BeanDefinitions. The model provides an
* important layer of indirection between the complexity of parsing a set of classes and the
* complexity of representing the contents of those classes as BeanDefinitions.
*
* <p>
* Interface follows the builder pattern for method chaining.
* </p>
*
* @author Chris Beams
* @see org.springframework.config.java.internal.parsing.ConfigurationParser
*/
public final class ConfigurationModel implements Validatable {
/* list is used because order and collection equality matters. */
private final ArrayList<ConfigurationClass> configurationClasses = new ArrayList<ConfigurationClass>();
private final ArrayList<Validator> validators = new ArrayList<Validator>();
/**
* Add a {@link Configuration @Configuration} class to the model. Classes may be added at will
* and without any particular validation. Malformed classes will be caught and errors processed
* during {@link #validate() validation}
*
* @param configurationClass user-supplied Configuration class
*/
public ConfigurationModel add(ConfigurationClass configurationClass) {
configurationClasses.add(configurationClass);
return this;
}
public void registerValidator(Validator validator) {
validators.add(validator);
}
/**
* Return configuration classes that have been directly added to this model.
*
* @see #getAllConfigurationClasses()
*/
public ConfigurationClass[] getConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[] { });
}
// /**
// * Return all configuration classes, including all imported configuration classes. This method
// * should be generally preferred over {@link #getConfigurationClasses()}
// *
// * @see #getConfigurationClasses()
// */
// public ConfigurationClass[] getAllConfigurationClasses() {
// ArrayList<ConfigurationClass> allConfigClasses = new ArrayList<ConfigurationClass>();
//
// for (ConfigurationClass configClass : configurationClasses)
// allConfigClasses.addAll(configClass.getSelfAndAllImports());
//
// return allConfigClasses.toArray(new ConfigurationClass[allConfigClasses.size()]);
// }
public ConfigurationClass[] getAllConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
}
/**
* Recurses through the model validating each object along the way and aggregating any <var>errors</var>.
*
* @see ConfigurationClass#validate(java.util.List)
* @see ModelMethod#validate(java.util.List)
* @see Validator
* @see UsageError
*/
public void validate(List<UsageError> errors) {
// user must specify at least one configuration
if (configurationClasses.isEmpty())
errors.add(new EmptyModelError());
// cascade through model and allow handlers to register validators
// depending on where they are registered (with the model, the class, or the method)
// they will be called directly or indirectly below
for (ConfigurationClass configClass : getAllConfigurationClasses()) {
for(ModelMethod method : configClass.getMethods()) {
for(Validator validator : method.getValidators()) {
if(validator.supports(method))
method.registerValidator(validator);
// TODO: support class-level validation
// if(validator.supports(configClass))
// configClass.registerValidator(validator);
if(validator.supports(this))
this.registerValidator(validator);
}
}
}
// process any validators registered directly with this model object
for(Validator validator : validators)
validator.validate(this, errors);
// each individual configuration class must be well-formed
// note that each configClass detects usage errors on its imports recursively
// note that each configClass will recursively process its respective methods
for (ConfigurationClass configClass : configurationClasses)
configClass.validate(errors);
}
@Override
public String toString() {
return format("%s: configurationClasses=%s",
getClass().getSimpleName(), configurationClasses);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((configurationClasses == null) ? 0 : configurationClasses.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ConfigurationModel other = (ConfigurationModel) obj;
if (configurationClasses == null) {
if (other.configurationClasses != null)
return false;
} else if (!configurationClasses.equals(other.configurationClasses))
return false;
return true;
}
public class EmptyModelError extends UsageError {
public EmptyModelError() {
super(null, 0);
}
@Override
public String getDescription() {
return format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName());
}
}
/* list is used because order and collection equality matters. */
private final ArrayList<ConfigurationClass> configurationClasses = new ArrayList<ConfigurationClass>();
private final ArrayList<Validator> validators = new ArrayList<Validator>();
/**
* Add a {@link Configuration @Configuration} class to the model. Classes may be added
* at will and without any particular validation. Malformed classes will be caught and
* errors processed during {@link #validate() validation}
*
* @param configurationClass user-supplied Configuration class
*/
public ConfigurationModel add(ConfigurationClass configurationClass) {
configurationClasses.add(configurationClass);
return this;
}
public void registerValidator(Validator validator) {
validators.add(validator);
}
/**
* Return configuration classes that have been directly added to this model.
*
* @see #getAllConfigurationClasses()
*/
public ConfigurationClass[] getConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[] {});
}
// /**
// * Return all configuration classes, including all imported configuration classes.
// This method
// * should be generally preferred over {@link #getConfigurationClasses()}
// *
// * @see #getConfigurationClasses()
// */
// public ConfigurationClass[] getAllConfigurationClasses() {
// ArrayList<ConfigurationClass> allConfigClasses = new ArrayList<ConfigurationClass>();
//
// for (ConfigurationClass configClass : configurationClasses)
// allConfigClasses.addAll(configClass.getSelfAndAllImports());
//
// return allConfigClasses.toArray(new ConfigurationClass[allConfigClasses.size()]);
// }
public ConfigurationClass[] getAllConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
}
/**
* Recurses through the model validating each object along the way and aggregating any
* <var>errors</var>.
*
* @see ConfigurationClass#validate(java.util.List)
* @see ModelMethod#validate(java.util.List)
* @see Validator
* @see UsageError
*/
public void validate(List<UsageError> errors) {
// user must specify at least one configuration
if (configurationClasses.isEmpty())
errors.add(new EmptyModelError());
// cascade through model and allow handlers to register validators
// depending on where they are registered (with the model, the class, or the method)
// they will be called directly or indirectly below
for (ConfigurationClass configClass : getAllConfigurationClasses()) {
for (ModelMethod method : configClass.getMethods()) {
for (Validator validator : method.getValidators()) {
if (validator.supports(method))
method.registerValidator(validator);
// TODO: support class-level validation
// if(validator.supports(configClass))
// configClass.registerValidator(validator);
if (validator.supports(this))
this.registerValidator(validator);
}
}
}
// process any validators registered directly with this model object
for (Validator validator : validators)
validator.validate(this, errors);
// each individual configuration class must be well-formed
// note that each configClass detects usage errors on its imports recursively
// note that each configClass will recursively process its respective methods
for (ConfigurationClass configClass : configurationClasses)
configClass.validate(errors);
}
@Override
public String toString() {
return format("%s: configurationClasses=%s", getClass().getSimpleName(), configurationClasses);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((configurationClasses == null) ? 0 : configurationClasses.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ConfigurationModel other = (ConfigurationModel) obj;
if (configurationClasses == null) {
if (other.configurationClasses != null)
return false;
} else if (!configurationClasses.equals(other.configurationClasses))
return false;
return true;
}
public class EmptyModelError extends UsageError {
public EmptyModelError() {
super(null, 0);
}
@Override
public String getDescription() {
return format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName());
}
}
}
......@@ -24,9 +24,10 @@ import java.lang.annotation.Target;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.NoOp;
/**
* Meta-annotation used to identify annotations as producers of beans and/or values.
*
*
* @author Chris Beams
*/
@Retention(RetentionPolicy.RUNTIME)
......@@ -34,22 +35,21 @@ import net.sf.cglib.proxy.NoOp;
@Documented
public @interface Factory {
/**
* Specifies which registrar (if any) should be used to register
* bean definitions for this {@link Factory} method.
*/
Class<? extends BeanDefinitionRegistrar> registrarType();
/**
* Specifies which registrar (if any) should be used to register bean definitions for
* this {@link Factory} method.
*/
Class<? extends BeanDefinitionRegistrar> registrarType();
/**
* Specifies what (if any) callback should be used when processing this {@link Factory} method.
* Defaults to CGLIB's {@link NoOp}, which does nothing.
* TODO: rename (interceptorType)? to keep with the -or|-ar nomenclature
*/
Class<? extends Callback> callbackType() default NoOp.class;
/**
* Specifies what (if any) callback should be used when processing this {@link Factory}
* method. Defaults to CGLIB's {@link NoOp}, which does nothing. TODO: rename
* (interceptorType)? to keep with the -or|-ar nomenclature
*/
Class<? extends Callback> callbackType() default NoOp.class;
/**
* TODO: document
* TODO: rename
*/
Class<? extends Validator>[] validatorTypes() default {};
/**
* TODO: document TODO: rename
*/
Class<? extends Validator>[] validatorTypes() default {};
}
......@@ -22,54 +22,54 @@ import java.util.List;
/**
* TODO: rename to UsageException / move outside .internal?
*
* @author Chris Beams
*
* @author Chris Beams
*/
@SuppressWarnings("serial")
public class MalformedConfigurationException extends RuntimeException {
private final List<? extends UsageError> errors;
private final List<? extends UsageError> errors;
public MalformedConfigurationException(String message) {
super(message);
this.errors = new ArrayList<UsageError>();
}
public MalformedConfigurationException(String message) {
super(message);
this.errors = new ArrayList<UsageError>();
}
public MalformedConfigurationException(UsageError... errors) {
super(toString(errors));
this.errors = Arrays.asList(errors);
}
public MalformedConfigurationException(UsageError... errors) {
super(toString(errors));
this.errors = Arrays.asList(errors);
}
public boolean containsError(Class<? extends UsageError> errorType) {
for (UsageError error : errors)
if (error.getClass().isAssignableFrom(errorType))
return true;
public boolean containsError(Class<? extends UsageError> errorType) {
for (UsageError error : errors)
if (error.getClass().isAssignableFrom(errorType))
return true;
return false;
}
return false;
}
/**
* Render a list of syntax errors as output suitable for diagnosis via System.err.
*/
private static String toString(UsageError... errors) {
StringBuilder sb = new StringBuilder();
/**
* Render a list of syntax errors as output suitable for diagnosis via System.err.
*/
private static String toString(UsageError... errors) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append("\n");
if (errors.length == 1)
sb.append("A usage error has ");
else
sb.append(errors.length + " usage errors have ");
if (errors.length == 1)
sb.append("A usage error has ");
else
sb.append(errors.length + " usage errors have ");
sb.append("been detected:\n");
sb.append("been detected:\n");
for (int i = 0; i < errors.length; i++) {
sb.append(errors[i].toString());
if ((i + 1) < errors.length)
sb.append('\n');
}
for (int i = 0; i < errors.length; i++) {
sb.append(errors[i].toString());
if ((i + 1) < errors.length)
sb.append('\n');
}
return sb.toString();
}
return sb.toString();
}
}
......@@ -21,139 +21,141 @@ import org.springframework.util.ClassUtils;
/**
* Abstract representation of a class, free from java reflection.
* Base class used within the internal JavaConfig metamodel for
* representing {@link Configuration} classes.
*
* Abstract representation of a class, free from java reflection. Base class used within the
* internal JavaConfig metamodel for representing {@link Configuration} classes.
*
* @author Chris Beams
*/
// TODO: Consider eliminating in favor of just ConfigurationClass
public class ModelClass implements BeanMetadataElement {
private String name;
private boolean isInterface;
private String source;
/**
* Creates a new and empty ModelClass instance.
*/
public ModelClass() { }
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
*/
public ModelClass(String name) {
this(name, false);
}
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
* @param isInterface whether the represented type is an interface
*/
public ModelClass(String name, boolean isInterface) {
this.name = name;
this.isInterface = isInterface;
}
/**
* Returns the fully-qualified name of this class.
*/
public String getName() {
return name;
}
/**
* Sets the fully-qualified name of this class.
*/
public void setName(String className) {
this.name = className;
}
/**
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
*/
public String getSimpleName() {
return name == null ? null : ClassUtils.getShortName(name);
}
/**
* Returns whether the class represented by this ModelClass instance is an interface.
*/
public boolean isInterface() {
return isInterface;
}
/**
* Signifies that this class is (true) or is not (false) an interface.
*/
public void setInterface(boolean isInterface) {
this.isInterface = isInterface;
}
/**
* Returns a resource path-formatted representation of the .java
* file that declares this class
*/
public String getSource() {
return source;
}
/**
* Set the source location for this class. Must be a resource-path formatted string.
*
* @param source resource path to the .java file that declares this class.
*/
public void setSource(Object source) {
Assert.isInstanceOf(String.class, source);
this.source = (String) source;
}
/**
* Given a ModelClass instance representing a class com.acme.Foo, this method will return
* <pre>
* ModelClass: name=Foo
* </pre>
*/
@Override
public String toString() {
return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + (isInterface ? 1231 : 1237);
result = (prime * result) + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelClass other = (ModelClass) obj;
if (isInterface != other.isInterface)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
private String name;
private boolean isInterface;
private String source;
/**
* Creates a new and empty ModelClass instance.
*/
public ModelClass() {
}
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
*/
public ModelClass(String name) {
this(name, false);
}
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
* @param isInterface whether the represented type is an interface
*/
public ModelClass(String name, boolean isInterface) {
this.name = name;
this.isInterface = isInterface;
}
/**
* Returns the fully-qualified name of this class.
*/
public String getName() {
return name;
}
/**
* Sets the fully-qualified name of this class.
*/
public void setName(String className) {
this.name = className;
}
/**
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
*/
public String getSimpleName() {
return name == null ? null : ClassUtils.getShortName(name);
}
/**
* Returns whether the class represented by this ModelClass instance is an interface.
*/
public boolean isInterface() {
return isInterface;
}
/**
* Signifies that this class is (true) or is not (false) an interface.
*/
public void setInterface(boolean isInterface) {
this.isInterface = isInterface;
}
/**
* Returns a resource path-formatted representation of the .java file that declares this
* class
*/
public String getSource() {
return source;
}
/**
* Set the source location for this class. Must be a resource-path formatted string.
*
* @param source resource path to the .java file that declares this class.
*/
public void setSource(Object source) {
Assert.isInstanceOf(String.class, source);
this.source = (String) source;
}
/**
* Given a ModelClass instance representing a class com.acme.Foo, this method will
* return
*
* <pre>
* ModelClass: name=Foo
* </pre>
*/
@Override
public String toString() {
return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + (isInterface ? 1231 : 1237);
result = (prime * result) + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelClass other = (ModelClass) obj;
if (isInterface != other.isInterface)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
......@@ -34,207 +34,206 @@ import org.springframework.util.Assert;
/** TODO: JAVADOC */
public final class ModelMethod implements Validatable {
private final String name;
private final int modifiers;
private final ModelClass returnType;
private final List<Annotation> annotations = new ArrayList<Annotation>();
private transient ConfigurationClass declaringClass;
private transient int lineNumber;
private transient Factory factoryAnno;
private transient final List<Validator> validators = new ArrayList<Validator>();
public ModelMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
Assert.hasText(name);
this.name = name;
Assert.notNull(annotations);
for(Annotation annotation : annotations) {
this.annotations.add(annotation);
if(factoryAnno == null)
factoryAnno = annotation.annotationType().getAnnotation(Factory.class);
}
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
this.modifiers = modifiers;
Assert.notNull(returnType);
this.returnType = returnType;
}
public String getName() {
return name;
}
public ModelClass getReturnType() {
return returnType;
}
/**
* @see java.lang.reflect.Modifier
*/
public int getModifiers() {
return modifiers;
}
/**
* Returns the annotation on this method matching <var>annoType</var> or null
* IllegalStateException} if not present.
*
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
for(Annotation anno : annotations)
if(anno.annotationType().equals(annoType))
return (T) anno;
return null;
}
/**
* Returns the annotation on this method matching <var>annoType</var> or throws
* {@link IllegalStateException} if not present.
*
* @see #getAnnotation(Class)
*/
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
T anno = getAnnotation(annoType);
if(anno == null)
throw new IllegalStateException(
format("annotation %s not found on %s", annoType.getSimpleName(), this));
return anno;
}
/**
* Sets up bi-directional relationship between this method and its declaring class.
*
* @see ConfigurationClass#addMethod(ModelMethod)
*/
public void setDeclaringClass(ConfigurationClass declaringClass) {
this.declaringClass = declaringClass;
}
public ConfigurationClass getDeclaringClass() {
return declaringClass;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
public void registerValidator(Validator validator) {
validators.add(validator);
}
public void validate(List<UsageError> errors) {
for(Validator validator : validators)
validator.validate(this, errors);
if (Modifier.isPrivate(getModifiers()))
errors.add(new PrivateMethodError());
if (Modifier.isFinal(getModifiers()))
errors.add(new FinalMethodError());
}
public BeanDefinitionRegistrar getRegistrar() {
return getInstance(factoryAnno.registrarType());
}
public Set<Validator> getValidators() {
HashSet<Validator> validator = new HashSet<Validator>();
for(Class<? extends Validator> validatorType : factoryAnno.validatorTypes())
validator.add(getInstance(validatorType));
return validator;
}
public Callback getCallback() {
Class<? extends Callback> callbackType = factoryAnno.callbackType();
if(callbackType.equals(NoOp.class))
return NoOp.INSTANCE;
return getInstance(callbackType);
}
@Override
public String toString() {
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
return String.format("%s: name=%s; returnType=%s; modifiers=%d",
getClass().getSimpleName(), name, returnTypeName, modifiers);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result + modifiers;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelMethod other = (ModelMethod) obj;
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!annotations.equals(other.annotations))
return false;
if (modifiers != other.modifiers)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (returnType == null) {
if (other.returnType != null)
return false;
} else if (!returnType.equals(other.returnType))
return false;
return true;
}
/** JavaConfigMethods must be visible (non-private) in order to accommodate CGLIB. */
public class PrivateMethodError extends UsageError {
public PrivateMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be private", getName());
}
}
/** JavaConfigMethods must be extensible (non-final) in order to accommodate CGLIB. */
public class FinalMethodError extends UsageError {
public FinalMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be final - remove the final modifier to continue",
getName());
}
}
private final String name;
private final int modifiers;
private final ModelClass returnType;
private final List<Annotation> annotations = new ArrayList<Annotation>();
private transient ConfigurationClass declaringClass;
private transient int lineNumber;
private transient Factory factoryAnno;
private transient final List<Validator> validators = new ArrayList<Validator>();
public ModelMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
Assert.hasText(name);
this.name = name;
Assert.notNull(annotations);
for (Annotation annotation : annotations) {
this.annotations.add(annotation);
if (factoryAnno == null)
factoryAnno = annotation.annotationType().getAnnotation(Factory.class);
}
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
this.modifiers = modifiers;
Assert.notNull(returnType);
this.returnType = returnType;
}
public String getName() {
return name;
}
public ModelClass getReturnType() {
return returnType;
}
/**
* @see java.lang.reflect.Modifier
*/
public int getModifiers() {
return modifiers;
}
/**
* Returns the annotation on this method matching <var>annoType</var> or null
* IllegalStateException} if not present.
*
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
for (Annotation anno : annotations)
if (anno.annotationType().equals(annoType))
return (T) anno;
return null;
}
/**
* Returns the annotation on this method matching <var>annoType</var> or throws
* {@link IllegalStateException} if not present.
*
* @see #getAnnotation(Class)
*/
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
T anno = getAnnotation(annoType);
if (anno == null)
throw new IllegalStateException(format("annotation %s not found on %s", annoType.getSimpleName(),
this));
return anno;
}
/**
* Sets up bi-directional relationship between this method and its declaring class.
*
* @see ConfigurationClass#addMethod(ModelMethod)
*/
public void setDeclaringClass(ConfigurationClass declaringClass) {
this.declaringClass = declaringClass;
}
public ConfigurationClass getDeclaringClass() {
return declaringClass;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
public void registerValidator(Validator validator) {
validators.add(validator);
}
public void validate(List<UsageError> errors) {
for (Validator validator : validators)
validator.validate(this, errors);
if (Modifier.isPrivate(getModifiers()))
errors.add(new PrivateMethodError());
if (Modifier.isFinal(getModifiers()))
errors.add(new FinalMethodError());
}
public BeanDefinitionRegistrar getRegistrar() {
return getInstance(factoryAnno.registrarType());
}
public Set<Validator> getValidators() {
HashSet<Validator> validator = new HashSet<Validator>();
for (Class<? extends Validator> validatorType : factoryAnno.validatorTypes())
validator.add(getInstance(validatorType));
return validator;
}
public Callback getCallback() {
Class<? extends Callback> callbackType = factoryAnno.callbackType();
if (callbackType.equals(NoOp.class))
return NoOp.INSTANCE;
return getInstance(callbackType);
}
@Override
public String toString() {
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
return String.format("%s: name=%s; returnType=%s; modifiers=%d", getClass().getSimpleName(), name,
returnTypeName, modifiers);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result + modifiers;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelMethod other = (ModelMethod) obj;
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!annotations.equals(other.annotations))
return false;
if (modifiers != other.modifiers)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (returnType == null) {
if (other.returnType != null)
return false;
} else if (!returnType.equals(other.returnType))
return false;
return true;
}
/** JavaConfigMethods must be visible (non-private) in order to accommodate CGLIB. */
public class PrivateMethodError extends UsageError {
public PrivateMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be private", getName());
}
}
/** JavaConfigMethods must be extensible (non-final) in order to accommodate CGLIB. */
public class FinalMethodError extends UsageError {
public FinalMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be final - remove the final modifier to continue", getName());
}
}
}
......@@ -19,23 +19,24 @@ package org.springframework.config.java;
/**
* Enumerates the names of the scopes supported out of the box in Spring.
* <p>
* Not modeled as an actual java enum because annotations that accept a scope
* attribute must allow for user-defined scope names. Given that java
* enums are not extensible, these must remain simple string constants.
*
* @author Chris Beams
* Not modeled as an actual java enum because annotations that accept a scope attribute must
* allow for user-defined scope names. Given that java enums are not extensible, these must
* remain simple string constants.
*
* @author Chris Beams
* @since 3.0
*/
public class Scopes {
private Scopes() { }
public static final String SINGLETON = "singleton"; // see BeanDefinition.SCOPE_SINGLETON;
private Scopes() {
}
public static final String SINGLETON = "singleton";
public static final String PROTOTYPE = "prototype"; // see BeanDefinition.SCOPE_PROTOTYPE;
public static final String PROTOTYPE = "prototype";
public static final String REQUEST = "request"; // see WebApplicationContext.SCOPE_REQUEST;
public static final String REQUEST = "request";
public static final String SESSION = "session"; // see WebApplicationContext.SCOPE_SESSION;
public static final String SESSION = "session";
}
......@@ -16,57 +16,58 @@
package org.springframework.config.java;
/**
* Represents an invalid usage of JavaConfig constructs, e.g. a {@link Configuration} that declares
* no {@link Bean @Bean} methods, or declaring both {@link Bean @Bean} and
* {@link ExternalBean @ExternalBean} on a single method. Explore the type hierarchy to discover all
* possible usage errors.
*
* @author Chris Beams
* @see MalformedConfigurationException
* Represents an invalid usage of JavaConfig constructs, e.g. a {@link Configuration} that
* declares no {@link Bean @Bean} methods, or declaring both {@link Bean @Bean} and
* {@link ExternalBean @ExternalBean} on a single method. Explore the type hierarchy to
* discover all possible usage errors.
*
* @author Chris Beams
* @see MalformedConfigurationException
*/
public abstract class UsageError {
private final ModelClass clazz;
private final int lineNumber;
private final ModelClass clazz;
private final int lineNumber;
/**
* Create a new usage error, providing information about where the error was detected.
*
* @param modelClass class in which this error was detected. Null value indicates that the
* error was not local to a single class.
* @param lineNumber line number on which this error was detected (useful for tooling integration)
*
* @see ModelClass#getSource()
*/
public UsageError(ModelClass modelClass, int lineNumber) {
this.clazz = modelClass;
this.lineNumber = lineNumber;
}
/**
* Create a new usage error, providing information about where the error was detected.
*
* @param modelClass class in which this error was detected. Null value indicates that
* the error was not local to a single class.
* @param lineNumber line number on which this error was detected (useful for tooling
* integration)
*
* @see ModelClass#getSource()
*/
public UsageError(ModelClass modelClass, int lineNumber) {
this.clazz = modelClass;
this.lineNumber = lineNumber;
}
/**
* Human-readable description of this error suitable for console output or IDE tooling.
*/
public abstract String getDescription();
/**
* Human-readable description of this error suitable for console output or IDE tooling.
*/
public abstract String getDescription();
/**
* Same as {@link #getDescription()} but attributed with class and line number information. If
* modelClass constructor parameter was null, class and line number information will be omitted.
*/
public final String getAttributedDescription() {
if (clazz == null)
return getDescription();
/**
* Same as {@link #getDescription()} but attributed with class and line number
* information. If modelClass constructor parameter was null, class and line number
* information will be omitted.
*/
public final String getAttributedDescription() {
if (clazz == null)
return getDescription();
return String.format("%s:%d: %s", clazz.getSource(), lineNumber, getDescription());
}
return String.format("%s:%d: %s", clazz.getSource(), lineNumber, getDescription());
}
/**
* Delegates directly to {@link #getAttributedDescription()}.
*/
@Override
public String toString() {
return getAttributedDescription();
}
/**
* Delegates directly to {@link #getAttributedDescription()}.
*/
@Override
public String toString() {
return getAttributedDescription();
}
}
......@@ -14,9 +14,10 @@ import org.springframework.util.ReflectionUtils;
import sun.security.x509.Extension;
/**
* Misc utils
*
*
* @author Chris Beams
*/
// TODO: SJC-242 general - check cycles with s101
......@@ -25,140 +26,142 @@ import sun.security.x509.Extension;
// TODO: SJC-242 rename, repackage, document
public class Util {
private static final Log log = LogFactory.getLog(Util.class);
private Util() { }
/**
* Returns instance of type T by invoking its default or no-arg
* constructor.
* <p>
* Any reflection-related issues are re-thrown as unchecked.
*/
public static <T> T getInstance(Class<? extends T> clazz) {
try {
Constructor<? extends T> noArgCtor = clazz.getDeclaredConstructor();
ReflectionUtils.makeAccessible(noArgCtor);
return noArgCtor.newInstance();
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
throw new IllegalStateException(
format("Unexpected reflection exception - %s: %s",
ex.getClass().getName(), ex.getMessage()));
}
}
/**
* Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown. This functionality is specifically
* implemented to accomodate tooling (Spring IDE) concerns, where user-defined types will not be
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class instance, null if class could not be found
*
* @see #loadRequiredClass(String)
* @see #loadToolingSafeClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadClass(String fqClassName) {
try {
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
return null;
}
}
/**
* Loads the specified class using the default class loader, rethrowing any
* {@link ClassNotFoundException} as an unchecked exception.
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class instance
*
* @throws IllegalArgumentException if configClassName cannot be loaded.
*
* @see #loadClass(String)
* @see #loadToolingSafeClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadRequiredClass(String fqClassName) {
try {
return (Class<? extends T>)getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(
format("Class [%s] could not be loaded, check your CLASSPATH.", fqClassName), ex);
}
}
/**
* Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging statement
* and return null. This functionality is specifically implemented to accomodate tooling
* (Spring IDE) concerns, where user-defined types will not be available to the tooling.
* <p>
* ASM class reading is used throughout JavaConfig, but there are certain cases where
* classloading cannot be avoided - specifically in cases where users define their own
* {@link Extension} or {@link Factory} annotations. This method should therefore be used sparingly
* but consistently where required.
* <p>
* Because {@link ClassNotFoundException} is compensated for by returning null, callers must
* take care to handle the null case appropriately.
* <p>
* In cases where the WARN logging statement is not desired, use the {@link #loadClass(String)}
* method, which returns null but issues no logging statements.
* <p>
* This method should only ever return null in the case of a user-defined type be processed at
* tooling time. Therefore, tooling may not be able to represent any custom annotation
* semantics, but JavaConfig itself will not have any problem loading and respecting them at
* actual runtime.
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class, null if class could not be found.
*
* @see #loadClass(String)
* @see #loadRequiredClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadToolingSafeClass(String fqClassName) {
try {
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
log.warn(format("Unable to load class [%s], likely due to tooling-specific restrictions."
+ "Attempting to continue, but unexpected errors may occur", fqClassName), ex);
return null;
}
}
/**
* Uses the default ClassLoader to load <var>pathToClass</var>. Appends '.class'
* to pathToClass before attempting to load.
*
* @param pathToClass resource path to class, not including .class suffix.
* e.g.: com/acme/MyClass
*
* @return inputStream for <var>pathToClass</var>
*
* @throws RuntimeException if <var>pathToClass</var> does not exist
*/
public static InputStream getClassAsStream(String pathToClass) {
String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX;
InputStream is = ClassUtils.getDefaultClassLoader().getResourceAsStream(classFileName);
if (is == null)
throw new RuntimeException(
new FileNotFoundException("Class file [" + classFileName + "] not found"));
return is;
}
private static final Log log = LogFactory.getLog(Util.class);
private Util() {
}
/**
* Returns instance of type T by invoking its default or no-arg constructor.
* <p>
* Any reflection-related issues are re-thrown as unchecked.
*/
public static <T> T getInstance(Class<? extends T> clazz) {
try {
Constructor<? extends T> noArgCtor = clazz.getDeclaredConstructor();
ReflectionUtils.makeAccessible(noArgCtor);
return noArgCtor.newInstance();
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
throw new IllegalStateException(format("Unexpected reflection exception - %s: %s", ex.getClass()
.getName(), ex.getMessage()));
}
}
/**
* Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown. This functionality is specifically
* implemented to accomodate tooling (Spring IDE) concerns, where user-defined types
* will not be
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class instance, null if class could not be found
*
* @see #loadRequiredClass(String)
* @see #loadToolingSafeClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadClass(String fqClassName) {
try {
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
return null;
}
}
/**
* Loads the specified class using the default class loader, rethrowing any
* {@link ClassNotFoundException} as an unchecked exception.
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class instance
*
* @throws IllegalArgumentException if configClassName cannot be loaded.
*
* @see #loadClass(String)
* @see #loadToolingSafeClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadRequiredClass(String fqClassName) {
try {
return (Class<? extends T>) getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(format(
"Class [%s] could not be loaded, check your CLASSPATH.", fqClassName), ex);
}
}
/**
* Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging
* statement and return null. This functionality is specifically implemented to
* accomodate tooling (Spring IDE) concerns, where user-defined types will not be
* available to the tooling.
* <p>
* ASM class reading is used throughout JavaConfig, but there are certain cases where
* classloading cannot be avoided - specifically in cases where users define their own
* {@link Extension} or {@link Factory} annotations. This method should therefore be
* used sparingly but consistently where required.
* <p>
* Because {@link ClassNotFoundException} is compensated for by returning null, callers
* must take care to handle the null case appropriately.
* <p>
* In cases where the WARN logging statement is not desired, use the
* {@link #loadClass(String)} method, which returns null but issues no logging
* statements.
* <p>
* This method should only ever return null in the case of a user-defined type be
* processed at tooling time. Therefore, tooling may not be able to represent any custom
* annotation semantics, but JavaConfig itself will not have any problem loading and
* respecting them at actual runtime.
*
* @param <T> type of class to be returned
* @param fqClassName fully-qualified class name
*
* @return newly loaded class, null if class could not be found.
*
* @see #loadClass(String)
* @see #loadRequiredClass(String)
* @see ClassUtils#getDefaultClassLoader()
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadToolingSafeClass(String fqClassName) {
try {
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) {
log.warn(format("Unable to load class [%s], likely due to tooling-specific restrictions."
+ "Attempting to continue, but unexpected errors may occur", fqClassName), ex);
return null;
}
}
/**
* Uses the default ClassLoader to load <var>pathToClass</var>. Appends '.class' to
* pathToClass before attempting to load.
*
* @param pathToClass resource path to class, not including .class suffix. e.g.:
* com/acme/MyClass
*
* @return inputStream for <var>pathToClass</var>
*
* @throws RuntimeException if <var>pathToClass</var> does not exist
*/
public static InputStream getClassAsStream(String pathToClass) {
String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX;
InputStream is = ClassUtils.getDefaultClassLoader().getResourceAsStream(classFileName);
if (is == null)
throw new RuntimeException(new FileNotFoundException("Class file [" + classFileName
+ "] not found"));
return is;
}
}
......@@ -2,18 +2,19 @@ package org.springframework.config.java;
import java.util.List;
/**
* Indicates a type is able to be validated for errors.
*
* @see Validator
*
*
* @author Chris Beams
*/
public interface Validatable {
/**
* Validates this object, adding any errors to the supplied list of <var>errors</var>.
*/
public void validate(List<UsageError> errors);
/**
* Validates this object, adding any errors to the supplied list of <var>errors</var>.
*/
public void validate(List<UsageError> errors);
}
......@@ -4,10 +4,10 @@ import java.util.List;
/** Marker interface */
//TODO: SJC-242 document
//TODO: SJC-242 rename
// TODO: SJC-242 document
// TODO: SJC-242 rename
public interface Validator {
boolean supports(Object object);
void validate(Object object, List<UsageError> errors);
boolean supports(Object object);
void validate(Object object, List<UsageError> errors);
}
......@@ -30,19 +30,19 @@ import org.springframework.util.Assert;
/**
* Base class for all {@link MethodInterceptor} implementations.
*
*
* @author Chris Beams
*/
public abstract class AbstractMethodInterceptor implements BeanFactoryAware, MethodInterceptor {
protected final Log log = LogFactory.getLog(this.getClass());
protected DefaultListableBeanFactory beanFactory;
protected final Log log = LogFactory.getLog(this.getClass());
protected DefaultListableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
protected String getBeanName(Method method) {
return method.getName();
}
protected String getBeanName(Method method) {
return method.getName();
}
}
......@@ -37,181 +37,184 @@ import org.springframework.config.java.Validator;
/**
* Annotation to be applied to methods that create beans in a Spring context. The name of the bean
* is the method name. (It is also possible to specify aliases using the aliases array on this
* annotation.)
* Annotation to be applied to methods that create beans in a Spring context. The name of
* the bean is the method name. (It is also possible to specify aliases using the aliases
* array on this annotation.)
*
* <p>Contains information similar to that held in Spring's internal BeanDefinition metadata.</p>
* <p>
* Contains information similar to that held in Spring's internal BeanDefinition metadata.
* </p>
*
* <p>
* Bean creation methods must be non-private (default, public or protected). Bean creation
* methods may throw any exception, which will be caught and handled by the Spring container
* on processing of the configuration class.<br>
* Bean creation methods must return an object type. The decision to return a class or an
* interface will be significant in the event of proxying. Bean methods that return
* interfaces will be proxied using dynamic proxies; those that return a class will require
* CGLIB or other subclass-based proxying. It is recommended to return an interface where
* possible, as this is also consistent with best practice around loose coupling.
* </p>
*
* <p>
* Bean creation methods may reference other bean creation methods by calling them directly,
* as follows. This ensures that references between beans are strongly typed:
* </p>
*
* <p>Bean creation methods must be non-private (default, public or protected). Bean creation
* methods may throw any exception, which will be caught and handled by the Spring container on
* processing of the configuration class.<br>
* Bean creation methods must return an object type. The decision to return a class or an interface
* will be significant in the event of proxying. Bean methods that return interfaces will be proxied
* using dynamic proxies; those that return a class will require CGLIB or other subclass-based
* proxying. It is recommended to return an interface where possible, as this is also consistent
* with best practice around loose coupling.</p>
*
* <p>Bean creation methods may reference other bean creation methods by calling them directly, as
* follows. This ensures that references between beans are strongly typed:</p>
*
* @see Configuration
* @see BeanNamingStrategy
*
* @author Rod Johnson
* @author Costin Leau
* @author Chris Beams
*
* @author Rod Johnson
* @author Costin Leau
* @author Chris Beams
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Factory(registrarType=BeanRegistrar.class,
callbackType=BeanMethodInterceptor.class,
validatorTypes={BeanValidator.class, IllegalBeanOverrideValidator.class})
@Factory(registrarType = BeanRegistrar.class, callbackType = BeanMethodInterceptor.class, validatorTypes = {
BeanValidator.class, IllegalBeanOverrideValidator.class })
public @interface Bean {
/**
* Role this bean plays in the overall application configuration.
*
* @see BeanDefinition#ROLE_APPLICATION
* @see BeanDefinition#ROLE_INFRASTRUCTURE
* @see BeanDefinition#ROLE_SUPPORT
*
* @see AbstractBeanDefinition the 'role' field is assigned by default to ROLE_APPLICATION
*/
int role() default BeanDefinition.ROLE_APPLICATION;
/**
* Bean aliases.
*/
String[] aliases() default { };
/**
* Scope: whether the bean is a singleton, prototype or custom scope.
* Default is singleton.
*/
String scope() default Scopes.SINGLETON;
/**
* Bean autowire strategy.
*/
Autowire autowire() default Autowire.INHERITED;
// /**
// * Bean lazy strategy.
// */
// Lazy lazy() default Lazy.UNSPECIFIED;
//
// /**
// * A bean may be marked as primary, useful for disambiguation when looking
// * up beans by type.
// *
// * @see org.springframework.config.java.context.JavaConfigApplicationContext#getBean(Class);
// */
// Primary primary() default Primary.UNSPECIFIED;
/**
* Bean init method name. Normally this is not needed, as the initialization
* (with parameterization) can be done directly through java code.
*/
String initMethodName() default "";
/**
* Bean destroy method name.
*/
String destroyMethodName() default "";
// /**
// * Bean dependency check strategy.
// */
// DependencyCheck dependencyCheck() default DependencyCheck.UNSPECIFIED;
/**
* Beans on which the current bean depends on.
*/
String[] dependsOn() default { };
// /**
// * Metadata for the current bean.
// */
// Meta[] meta() default { };
/**
* Allow the bean to be overridden in another JavaConfig, XML or other
* non-Java configuration. This is consistent with
* DefaultListableBeanFactory's allowBeanDefinitionOverriding property,
* which defaults to true.
*
* @return whether overriding of this bean is allowed
*/
boolean allowOverriding() default true;
/**
* Role this bean plays in the overall application configuration.
*
* @see BeanDefinition#ROLE_APPLICATION
* @see BeanDefinition#ROLE_INFRASTRUCTURE
* @see BeanDefinition#ROLE_SUPPORT
*
* @see AbstractBeanDefinition the 'role' field is assigned by default to
* ROLE_APPLICATION
*/
int role() default BeanDefinition.ROLE_APPLICATION;
/**
* Bean aliases.
*/
String[] aliases() default {};
/**
* Scope: whether the bean is a singleton, prototype or custom scope. Default is
* singleton.
*/
String scope() default Scopes.SINGLETON;
/**
* Bean autowire strategy.
*/
Autowire autowire() default Autowire.INHERITED;
// /**
// * Bean lazy strategy.
// */
// Lazy lazy() default Lazy.UNSPECIFIED;
//
// /**
// * A bean may be marked as primary, useful for disambiguation when looking
// * up beans by type.
// *
// * @see
// org.springframework.config.java.context.JavaConfigApplicationContext#getBean(Class);
// */
// Primary primary() default Primary.UNSPECIFIED;
/**
* Bean init method name. Normally this is not needed, as the initialization (with
* parameterization) can be done directly through java code.
*/
String initMethodName() default "";
/**
* Bean destroy method name.
*/
String destroyMethodName() default "";
// /**
// * Bean dependency check strategy.
// */
// DependencyCheck dependencyCheck() default DependencyCheck.UNSPECIFIED;
/**
* Beans on which the current bean depends on.
*/
String[] dependsOn() default {};
// /**
// * Metadata for the current bean.
// */
// Meta[] meta() default { };
/**
* Allow the bean to be overridden in another JavaConfig, XML or other non-Java
* configuration. This is consistent with DefaultListableBeanFactory's
* allowBeanDefinitionOverriding property, which defaults to true.
*
* @return whether overriding of this bean is allowed
*/
boolean allowOverriding() default true;
}
/**
* Detects any user errors when declaring {@link Bean}-annotated methods.
*
*
* @author Chris Beams
*/
class BeanValidator implements Validator {
public boolean supports(Object object) {
return object instanceof ModelMethod;
}
public void validate(Object object, List<UsageError> errors) {
ModelMethod method = (ModelMethod) object;
// TODO: re-enable for @ScopedProxy support
// if (method.getAnnotation(ScopedProxy.class) == null)
// return;
//
// Bean bean = method.getRequiredAnnotation(Bean.class);
//
// if (bean.scope().equals(DefaultScopes.SINGLETON)
// || bean.scope().equals(DefaultScopes.PROTOTYPE))
// errors.add(new InvalidScopedProxyDeclarationError(method));
}
public boolean supports(Object object) {
return object instanceof ModelMethod;
}
public void validate(Object object, List<UsageError> errors) {
ModelMethod method = (ModelMethod) object;
// TODO: re-enable for @ScopedProxy support
// if (method.getAnnotation(ScopedProxy.class) == null)
// return;
//
// Bean bean = method.getRequiredAnnotation(Bean.class);
//
// if (bean.scope().equals(DefaultScopes.SINGLETON)
// || bean.scope().equals(DefaultScopes.PROTOTYPE))
// errors.add(new InvalidScopedProxyDeclarationError(method));
}
}
/**
* Detects any illegally-overridden {@link Bean} definitions within a particular
* {@link ConfigurationModel}
*
* @see Bean#allowOverriding()
*
*
* @author Chris Beams
*/
class IllegalBeanOverrideValidator implements Validator {
public boolean supports(Object object) {
return object instanceof ConfigurationModel;
}
public void validate(Object object, List<UsageError> errors) {
ConfigurationModel model = (ConfigurationModel) object;
ConfigurationClass[] allClasses = model.getAllConfigurationClasses();
for (int i = 0; i < allClasses.length; i++) {
for (ModelMethod method : allClasses[i].getMethods()) {
Bean bean = method.getAnnotation(Bean.class);
if (bean == null || bean.allowOverriding())
continue;
for (int j = i + 1; j < allClasses.length; j++)
if (allClasses[j].hasMethod(method.getName()))
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method));
}
}
}
public boolean supports(Object object) {
return object instanceof ConfigurationModel;
}
public void validate(Object object, List<UsageError> errors) {
ConfigurationModel model = (ConfigurationModel) object;
ConfigurationClass[] allClasses = model.getAllConfigurationClasses();
for (int i = 0; i < allClasses.length; i++) {
for (ModelMethod method : allClasses[i].getMethods()) {
Bean bean = method.getAnnotation(Bean.class);
if (bean == null || bean.allowOverriding())
continue;
for (int j = i + 1; j < allClasses.length; j++)
if (allClasses[j].hasMethod(method.getName()))
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method));
}
}
}
}
......@@ -23,6 +23,7 @@ import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
/**
* Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
* handling of bean semantics such as scoping and AOP proxying.
......@@ -34,51 +35,54 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
*/
class BeanMethodInterceptor extends AbstractMethodInterceptor {
/**
* Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the existence
* of this bean object.
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String beanName = getBeanName(method);
// TODO: re-enable for @ScopedProxy support
// boolean isScopedProxy = (AnnotationUtils.findAnnotation(method, ScopedProxy.class) != null);
//
// String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
// beanName = scopedBeanName;
/**
* Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the
* existence of this bean object.
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String beanName = getBeanName(method);
// TODO: re-enable for @ScopedProxy support
// boolean isScopedProxy = (AnnotationUtils.findAnnotation(method,
// ScopedProxy.class) != null);
//
// String scopedBeanName =
// ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
// beanName = scopedBeanName;
if (factoryContainsBean(beanName)) {
// we have an already existing cached instance of this bean -> retrieve it
Object cachedBean = beanFactory.getBean(beanName);
if (log.isInfoEnabled())
log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s",
cachedBean, method.getDeclaringClass().getSimpleName(), beanName));
if (factoryContainsBean(beanName)) {
// we have an already existing cached instance of this bean -> retrieve it
Object cachedBean = beanFactory.getBean(beanName);
if (log.isInfoEnabled())
log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s", cachedBean,
method.getDeclaringClass().getSimpleName(), beanName));
return cachedBean;
}
return cachedBean;
}
return proxy.invokeSuper(obj, args);
}
return proxy.invokeSuper(obj, args);
}
/**
* Check the beanFactory to see whether the bean named <var>beanName</var> already exists.
* Accounts for the fact that the requested bean may be "in creation", i.e.: we're in the
* middle of servicing the initial request for this bean. From JavaConfig's perspective,
* this means that the bean does not actually yet exist, and that it is now our job to
* create it for the first time by executing the logic in the corresponding Bean method.
* <p>
* Said another way, this check repurposes {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)}
* to determine whether the container is calling this method or the user is calling this method.
*
* @param beanName name of bean to check for
*
* @return true if <var>beanName</var> already exists in beanFactory
*/
private boolean factoryContainsBean(String beanName) {
return beanFactory.containsBean(beanName)
&& !beanFactory.isCurrentlyInCreation(beanName);
}
/**
* Check the beanFactory to see whether the bean named <var>beanName</var> already
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
* we're in the middle of servicing the initial request for this bean. From JavaConfig's
* perspective, this means that the bean does not actually yet exist, and that it is now
* our job to create it for the first time by executing the logic in the corresponding
* Bean method.
* <p>
* Said another way, this check repurposes
* {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
* the container is calling this method or the user is calling this method.
*
* @param beanName name of bean to check for
*
* @return true if <var>beanName</var> already exists in beanFactory
*/
private boolean factoryContainsBean(String beanName) {
return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
}
}
......@@ -26,178 +26,183 @@ import org.springframework.util.Assert;
// TODO: SJC-242 document BeanHandler
// TODO: SJC-242 make package-private
class BeanRegistrar implements BeanDefinitionRegistrar {
private static final Log logger = LogFactory.getLog(BeanRegistrar.class);
/**
* Ensures that <var>member</var> is a method and is annotated (directly or indirectly)
* with {@link Bean @Bean}.
*/
public boolean accepts(Method method) {
return AnnotationUtils.findAnnotation(method, Bean.class) != null;
}
// TODO: SJC-242 method too long
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDef = new JavaConfigBeanDefinition();
ConfigurationClass configClass = method.getDeclaringClass();
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setFactoryMethodName(method.getName());
Bean bean = method.getRequiredAnnotation(Bean.class);
Configuration defaults = configClass.getMetadata();
// consider scoping
beanDef.setScope(bean.scope());
// consider autowiring
if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire"))
beanDef.setAutowireMode(bean.autowire().value());
else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class, "defaultAutowire"))
beanDef.setAutowireMode(defaults.defaultAutowire().value());
String beanName = method.getName();
// has this already been overriden (i.e.: via XML)?
if (containsBeanDefinitionIncludingAncestry(beanName, registry)) {
BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry);
// is the existing bean definition one that was created by JavaConfig?
if (!(existingBeanDef instanceof JavaConfigBeanDefinition)) {
// no -> then it's an external override, probably XML
// ensure that overriding is ok
if (bean.allowOverriding() == false) {
UsageError error = configClass.new IllegalBeanOverrideError(null, method);
throw new MalformedConfigurationException(error);
}
// overriding is legal, return immediately
logger.info(format("Skipping loading bean definition for %s: a definition for bean '%s' already exists. "
+ "This is likely due to an override in XML.",
method, beanName));
return;
}
}
// propagate this bean's 'role' attribute
beanDef.setRole(bean.role());
// consider aliases
for (String alias : bean.aliases())
registry.registerAlias(beanName, alias);
// TODO: re-enable for Lazy support
// // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE)
// beanDef.setPrimary(true);
//
// // is this bean lazily instantiated?
// if ((bean.lazy() == Lazy.TRUE)
// || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE)))
// beanDef.setLazyInit(true);
// does this bean have a custom init-method specified?
String initMethodName = bean.initMethodName();
if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified?
String destroyMethodName = bean.destroyMethodName();
if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName);
// TODO: re-enable for @ScopedProxy support
// is this method annotated with @ScopedProxy?
// ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class);
// if (scopedProxy != null) {
// RootBeanDefinition targetDef = beanDef;
//
// // Create a scoped proxy definition for the original bean name,
// // "hiding" the target bean in an internal target definition.
// String targetBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
// scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
//
// if (scopedProxy.proxyTargetClass())
// targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// // don't need to set it explicitly here.
// else
// scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
//
// // The target bean should be ignored in favor of the scoped proxy.
// targetDef.setAutowireCandidate(false);
//
// // Register the target bean as separate bean in the factory
// registry.registerBeanDefinition(targetBeanName, targetDef);
//
// // replace the original bean definition with the target one
// beanDef = scopedProxyDefinition;
// }
// TODO: re-enable for @Meta support
// does this bean method have any @Meta annotations?
// for (Meta meta : bean.meta())
// beanDef.addMetadataAttribute(new BeanMetadataAttribute(meta.key(), meta.value()));
if(bean.dependsOn().length > 0)
beanDef.setDependsOn(bean.dependsOn());
logger.info(format("Registering bean definition for @Bean method %s.%s()",
configClass.getName(), beanName));
registry.registerBeanDefinition(beanName, beanDef);
}
private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
try {
getBeanDefinitionIncludingAncestry(beanName, registry);
return true;
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry);
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory)registry;
do {
if (clbf.containsBeanDefinition(beanName))
return registry.getBeanDefinition(beanName);
BeanFactory parent = clbf.getParentBeanFactory();
if (parent == null) {
clbf = null;
} else if (parent instanceof ConfigurableListableBeanFactory) {
clbf = (ConfigurableListableBeanFactory) parent;
// TODO: re-enable
// } else if (parent instanceof AbstractApplicationContext) {
// clbf = ((AbstractApplicationContext) parent).getBeanFactory();
} else {
throw new IllegalStateException("unknown parent type: " + parent.getClass().getName());
}
} while (clbf != null);
throw new NoSuchBeanDefinitionException(
format("No bean definition matching name '%s' "
+ "could be found in %s or its ancestry", beanName, registry));
}
private static final Log logger = LogFactory.getLog(BeanRegistrar.class);
/**
* Ensures that <var>member</var> is a method and is annotated (directly or indirectly)
* with {@link Bean @Bean}.
*/
public boolean accepts(Method method) {
return AnnotationUtils.findAnnotation(method, Bean.class) != null;
}
// TODO: SJC-242 method too long
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition();
ConfigurationClass configClass = method.getDeclaringClass();
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setFactoryMethodName(method.getName());
Bean bean = method.getRequiredAnnotation(Bean.class);
Configuration defaults = configClass.getMetadata();
// consider scoping
beanDef.setScope(bean.scope());
// consider autowiring
if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire"))
beanDef.setAutowireMode(bean.autowire().value());
else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class,
"defaultAutowire"))
beanDef.setAutowireMode(defaults.defaultAutowire().value());
String beanName = method.getName();
// has this already been overriden (i.e.: via XML)?
if (containsBeanDefinitionIncludingAncestry(beanName, registry)) {
BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry);
// is the existing bean definition one that was created by JavaConfig?
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
// no -> then it's an external override, probably XML
// ensure that overriding is ok
if (bean.allowOverriding() == false) {
UsageError error = configClass.new IllegalBeanOverrideError(null, method);
throw new MalformedConfigurationException(error);
}
// overriding is legal, return immediately
logger.info(format(
"Skipping loading bean definition for %s: a definition for bean '%s' already exists. "
+ "This is likely due to an override in XML.", method, beanName));
return;
}
}
// propagate this bean's 'role' attribute
beanDef.setRole(bean.role());
// consider aliases
for (String alias : bean.aliases())
registry.registerAlias(beanName, alias);
// TODO: re-enable for Lazy support
// // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE)
// beanDef.setPrimary(true);
//
// // is this bean lazily instantiated?
// if ((bean.lazy() == Lazy.TRUE)
// || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE)))
// beanDef.setLazyInit(true);
// does this bean have a custom init-method specified?
String initMethodName = bean.initMethodName();
if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified?
String destroyMethodName = bean.destroyMethodName();
if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName);
// TODO: re-enable for @ScopedProxy support
// is this method annotated with @ScopedProxy?
// ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class);
// if (scopedProxy != null) {
// RootBeanDefinition targetDef = beanDef;
//
// // Create a scoped proxy definition for the original bean name,
// // "hiding" the target bean in an internal target definition.
// String targetBeanName =
// ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// RootBeanDefinition scopedProxyDefinition = new
// RootBeanDefinition(ScopedProxyFactoryBean.class);
// scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName",
// targetBeanName);
//
// if (scopedProxy.proxyTargetClass())
// targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE,
// Boolean.TRUE);
// // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// // don't need to set it explicitly here.
// else
// scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass",
// Boolean.FALSE);
//
// // The target bean should be ignored in favor of the scoped proxy.
// targetDef.setAutowireCandidate(false);
//
// // Register the target bean as separate bean in the factory
// registry.registerBeanDefinition(targetBeanName, targetDef);
//
// // replace the original bean definition with the target one
// beanDef = scopedProxyDefinition;
// }
// TODO: re-enable for @Meta support
// does this bean method have any @Meta annotations?
// for (Meta meta : bean.meta())
// beanDef.addMetadataAttribute(new BeanMetadataAttribute(meta.key(),
// meta.value()));
if (bean.dependsOn().length > 0)
beanDef.setDependsOn(bean.dependsOn());
logger.info(format("Registering bean definition for @Bean method %s.%s()", configClass.getName(),
beanName));
registry.registerBeanDefinition(beanName, beanDef);
}
private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
try {
getBeanDefinitionIncludingAncestry(beanName, registry);
return true;
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry);
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry;
do {
if (clbf.containsBeanDefinition(beanName))
return registry.getBeanDefinition(beanName);
BeanFactory parent = clbf.getParentBeanFactory();
if (parent == null) {
clbf = null;
} else if (parent instanceof ConfigurableListableBeanFactory) {
clbf = (ConfigurableListableBeanFactory) parent;
// TODO: re-enable
// } else if (parent instanceof AbstractApplicationContext) {
// clbf = ((AbstractApplicationContext) parent).getBeanFactory();
} else {
throw new IllegalStateException("unknown parent type: " + parent.getClass().getName());
}
} while (clbf != null);
throw new NoSuchBeanDefinitionException(format("No bean definition matching name '%s' "
+ "could be found in %s or its ancestry", beanName, registry));
}
}
/**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
* created by JavaConfig as opposed to any other configuration source. Used in bean
* overriding cases where it's necessary to determine whether the bean definition was created
* externally (e.g. via XML).
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition created
* by JavaConfig as opposed to any other configuration source. Used in bean overriding cases
* where it's necessary to determine whether the bean definition was created externally
* (e.g. via XML).
*/
@SuppressWarnings("serial")
// TODO: SJC-242 what to do about JavaConfigBeanDefinition?
class JavaConfigBeanDefinition extends RootBeanDefinition {
class ConfigurationClassBeanDefinition extends RootBeanDefinition {
}
\ No newline at end of file
......@@ -24,93 +24,92 @@ import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
/**
* Transforms a class by adding bytecode for a class-level annotation.
* Checks to ensure that the desired annotation is not already present
* before adding. Used by {@link ConfigurationEnhancer} to dynamically add
* an {@link org.aspectj.lang.Aspect} annotation to an enhanced Configuration
* subclass.
* Transforms a class by adding bytecode for a class-level annotation. Checks to ensure that
* the desired annotation is not already present before adding. Used by
* {@link ConfigurationEnhancer} to dynamically add an {@link org.aspectj.lang.Aspect}
* annotation to an enhanced Configuration subclass.
* <p/>
* This class was originally adapted from examples the ASM 3.0 documentation.
*
*
* @author Chris Beams
*/
class AddAnnotationAdapter extends ClassAdapter {
private String annotationDesc;
private boolean isAnnotationPresent;
private String annotationDesc;
private boolean isAnnotationPresent;
/**
* Creates a new AddAnnotationAdapter instance.
*
* @param cv the ClassVisitor delegate
* @param annotationDesc name of the annotation to be added
* (in type descriptor format)
*/
public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) {
super(cv);
this.annotationDesc = annotationDesc;
}
/**
* Creates a new AddAnnotationAdapter instance.
*
* @param cv the ClassVisitor delegate
* @param annotationDesc name of the annotation to be added (in type descriptor format)
*/
public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) {
super(cv);
this.annotationDesc = annotationDesc;
}
/**
* Ensures that the version of the resulting class is Java 5 or better.
*/
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
int v = (version & 0xFF) < Constants.V1_5 ? Constants.V1_5 : version;
cv.visit(v, access, name, signature, superName, interfaces);
}
/**
* Ensures that the version of the resulting class is Java 5 or better.
*/
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
int v = (version & 0xFF) < Constants.V1_5 ? Constants.V1_5 : version;
cv.visit(v, access, name, signature, superName, interfaces);
}
/**
* Checks to ensure that the desired annotation is not already present.
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (visible && desc.equals(annotationDesc)) {
isAnnotationPresent = true;
}
return cv.visitAnnotation(desc, visible);
}
/**
* Checks to ensure that the desired annotation is not already present.
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (visible && desc.equals(annotationDesc)) {
isAnnotationPresent = true;
}
return cv.visitAnnotation(desc, visible);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
addAnnotation();
cv.visitInnerClass(name, outerName, innerName, access);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
addAnnotation();
cv.visitInnerClass(name, outerName, innerName, access);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
addAnnotation();
return cv.visitField(access, name, desc, signature, value);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
addAnnotation();
return cv.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
addAnnotation();
return cv.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
addAnnotation();
return cv.visitMethod(access, name, desc, signature, exceptions);
}
/**
* Kicks off the process of actually adding the desired annotation.
*
* @see #addAnnotation()
*/
@Override
public void visitEnd() {
addAnnotation();
cv.visitEnd();
}
/**
* Kicks off the process of actually adding the desired annotation.
*
* @see #addAnnotation()
*/
@Override
public void visitEnd() {
addAnnotation();
cv.visitEnd();
}
/**
* Actually adds the desired annotation.
*/
private void addAnnotation() {
if (!isAnnotationPresent) {
AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true);
if (av != null) {
av.visitEnd();
}
isAnnotationPresent = true;
}
}
/**
* Actually adds the desired annotation.
*/
private void addAnnotation() {
if (!isAnnotationPresent) {
AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true);
if (av != null) {
av.visitEnd();
}
isAnnotationPresent = true;
}
}
}
......@@ -46,7 +46,6 @@ import org.springframework.config.java.ConfigurationModel;
import org.springframework.config.java.ModelMethod;
/**
* Enhances {@link Configuration} classes by generating a CGLIB subclass capable of
* interacting with the Spring container to respect bean semantics.
......@@ -57,176 +56,173 @@ import org.springframework.config.java.ModelMethod;
*/
public class ConfigurationEnhancer {
private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class);
private final ArrayList<Class<? extends Callback>> callbackTypes =
new ArrayList<Class<? extends Callback>>();
private final LinkedHashSet<BeanDefinitionRegistrar> registrars =
new LinkedHashSet<BeanDefinitionRegistrar>();
private final ArrayList<Callback> callbackInstances =
new ArrayList<Callback>();
private final CallbackFilter callbackFilter =
new CallbackFilter() {
public int accept(Method candidateMethod) {
Iterator<BeanDefinitionRegistrar> iter = registrars.iterator();
for(int i=0; iter.hasNext(); i++)
if(iter.next().accepts(candidateMethod))
return i;
throw new IllegalStateException(format("No registrar is capable of " +
"handling method [%s]. Perhaps you forgot to add a catch-all registrar?",
candidateMethod.getName()));
}
};
/**
* Creates a new {@link ConfigurationEnhancer} instance.
*/
public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
notNull(beanFactory, "beanFactory must be non-null");
notNull(model, "model must be non-null");
populateRegistrarsAndCallbacks(beanFactory, model);
}
/**
* Reads the contents of {@code model} in order to populate {@link #registrars},
* {@link #callbackInstances} and {@link #callbackTypes} appropriately.
*
* @see #callbackFilter
*/
private void populateRegistrarsAndCallbacks(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
for (ModelMethod method : configClass.getMethods()) {
registrars.add(method.getRegistrar());
Callback callback = method.getCallback();
if(callback instanceof BeanFactoryAware)
((BeanFactoryAware)callback).setBeanFactory(beanFactory);
callbackInstances.add(callback);
}
}
// register a 'catch-all' registrar
registrars.add(new BeanDefinitionRegistrar() {
public boolean accepts(Method method) {
return true;
}
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
// no-op
}
});
callbackInstances.add(NoOp.INSTANCE);
for(Callback callback : callbackInstances)
callbackTypes.add(callback.getClass());
}
/**
* Loads the specified class and generates a CGLIB subclass of it equipped with container-aware
* callbacks capable of respecting scoping and other bean semantics.
*
* @return fully-qualified name of the enhanced subclass
*/
public String enhance(String configClassName) {
if (log.isInfoEnabled())
log.info("Enhancing " + configClassName);
Class<?> superclass = loadRequiredClass(configClassName);
Class<?> subclass = createClass(newEnhancer(superclass), superclass);
subclass = nestOneClassDeeperIfAspect(superclass, subclass);
if (log.isInfoEnabled())
log.info(format("Successfully enhanced %s; enhanced class name is: %s",
configClassName, subclass.getName()));
return subclass.getName();
}
/**
* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> superclass) {
Enhancer enhancer = new Enhancer();
// because callbackFilter and callbackTypes are dynamically populated
// there's no opportunity for caching. This does not appear to be causing
// any performance problem.
enhancer.setUseCache(false);
enhancer.setSuperclass(superclass);
enhancer.setUseFactory(false);
enhancer.setCallbackFilter(callbackFilter);
enhancer.setCallbackTypes(callbackTypes.toArray(new Class<?>[]{}));
return enhancer;
}
/**
* Uses enhancer to generate a subclass of superclass, ensuring that
* {@link #callbackInstances} are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer, Class<?> superclass) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
return subclass;
}
/**
* Works around a constraint imposed by the AspectJ 5 annotation-style programming model. See
* comments inline for detail.
*
* @return original subclass instance unless superclass is annnotated with @Aspect, in which
* case a subclass of the subclass is returned
*/
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false;
// check for @Aspect by name rather than by class literal to avoid
// requiring AspectJ as a runtime dependency.
for(Annotation anno : superclass.getAnnotations())
if(anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect"))
superclassIsAnAspect = true;
if(!superclassIsAnAspect)
return origSubclass;
// the superclass is annotated with AspectJ's @Aspect.
// this means that we must create a subclass of the subclass
// in order to avoid some guard logic in Spring core that disallows
// extending a concrete aspect class.
Enhancer enhancer = newEnhancer(origSubclass);
enhancer.setStrategy(new DefaultGeneratorStrategy() {
@Override
protected byte[] transform(byte[] b) throws Exception {
ClassWriter writer = new ClassWriter(false);
ClassAdapter adapter =
new AddAnnotationAdapter(writer, "Lorg/aspectj/lang/annotation/Aspect;");
ClassReader reader = new ClassReader(b);
reader.accept(adapter, false);
return writer.toByteArray();
}
});
// create a subclass of the original subclass
Class<?> newSubclass = createClass(enhancer, origSubclass);
return newSubclass;
}
private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class);
private final ArrayList<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
private final LinkedHashSet<BeanDefinitionRegistrar> registrars = new LinkedHashSet<BeanDefinitionRegistrar>();
private final ArrayList<Callback> callbackInstances = new ArrayList<Callback>();
private final CallbackFilter callbackFilter = new CallbackFilter() {
public int accept(Method candidateMethod) {
Iterator<BeanDefinitionRegistrar> iter = registrars.iterator();
for (int i = 0; iter.hasNext(); i++)
if (iter.next().accepts(candidateMethod))
return i;
throw new IllegalStateException(format("No registrar is capable of "
+ "handling method [%s]. Perhaps you forgot to add a catch-all registrar?",
candidateMethod.getName()));
}
};
/**
* Creates a new {@link ConfigurationEnhancer} instance.
*/
public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
notNull(beanFactory, "beanFactory must be non-null");
notNull(model, "model must be non-null");
populateRegistrarsAndCallbacks(beanFactory, model);
}
/**
* Reads the contents of {@code model} in order to populate {@link #registrars},
* {@link #callbackInstances} and {@link #callbackTypes} appropriately.
*
* @see #callbackFilter
*/
private void populateRegistrarsAndCallbacks(DefaultListableBeanFactory beanFactory,
ConfigurationModel model) {
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
for (ModelMethod method : configClass.getMethods()) {
registrars.add(method.getRegistrar());
Callback callback = method.getCallback();
if (callback instanceof BeanFactoryAware)
((BeanFactoryAware) callback).setBeanFactory(beanFactory);
callbackInstances.add(callback);
}
}
// register a 'catch-all' registrar
registrars.add(new BeanDefinitionRegistrar() {
public boolean accepts(Method method) {
return true;
}
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
// no-op
}
});
callbackInstances.add(NoOp.INSTANCE);
for (Callback callback : callbackInstances)
callbackTypes.add(callback.getClass());
}
/**
* Loads the specified class and generates a CGLIB subclass of it equipped with
* container-aware callbacks capable of respecting scoping and other bean semantics.
*
* @return fully-qualified name of the enhanced subclass
*/
public String enhance(String configClassName) {
if (log.isInfoEnabled())
log.info("Enhancing " + configClassName);
Class<?> superclass = loadRequiredClass(configClassName);
Class<?> subclass = createClass(newEnhancer(superclass), superclass);
subclass = nestOneClassDeeperIfAspect(superclass, subclass);
if (log.isInfoEnabled())
log.info(format("Successfully enhanced %s; enhanced class name is: %s", configClassName, subclass
.getName()));
return subclass.getName();
}
/**
* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> superclass) {
Enhancer enhancer = new Enhancer();
// because callbackFilter and callbackTypes are dynamically populated
// there's no opportunity for caching. This does not appear to be causing
// any performance problem.
enhancer.setUseCache(false);
enhancer.setSuperclass(superclass);
enhancer.setUseFactory(false);
enhancer.setCallbackFilter(callbackFilter);
enhancer.setCallbackTypes(callbackTypes.toArray(new Class<?>[] {}));
return enhancer;
}
/**
* Uses enhancer to generate a subclass of superclass, ensuring that
* {@link #callbackInstances} are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer, Class<?> superclass) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
return subclass;
}
/**
* Works around a constraint imposed by the AspectJ 5 annotation-style programming
* model. See comments inline for detail.
*
* @return original subclass instance unless superclass is annnotated with @Aspect, in
* which case a subclass of the subclass is returned
*/
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false;
// check for @Aspect by name rather than by class literal to avoid
// requiring AspectJ as a runtime dependency.
for (Annotation anno : superclass.getAnnotations())
if (anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect"))
superclassIsAnAspect = true;
if (!superclassIsAnAspect)
return origSubclass;
// the superclass is annotated with AspectJ's @Aspect.
// this means that we must create a subclass of the subclass
// in order to avoid some guard logic in Spring core that disallows
// extending a concrete aspect class.
Enhancer enhancer = newEnhancer(origSubclass);
enhancer.setStrategy(new DefaultGeneratorStrategy() {
@Override
protected byte[] transform(byte[] b) throws Exception {
ClassWriter writer = new ClassWriter(false);
ClassAdapter adapter = new AddAnnotationAdapter(writer,
"Lorg/aspectj/lang/annotation/Aspect;");
ClassReader reader = new ClassReader(b);
reader.accept(adapter, false);
return writer.toByteArray();
}
});
// create a subclass of the original subclass
Class<?> newSubclass = createClass(enhancer, origSubclass);
return newSubclass;
}
}
......@@ -19,48 +19,47 @@ import org.objectweb.asm.AnnotationVisitor;
/**
* An empty AnnotationVisitor that delegates to another AnnotationVisitor.
* This class can be used as a super class to quickly implement
* useful annotation adapter classes, just by overriding the necessary
* methods. Note that for some reason, ASM doesn't provide this class
* (it does provide MethodAdapter and ClassAdapter), thus we're following
* the general pattern and adding our own here.
*
* An empty AnnotationVisitor that delegates to another AnnotationVisitor. This class can be
* used as a super class to quickly implement useful annotation adapter classes, just by
* overriding the necessary methods. Note that for some reason, ASM doesn't provide this
* class (it does provide MethodAdapter and ClassAdapter), thus we're following the general
* pattern and adding our own here.
*
* @author Chris Beams
*/
class AnnotationAdapter implements AnnotationVisitor {
private AnnotationVisitor delegate;
private AnnotationVisitor delegate;
/**
* Creates a new AnnotationAdapter instance that will delegate
* all its calls to <var>delegate</var>.
*
* @param delegate In most cases, the delegate will simply be
* {@link AsmUtils#EMPTY_VISITOR}
*/
public AnnotationAdapter(AnnotationVisitor delegate) {
this.delegate = delegate;
}
/**
* Creates a new AnnotationAdapter instance that will delegate all its calls to
* <var>delegate</var>.
*
* @param delegate In most cases, the delegate will simply be
* {@link AsmUtils#EMPTY_VISITOR}
*/
public AnnotationAdapter(AnnotationVisitor delegate) {
this.delegate = delegate;
}
public void visit(String arg0, Object arg1) {
delegate.visit(arg0, arg1);
}
public void visit(String arg0, Object arg1) {
delegate.visit(arg0, arg1);
}
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
return delegate.visitAnnotation(arg0, arg1);
}
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
return delegate.visitAnnotation(arg0, arg1);
}
public AnnotationVisitor visitArray(String arg0) {
return delegate.visitArray(arg0);
}
public AnnotationVisitor visitArray(String arg0) {
return delegate.visitArray(arg0);
}
public void visitEnum(String arg0, String arg1, String arg2) {
delegate.visitEnum(arg0, arg1, arg2);
}
public void visitEnum(String arg0, String arg1, String arg2) {
delegate.visitEnum(arg0, arg1, arg2);
}
public void visitEnd() {
delegate.visitEnd();
}
public void visitEnd() {
delegate.visitEnd();
}
}
......@@ -30,108 +30,115 @@ import org.springframework.config.java.Util;
*/
class AsmUtils {
public static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor();
private static final Log log = LogFactory.getLog(AsmUtils.class);
/**
* @param className a standard, dot-delimeted, fully-qualified Java class name
* @return internal version of className, as per ASM guide section 2.1.2 "Internal Names"
*/
public static String convertClassNameToInternalName(String className) {
return className.replace('.', '/');
}
/**
* 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 convertTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
// TODO: SJC-242 should catch all possible cases. use case statement and switch on char
// TODO: SJC-242 converting from primitive to object here won't be intuitive to users
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 getReturnTypeFromMethodDescriptor(String methodDescriptor) {
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')')+1);
return convertTypeDescriptorToClassName(returnTypeDescriptor);
}
/**
* Creates a new ASM {@link ClassReader} for <var>pathToClass</var>. Appends '.class'
* to pathToClass before attempting to load.
*
* @throws RuntimeException if <var>pathToClass</var>+.class cannot be found on the classpath
* @throws RuntimeException if an IOException occurs when creating the new ClassReader
*/
public static ClassReader newClassReader(String pathToClass) {
InputStream is = Util.getClassAsStream(pathToClass);
return newClassReader(is);
}
/**
* Convenience method that simply returns a new ASM {@link ClassReader} instance based on
* the supplied <var>bytes</var> byte array. This method is exactly equivalent to calling
* new ClassReader(byte[]), and is mainly provided for symmetry with usage of
* {@link #newClassReader(InputStream)}.
*
* @param bytes byte array that will be provided as input to the new ClassReader instance.
*
* @return
*/
public static ClassReader newClassReader(byte[] bytes) {
return new ClassReader(bytes);
}
/**
* Convenience method that creates and returns a new ASM {@link ClassReader} for the given
* InputStream <var>is</var>, closing the InputStream after creating the ClassReader and rethrowing
* any IOException thrown during ClassReader instantiation as an unchecked exception. Logs and ignores
* any IOException thrown when closing the InputStream.
*
* @param is InputStream that will be provided to the new ClassReader instance.
*/
public static ClassReader newClassReader(InputStream is) {
try {
return new ClassReader(is);
} catch (IOException ex) {
throw new RuntimeException("An unexpected exception occurred while creating ASM ClassReader: " + ex);
} finally {
try {
is.close();
} catch (IOException ex) {
log.error("Ignoring exception thrown while closing InputStream", ex);
}
}
}
public static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor();
private static final Log log = LogFactory.getLog(AsmUtils.class);
/**
* @param className a standard, dot-delimeted, fully-qualified Java class name
* @return internal version of className, as per ASM guide section 2.1.2
* "Internal Names"
*/
public static String convertClassNameToInternalName(String className) {
return className.replace('.', '/');
}
/**
* 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 convertTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
// TODO: SJC-242 should catch all possible cases. use case statement and switch on
// char
// TODO: SJC-242 converting from primitive to object here won't be intuitive to
// users
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 getReturnTypeFromMethodDescriptor(String methodDescriptor) {
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
return convertTypeDescriptorToClassName(returnTypeDescriptor);
}
/**
* Creates a new ASM {@link ClassReader} for <var>pathToClass</var>. Appends '.class' to
* pathToClass before attempting to load.
*
* @throws RuntimeException if <var>pathToClass</var>+.class cannot be found on the
* classpath
* @throws RuntimeException if an IOException occurs when creating the new ClassReader
*/
public static ClassReader newClassReader(String pathToClass) {
InputStream is = Util.getClassAsStream(pathToClass);
return newClassReader(is);
}
/**
* Convenience method that simply returns a new ASM {@link ClassReader} instance based
* on the supplied <var>bytes</var> byte array. This method is exactly equivalent to
* calling new ClassReader(byte[]), and is mainly provided for symmetry with usage of
* {@link #newClassReader(InputStream)}.
*
* @param bytes byte array that will be provided as input to the new ClassReader
* instance.
*
* @return
*/
public static ClassReader newClassReader(byte[] bytes) {
return new ClassReader(bytes);
}
/**
* Convenience method that creates and returns a new ASM {@link ClassReader} for the
* given InputStream <var>is</var>, closing the InputStream after creating the
* ClassReader and rethrowing any IOException thrown during ClassReader instantiation as
* an unchecked exception. Logs and ignores any IOException thrown when closing the
* InputStream.
*
* @param is InputStream that will be provided to the new ClassReader instance.
*/
public static ClassReader newClassReader(InputStream is) {
try {
return new ClassReader(is);
} catch (IOException ex) {
throw new RuntimeException("An unexpected exception occurred while creating ASM ClassReader: "
+ ex);
} finally {
try {
is.close();
} catch (IOException ex) {
log.error("Ignoring exception thrown while closing InputStream", ex);
}
}
}
}
......@@ -34,113 +34,114 @@ import org.springframework.config.java.Factory;
import org.springframework.config.java.ModelClass;
import org.springframework.config.java.ModelMethod;
/**
* Visits a single method declared in a given {@link Configuration} class. Determines whether the
* method is a {@link Factory} method and if so, adds it to the {@link ConfigurationClass}.
* Visits a single method declared in a given {@link Configuration} class. Determines
* whether the method is a {@link Factory} method and if so, adds it to the
* {@link ConfigurationClass}.
*
* @author Chris Beams
*/
class ConfigurationClassMethodVisitor extends MethodAdapter {
private final ConfigurationClass configClass;
private final String methodName;
private final int modifiers;
private final ModelClass returnType;
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
private boolean isModelMethod = false;
private int lineNumber;
/**
* Creates 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) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.methodName = methodName;
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
this.modifiers = modifiers;
}
/**
* Visits a single annotation on this method. Will be called once for each
* annotation present (regardless of its RetentionPolicy).
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoClassName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoClass = loadToolingSafeClass(annoClassName);
if(annoClass == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation annotation = createMutableAnnotation(annoClass);
annotations.add(annotation);
return new MutableAnnotationVisitor(annotation);
}
/**
* 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 Factory} method or not and if so adds it to the
* enclosing {@link #configClass}.
*/
@Override
public void visitEnd() {
for(Annotation anno : annotations) {
if(anno.annotationType().getAnnotation(Factory.class) != null) {
isModelMethod = true;
break;
}
}
if(!isModelMethod)
return;
Annotation[] annoArray = annotations.toArray(new Annotation[] { });
ModelMethod method = new ModelMethod(methodName, modifiers, returnType, annoArray);
method.setLineNumber(lineNumber);
configClass.addMethod(method);
}
/**
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
* that type is an interface.
*/
private static ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) {
final ModelClass returnType = new ModelClass(getReturnTypeFromMethodDescriptor(methodDescriptor));
// detect whether the return type is an interface
newClassReader(convertClassNameToResourcePath(returnType.getName())).accept(
new ClassAdapter(AsmUtils.EMPTY_VISITOR) {
@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;
}
private final ConfigurationClass configClass;
private final String methodName;
private final int modifiers;
private final ModelClass returnType;
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
private boolean isModelMethod = false;
private int lineNumber;
/**
* Creates 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) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.methodName = methodName;
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
this.modifiers = modifiers;
}
/**
* Visits a single annotation on this method. Will be called once for each annotation
* present (regardless of its RetentionPolicy).
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoClassName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoClass = loadToolingSafeClass(annoClassName);
if (annoClass == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation annotation = createMutableAnnotation(annoClass);
annotations.add(annotation);
return new MutableAnnotationVisitor(annotation);
}
/**
* 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 Factory} method or not and if so adds it to the enclosing
* {@link #configClass}.
*/
@Override
public void visitEnd() {
for (Annotation anno : annotations) {
if (anno.annotationType().getAnnotation(Factory.class) != null) {
isModelMethod = true;
break;
}
}
if (!isModelMethod)
return;
Annotation[] annoArray = annotations.toArray(new Annotation[] {});
ModelMethod method = new ModelMethod(methodName, modifiers, returnType, annoArray);
method.setLineNumber(lineNumber);
configClass.addMethod(method);
}
/**
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
* that type is an interface.
*/
private static ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) {
final ModelClass returnType = new ModelClass(getReturnTypeFromMethodDescriptor(methodDescriptor));
// detect whether the return type is an interface
newClassReader(convertClassNameToResourcePath(returnType.getName())).accept(
new ClassAdapter(AsmUtils.EMPTY_VISITOR) {
@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;
}
}
......@@ -37,198 +37,196 @@ import org.springframework.util.ClassUtils;
/**
* Visits a {@link Configuration} class, populating a {@link ConfigurationClass} instance with
* information gleaned along the way.
* Visits a {@link Configuration} class, populating a {@link ConfigurationClass} instance
* with information gleaned along the way.
*
* @author Chris Beams
*/
class ConfigurationClassVisitor extends ClassAdapter {
private static final Log log = LogFactory.getLog(ConfigurationClassVisitor.class);
private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName());
private final ConfigurationClass configClass;
private final ConfigurationModel model;
private final HashMap<String, ConfigurationClass> innerClasses = new HashMap<String, ConfigurationClass>();
private boolean processInnerClasses = true;
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.model = model;
}
public void setProcessInnerClasses(boolean processInnerClasses) {
this.processInnerClasses = processInnerClasses;
}
@Override
public void visitSource(String sourceFile, String debug) {
String resourcePath =
convertClassNameToResourcePath(configClass.getName())
.substring(0, configClass.getName().lastIndexOf('.')+1)
.concat(sourceFile);
configClass.setSource(resourcePath);
}
@Override
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3,
String superTypeDesc, String[] arg5) {
visitSuperType(superTypeDesc);
configClass.setName(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);
ClassReader reader = AsmUtils.newClassReader(superTypeDesc);
reader.accept(visitor, false);
}
/**
* Visits a class level annotation on a {@link Configuration @Configuration} class.
* Accounts for all possible class-level annotations that are respected by JavaConfig
* including AspectJ's {@code @Aspect} annotation.
* <p>
* Upon encountering such an annotation, update the {@link #configClass} model object
* appropriately, and then return an {@link AnnotationVisitor} implementation that can
* populate the annotation appropriately with data.
*
* @see MutableAnnotation
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
if (Configuration.class.getName().equals(annoTypeName)) {
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class);
configClass.setMetadata(mutableConfiguration);
return new MutableAnnotationVisitor(mutableConfiguration);
}
// TODO: re-enable for @Import support
// if (Import.class.getName().equals(annoTypeName)) {
// ImportStack importStack = ImportStackHolder.getImportStack();
//
// if(importStack.contains(configClass))
// throw new CircularImportException(configClass, importStack);
//
// importStack.push(configClass);
//
// return new ImportAnnotationVisitor(model);
// }
// -------------------------------------
// Detect @Plugin annotations
// -------------------------------------
PluginAnnotationDetectingClassVisitor classVisitor = new PluginAnnotationDetectingClassVisitor();
String className = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
String resourcePath = ClassUtils.convertClassNameToResourcePath(className);
ClassReader reader = AsmUtils.newClassReader(resourcePath);
reader.accept(classVisitor, false);
if(!classVisitor.hasPluginAnnotation())
return super.visitAnnotation(annoTypeDesc, visible);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if(annoType == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation pluginAnno = createMutableAnnotation(annoType);
configClass.addPluginAnnotation(pluginAnno);
return new MutableAnnotationVisitor(pluginAnno);
}
private static class PluginAnnotationDetectingClassVisitor extends ClassAdapter {
private boolean hasPluginAnnotation = false;
private final Extension pluginAnnotation = createMutableAnnotation(Extension.class);
public PluginAnnotationDetectingClassVisitor() {
super(AsmUtils.EMPTY_VISITOR);
}
@Override
public AnnotationVisitor visitAnnotation(String typeDesc, boolean arg1) {
if(Extension.class.getName().equals(AsmUtils.convertTypeDescriptorToClassName(typeDesc))) {
hasPluginAnnotation = true;
return new MutableAnnotationVisitor(pluginAnnotation);
}
return super.visitAnnotation(typeDesc, arg1);
}
public boolean hasPluginAnnotation() {
return hasPluginAnnotation;
}
public Extension getPluginAnnotation() {
return pluginAnnotation;
}
}
/**
* Delegates all {@link Configuration @Configuration} class method parsing to
* {@link ConfigurationClassMethodVisitor}.
*/
@Override
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor,
String arg3, String[] arg4) {
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers);
}
/**
* Implementation deals with inner classes here even though it would have
* been more intuitive to deal with outer classes. Due to limitations in ASM
* (resulting from limitations in the VM spec) we cannot directly look for outer classes
* in all cases, so instead build up a model of {@link #innerClasses} and process
* declaring class logic in a kind of inverted manner.
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if(processInnerClasses == false)
return;
String innerClassName = convertResourcePathToClassName(name);
String configClassName = configClass.getName();
// if the innerClassName is equal to configClassName, we just
// ran into the outermost inner class look up the outer class
// associated with this
if(innerClassName.equals(configClassName)) {
if(innerClasses.containsKey(outerName)) {
configClass.setDeclaringClass(innerClasses.get(outerName));
}
return;
}
ConfigurationClass innerConfigClass = new ConfigurationClass();
ConfigurationClassVisitor ccVisitor =
new ConfigurationClassVisitor(innerConfigClass, new ConfigurationModel());
ccVisitor.setProcessInnerClasses(false);
ClassReader reader = AsmUtils.newClassReader(name);
reader.accept(ccVisitor, false);
if(innerClasses.containsKey(outerName))
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list
if(innerConfigClass.getMetadata() != null)
innerClasses.put(name, innerConfigClass);
}
private static final Log log = LogFactory.getLog(ConfigurationClassVisitor.class);
private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName());
private final ConfigurationClass configClass;
private final ConfigurationModel model;
private final HashMap<String, ConfigurationClass> innerClasses = new HashMap<String, ConfigurationClass>();
private boolean processInnerClasses = true;
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.model = model;
}
public void setProcessInnerClasses(boolean processInnerClasses) {
this.processInnerClasses = processInnerClasses;
}
@Override
public void visitSource(String sourceFile, String debug) {
String resourcePath = convertClassNameToResourcePath(configClass.getName()).substring(0,
configClass.getName().lastIndexOf('.') + 1).concat(sourceFile);
configClass.setSource(resourcePath);
}
@Override
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3,
String superTypeDesc, String[] arg5) {
visitSuperType(superTypeDesc);
configClass.setName(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);
ClassReader reader = AsmUtils.newClassReader(superTypeDesc);
reader.accept(visitor, false);
}
/**
* Visits a class level annotation on a {@link Configuration @Configuration} class.
* Accounts for all possible class-level annotations that are respected by JavaConfig
* including AspectJ's {@code @Aspect} annotation.
* <p>
* Upon encountering such an annotation, update the {@link #configClass} model object
* appropriately, and then return an {@link AnnotationVisitor} implementation that can
* populate the annotation appropriately with data.
*
* @see MutableAnnotation
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
if (Configuration.class.getName().equals(annoTypeName)) {
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class);
configClass.setMetadata(mutableConfiguration);
return new MutableAnnotationVisitor(mutableConfiguration);
}
// TODO: re-enable for @Import support
// if (Import.class.getName().equals(annoTypeName)) {
// ImportStack importStack = ImportStackHolder.getImportStack();
//
// if(importStack.contains(configClass))
// throw new CircularImportException(configClass, importStack);
//
// importStack.push(configClass);
//
// return new ImportAnnotationVisitor(model);
// }
// -------------------------------------
// Detect @Plugin annotations
// -------------------------------------
PluginAnnotationDetectingClassVisitor classVisitor = new PluginAnnotationDetectingClassVisitor();
String className = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
String resourcePath = ClassUtils.convertClassNameToResourcePath(className);
ClassReader reader = AsmUtils.newClassReader(resourcePath);
reader.accept(classVisitor, false);
if (!classVisitor.hasPluginAnnotation())
return super.visitAnnotation(annoTypeDesc, visible);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if (annoType == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation pluginAnno = createMutableAnnotation(annoType);
configClass.addPluginAnnotation(pluginAnno);
return new MutableAnnotationVisitor(pluginAnno);
}
private static class PluginAnnotationDetectingClassVisitor extends ClassAdapter {
private boolean hasPluginAnnotation = false;
private final Extension pluginAnnotation = createMutableAnnotation(Extension.class);
public PluginAnnotationDetectingClassVisitor() {
super(AsmUtils.EMPTY_VISITOR);
}
@Override
public AnnotationVisitor visitAnnotation(String typeDesc, boolean arg1) {
if (Extension.class.getName().equals(AsmUtils.convertTypeDescriptorToClassName(typeDesc))) {
hasPluginAnnotation = true;
return new MutableAnnotationVisitor(pluginAnnotation);
}
return super.visitAnnotation(typeDesc, arg1);
}
public boolean hasPluginAnnotation() {
return hasPluginAnnotation;
}
public Extension getPluginAnnotation() {
return pluginAnnotation;
}
}
/**
* Delegates all {@link Configuration @Configuration} class method parsing to
* {@link ConfigurationClassMethodVisitor}.
*/
@Override
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3,
String[] arg4) {
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers);
}
/**
* Implementation deals with inner classes here even though it would have been more
* intuitive to deal with outer classes. Due to limitations in ASM (resulting from
* limitations in the VM spec) we cannot directly look for outer classes in all cases,
* so instead build up a model of {@link #innerClasses} and process declaring class
* logic in a kind of inverted manner.
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (processInnerClasses == false)
return;
String innerClassName = convertResourcePathToClassName(name);
String configClassName = configClass.getName();
// if the innerClassName is equal to configClassName, we just
// ran into the outermost inner class look up the outer class
// associated with this
if (innerClassName.equals(configClassName)) {
if (innerClasses.containsKey(outerName)) {
configClass.setDeclaringClass(innerClasses.get(outerName));
}
return;
}
ConfigurationClass innerConfigClass = new ConfigurationClass();
ConfigurationClassVisitor ccVisitor = new ConfigurationClassVisitor(innerConfigClass,
new ConfigurationModel());
ccVisitor.setProcessInnerClasses(false);
ClassReader reader = AsmUtils.newClassReader(name);
reader.accept(ccVisitor, false);
if (innerClasses.containsKey(outerName))
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list
if (innerConfigClass.getMetadata() != null)
innerClasses.put(name, innerConfigClass);
}
}
......@@ -16,16 +16,15 @@
package org.springframework.config.java.internal.parsing;
/**
* Note: the visibility of this interface would be reduced to package-private
* save for an obscure restriction of JDK dynamic proxies.
* {@link MutableAnnotationUtils#createMutableAnnotation(Class)} creates a proxy
* based on two interfaces: this one, and whatever annotation is currently being
* parsed. The restriction is that both interfaces may not be package-private if
* they are in separate packages. In order to avoid unnecessarily restricting
* the visibility options for user-defined annotations, this interface becomes
* public. Because it is in the internal.* package, it won't pollute the public
* API, but developers should take caution not to use this annotation outside
* the internal.parsing package.
* Note: the visibility of this interface would be reduced to package-private save for an
* obscure restriction of JDK dynamic proxies.
* {@link MutableAnnotationUtils#createMutableAnnotation(Class)} creates a proxy based on
* two interfaces: this one, and whatever annotation is currently being parsed. The
* restriction is that both interfaces may not be package-private if they are in separate
* packages. In order to avoid unnecessarily restricting the visibility options for
* user-defined annotations, this interface becomes public. Because it is in the internal.*
* package, it won't pollute the public API, but developers should take caution not to use
* this annotation outside the internal.parsing package.
*
* @author Chris Beams
*/
......
......@@ -29,43 +29,43 @@ import org.objectweb.asm.AnnotationVisitor;
/** TODO: JAVADOC */
class MutableAnnotationArrayVisitor extends AnnotationAdapter {
private static final Log log = LogFactory.getLog(MutableAnnotationArrayVisitor.class);
private final ArrayList<Object> values = new ArrayList<Object>();
private final MutableAnnotation mutableAnno;
private final String attribName;
public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName) {
super(AsmUtils.EMPTY_VISITOR);
private static final Log log = LogFactory.getLog(MutableAnnotationArrayVisitor.class);
this.mutableAnno = mutableAnno;
this.attribName = attribName;
}
private final ArrayList<Object> values = new ArrayList<Object>();
private final MutableAnnotation mutableAnno;
private final String attribName;
@Override
public void visit(String na, Object value) {
values.add(value);
}
public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName) {
super(AsmUtils.EMPTY_VISITOR);
@Override
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if(annoType == null)
return super.visitAnnotation(na, annoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
values.add(anno);
return new MutableAnnotationVisitor(anno);
}
this.mutableAnno = mutableAnno;
this.attribName = attribName;
}
@Override
public void visitEnd() {
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
Object[] array = (Object[])Array.newInstance(arrayType.getComponentType(), 0);
mutableAnno.setAttributeValue(attribName, values.toArray(array));
}
@Override
public void visit(String na, Object value) {
values.add(value);
}
@Override
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if (annoType == null)
return super.visitAnnotation(na, annoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
values.add(anno);
return new MutableAnnotationVisitor(anno);
}
@Override
public void visitEnd() {
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0);
mutableAnno.setAttributeValue(attribName, values.toArray(array));
}
}
......@@ -33,170 +33,173 @@ import org.springframework.util.StringUtils;
/** TODO: JAVADOC */
final class MutableAnnotationInvocationHandler implements InvocationHandler {
private final Class<? extends Annotation> annoType;
private final HashMap<String, Object> attributes = new HashMap<String, Object>();
private final HashMap<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(), 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 RuntimeException(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() {
ArrayList<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) {
Method method = null;
try {
method = annotationType.getDeclaredMethod(attributeName);
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
return method.getReturnType();
}
private final Class<? extends Annotation> annoType;
private final HashMap<String, Object> attributes = new HashMap<String, Object>();
private final HashMap<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(), 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 RuntimeException(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() {
ArrayList<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) {
Method method = null;
try {
method = annotationType.getDeclaredMethod(attributeName);
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
return method.getReturnType();
}
}
......@@ -19,6 +19,6 @@ package test.beans;
/** TODO: JAVADOC */
public interface INestedTestBean {
String getCompany();
String getCompany();
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册