+ * Any reflection-related issues are re-thrown as unchecked.
+ */
+ public static 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 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 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 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 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.
+ *
+ * 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.
+ *
+ * Because {@link ClassNotFoundException} is compensated for by returning null, callers must
+ * take care to handle the null case appropriately.
+ *
+ * In cases where the WARN logging statement is not desired, use the {@link #loadClass(String)}
+ * method, which returns null but issues no logging statements.
+ *
+ * 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 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 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 pathToClass. 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 pathToClass
+ *
+ * @throws RuntimeException if pathToClass 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;
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/AbstractMethodInterceptor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/AbstractMethodInterceptor.java
new file mode 100644
index 0000000000..bdf615860e
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/AbstractMethodInterceptor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.annotation;
+
+import java.lang.reflect.Method;
+
+import net.sf.cglib.proxy.MethodInterceptor;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.util.Assert;
+
+
+/**
+ * Base class for all {@link MethodInterceptor} implementations.
+ *
+ * @author Chris Beams
+ */
+abstract class AbstractMethodInterceptor implements BeanFactoryAware, MethodInterceptor {
+ 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;
+ }
+
+ protected String getBeanName(Method method) {
+ return method.getName();
+ }
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Bean.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Bean.java
new file mode 100644
index 0000000000..d7f7b12fc6
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Bean.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.config.java.model.ModelMethod;
+import org.springframework.config.java.model.UsageError;
+
+
+/**
+ * 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.)
+ *
+ *
Contains information similar to that held in Spring's internal BeanDefinition metadata.
+ *
+ *
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.
+ * 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.
+ *
+ *
Bean creation methods may reference other bean creation methods by calling them directly, as
+ * follows. This ensures that references between beans are strongly typed:
+ *
+ * @see Configuration
+ * @see BeanNamingStrategy
+ *
+ * @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})
+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 BeanDefinition.SCOPE_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 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 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));
+ }
+ }
+ }
+
+}
+
+
+
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanDefinitionRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanDefinitionRegistrar.java
new file mode 100644
index 0000000000..40f8402dd0
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanDefinitionRegistrar.java
@@ -0,0 +1,36 @@
+package org.springframework.config.java.annotation;
+
+import java.lang.reflect.Method;
+
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.config.java.model.ModelMethod;
+
+/**
+ * Registers bean definition(s) for a particular method, usually based on its annotation metadata.
+ *
+ *
Constraints
+ * Implementations must have only a default constructor, or explicitly declare
+ * a no-arg constructor.
+ *
+ * @see Factory
+ * @see ModelMethod
+ *
+ * @author Chris Beams
+ */
+// TODO: SJC-242 document FactoryMethodHandler
+// TODO: SJC-242 odd that the api here uses both ModelMethod and java.lang.reflect.Member
+// TODO: SJC-242 document that there must be a no-arg ctor
+public interface BeanDefinitionRegistrar {
+
+ /**
+ * Determines whether this registrar is capable of handling method.
+ */
+ // TODO: rename to supports() in alignment with Validator nomenclature
+ boolean accepts(Method method);
+
+ /**
+ * Registers any bean definitions for method with registry.
+ */
+ void register(ModelMethod method, BeanDefinitionRegistry registry);
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanMethodInterceptor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanMethodInterceptor.java
new file mode 100644
index 0000000000..4ee4be5c61
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanMethodInterceptor.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.annotation;
+
+import static java.lang.String.*;
+
+import java.lang.reflect.Method;
+
+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.
+ *
+ * @see Bean
+ * @see BeanRegistrar
+ *
+ * @author Chris Beams
+ */
+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;
+
+ 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 proxy.invokeSuper(obj, args);
+ }
+
+ /**
+ * Check the beanFactory to see whether the bean named beanName 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.
+ *
+ * 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 beanName already exists in beanFactory
+ */
+ private boolean factoryContainsBean(String beanName) {
+ return beanFactory.containsBean(beanName)
+ && !beanFactory.isCurrentlyInCreation(beanName);
+ }
+
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanRegistrar.java
new file mode 100644
index 0000000000..1b583976b6
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/BeanRegistrar.java
@@ -0,0 +1,201 @@
+package org.springframework.config.java.annotation;
+
+import static java.lang.String.*;
+import static org.springframework.util.StringUtils.*;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.MalformedJavaConfigurationException;
+import org.springframework.config.java.model.ModelMethod;
+import org.springframework.config.java.model.UsageError;
+import org.springframework.core.annotation.AnnotationUtils;
+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 member 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 MalformedJavaConfigurationException(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).
+ */
+@SuppressWarnings("serial")
+// TODO: SJC-242 what to do about JavaConfigBeanDefinition?
+class JavaConfigBeanDefinition extends RootBeanDefinition {
+}
\ No newline at end of file
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Configuration.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Configuration.java
new file mode 100644
index 0000000000..b8e33be842
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Configuration.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Required;
+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.
+ *
+ *
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.
+ *
+ *
Configuration-annotated classes are also candidates for component scanning thanks to the fact
+ * that this annotation is meta-annotated with {@link Component @Component}.
+ *
+ * @author Rod Johnson
+ * @author Chris Beams
+ */
+@Component
+@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 "";
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Factory.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Factory.java
new file mode 100644
index 0000000000..dfe1fbcf70
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Factory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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)
+@Target(ElementType.ANNOTATION_TYPE)
+@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 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 {};
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Validator.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Validator.java
new file mode 100644
index 0000000000..5bc29edd75
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/annotation/Validator.java
@@ -0,0 +1,14 @@
+package org.springframework.config.java.annotation;
+
+import java.util.List;
+
+import org.springframework.config.java.model.UsageError;
+
+/** Marker interface */
+//TODO: SJC-242 document
+//TODO: SJC-242 rename
+public interface Validator {
+ boolean supports(Object object);
+
+ void validate(Object object, List errors);
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/AddAnnotationAdapter.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/AddAnnotationAdapter.java
new file mode 100644
index 0000000000..96ae3bfefe
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/AddAnnotationAdapter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.enhancement;
+
+import net.sf.cglib.asm.Constants;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+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.
+ *
+ * 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;
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 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);
+ }
+
+ /**
+ * 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;
+ }
+ }
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/CglibConfigurationEnhancer.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/CglibConfigurationEnhancer.java
new file mode 100644
index 0000000000..064d930662
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/CglibConfigurationEnhancer.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.enhancement;
+
+import static java.lang.String.*;
+import static org.springframework.config.java.Util.*;
+import static org.springframework.util.Assert.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+
+import net.sf.cglib.core.DefaultGeneratorStrategy;
+import net.sf.cglib.proxy.Callback;
+import net.sf.cglib.proxy.CallbackFilter;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.NoOp;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.config.java.annotation.BeanDefinitionRegistrar;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.config.java.model.ModelMethod;
+
+
+
+/**
+ * Enhances {@link Configuration} classes by generating a CGLIB subclass capable of
+ * interacting with the Spring container to respect bean semantics.
+ *
+ * @see #enhance(String)
+ *
+ * @author Chris Beams
+ */
+public class CglibConfigurationEnhancer implements ConfigurationEnhancer {
+
+ private static final Log log = LogFactory.getLog(CglibConfigurationEnhancer.class);
+
+ private final ArrayList> callbackTypes =
+ new ArrayList>();
+
+ private final LinkedHashSet handlers =
+ new LinkedHashSet();
+
+ private final ArrayList callbackInstances =
+ new ArrayList();
+
+ private final CallbackFilter callbackFilter =
+ new CallbackFilter() {
+ public int accept(Method candidateMethod) {
+ Iterator iter = handlers.iterator();
+ for(int i=0; iter.hasNext(); i++)
+ if(iter.next().accepts(candidateMethod))
+ return i;
+
+ throw new IllegalStateException(format("No registered handler is capable of " +
+ "handling method [%s]. Perhaps you forgot to register a catch-all registrar?",
+ candidateMethod.getName()));
+ }
+ };
+
+
+ /**
+ * Creates a new {@link CglibConfigurationEnhancer} instance.
+ */
+ public CglibConfigurationEnhancer(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
+ notNull(beanFactory, "beanFactory must be non-null");
+ notNull(model, "model must be non-null");
+
+ populateHandlersAndCallbacks(beanFactory, model);
+ }
+
+
+ /**
+ * Reads the contents of {@code model} in order to populate {@link #handlers},
+ * {@link #callbackInstances} and {@link #callbackTypes} appropriately.
+ *
+ * @see #callbackFilter
+ */
+ private void populateHandlersAndCallbacks(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
+
+ for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
+ for (ModelMethod method : configClass.getMethods()) {
+ handlers.add(method.getRegistrar());
+
+ Callback callback = method.getCallback();
+
+ if(callback instanceof BeanFactoryAware)
+ ((BeanFactoryAware)callback).setBeanFactory(beanFactory);
+
+ callbackInstances.add(callback);
+ }
+ }
+
+ handlers.add(new InitializingBeanRegistrar());
+ callbackInstances.add(new InitializingBeanCallback(beanFactory));
+
+ // register a 'catch-all' registrar
+ handlers.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.
+ */
+ 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.setInterfaces(new Class>[]{InitializingBean.class});
+ 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();
+
+ // see #registerThreadLocalCleanupBeanDefinition
+ Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
+ //Enhancer.registerStaticCallbacks(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;
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/ConfigurationEnhancer.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/ConfigurationEnhancer.java
new file mode 100644
index 0000000000..10c2184d47
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/ConfigurationEnhancer.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.enhancement;
+
+
+/** TODO: JAVADOC */
+public interface ConfigurationEnhancer {
+
+ String enhance(String configClassName);
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanCallback.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanCallback.java
new file mode 100644
index 0000000000..dc1d3fb793
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanCallback.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.enhancement;
+
+import java.lang.reflect.Method;
+
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+
+// TODO: should not be necessary to make configurations implement InitializingBean anymore once @Value is in the picture
+class InitializingBeanCallback implements MethodInterceptor {
+
+ private final DefaultListableBeanFactory beanFactory;
+
+ public InitializingBeanCallback(DefaultListableBeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+
+ // TODO: SJC-242 ExternalValueInjector - revisit this line (inline method?)
+ // TODO: should be handled by @Value
+ //new ExternalValueInjector(beanFactory).injectExternalValues(obj);
+
+ // only call the superclass afterPropertiesSet method if it is actually implemented
+ if(!InitializingBean.class.equals(method.getDeclaringClass()))
+ return proxy.invokeSuper(obj, args);
+
+ return Void.TYPE;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanRegistrar.java
new file mode 100644
index 0000000000..fe7f2a000f
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/InitializingBeanRegistrar.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.enhancement;
+
+import java.lang.reflect.Method;
+
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.config.java.annotation.BeanDefinitionRegistrar;
+import org.springframework.config.java.model.ModelMethod;
+
+class InitializingBeanRegistrar implements BeanDefinitionRegistrar {
+ public boolean accepts(Method method) {
+ return method.getName().equals("afterPropertiesSet")
+ && method.getReturnType().equals(void.class);
+ }
+
+ public void register(ModelMethod method, BeanDefinitionRegistry registry) {
+ // no-op
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/AsmJavaConfigBeanDefinitionReader.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/AsmJavaConfigBeanDefinitionReader.java
new file mode 100644
index 0000000000..2fb2e8d3ac
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/AsmJavaConfigBeanDefinitionReader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.factory.support;
+
+import static org.springframework.config.java.Util.*;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.objectweb.asm.ClassReader;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.internal.parsing.ConfigurationParser;
+import org.springframework.config.java.internal.parsing.asm.AsmConfigurationParser;
+import org.springframework.config.java.internal.parsing.asm.AsmUtils;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.config.java.model.MalformedJavaConfigurationException;
+import org.springframework.config.java.model.UsageError;
+import org.springframework.core.io.ClassPathResource;
+
+
+/**
+ * Uses ASM to parse {@link Configuration @Configuration} classes. Fashioned after the
+ * {@link BeanDefinitionReader} hierarchy, but does not extend or implement any of its
+ * types because differences were significant enough to merit the departure.
+ *
+ * @see AsmConfigurationParser
+ *
+ * @author Chris Beams
+ */
+public class AsmJavaConfigBeanDefinitionReader {
+
+ private final ConfigurationModelBeanDefinitionReader modelBeanDefinitionReader;
+
+ /**
+ * Creates a new {@link AsmJavaConfigBeanDefinitionReader}.
+ *
+ * @param registry {@link BeanDefinitionRegistry} into which new bean definitions will be
+ * registered as they are read from Configuration classes.
+ */
+ public AsmJavaConfigBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
+ this.modelBeanDefinitionReader = new ConfigurationModelBeanDefinitionReader(beanFactory);
+ }
+
+ /**
+ * Parses each {@link Configuration} class specified by configClassResources and registers
+ * individual bean definitions from those Configuration classes into the BeanDefinitionRegistry
+ * supplied during construction.
+ */
+ public int loadBeanDefinitions(ConfigurationModel model, Map configClassResources) throws BeanDefinitionStoreException {
+ ConfigurationParser parser = new AsmConfigurationParser(model);
+
+ for (String id : configClassResources.keySet()) {
+ String resourcePath = configClassResources.get(id).getPath();
+ ClassReader configClassReader = AsmUtils.newClassReader(getClassAsStream(resourcePath));
+ parser.parse(configClassReader, id);
+ }
+
+ ArrayList errors = new ArrayList();
+ model.validate(errors);
+ if (errors.size() > 0)
+ throw new MalformedJavaConfigurationException(errors.toArray(new UsageError[] { }));
+
+ return modelBeanDefinitionReader.loadBeanDefinitions(model);
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/ConfigurationModelBeanDefinitionReader.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/ConfigurationModelBeanDefinitionReader.java
new file mode 100644
index 0000000000..dd181113cf
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/factory/support/ConfigurationModelBeanDefinitionReader.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.factory.support;
+
+
+import static java.lang.String.*;
+import static org.springframework.config.java.Util.*;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.annotation.Factory;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.config.java.model.ModelMethod;
+import org.springframework.config.java.plugin.Extension;
+import org.springframework.config.java.plugin.ExtensionAnnotationBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+
+
+/**
+ * Reads a given fully-populated {@link ConfigurationModel}, registering bean definitions with
+ * the given {@link BeanDefinitionRegistry} based on its contents.
+ *
+ * This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does not
+ * implement/extend any of its artifacts as {@link ConfigurationModel} is not a
+ * {@link Resource}.
+ *
+ * @author Chris Beams
+ */
+// TODO: Unit test
+public class ConfigurationModelBeanDefinitionReader {
+
+ private static final Log log = LogFactory.getLog(ConfigurationModelBeanDefinitionReader.class);
+
+ private final DefaultListableBeanFactory beanFactory;
+
+
+ /**
+ * Creates a new {@link ConfigurationModelBeanDefinitionReader} instance.
+ */
+ public ConfigurationModelBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Reads {@code model}, registering bean definitions with {@link #beanFactory}
+ * based on its contents.
+ *
+ * @return number of bean definitions generated
+ */
+ public int loadBeanDefinitions(ConfigurationModel model) {
+ int initialBeanDefCount = beanFactory.getBeanDefinitionCount();
+
+ for (ConfigurationClass configClass : model.getAllConfigurationClasses())
+ loadBeanDefinitionsForConfigurationClass(configClass);
+
+ return beanFactory.getBeanDefinitionCount() - initialBeanDefCount;
+ }
+
+ /**
+ * Reads a particular {@link ConfigurationClass}, registering bean definitions
+ * for the class itself, all its {@link Factory} methods and all its {@link Extension}
+ * annotations.
+ */
+ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
+ doLoadBeanDefinitionForConfigurationClass(configClass);
+
+ for (ModelMethod method : configClass.getMethods())
+ loadBeanDefinitionsForModelMethod(method);
+
+ Annotation[] pluginAnnotations = configClass.getPluginAnnotations();
+ Arrays.sort(pluginAnnotations, new PluginComparator());
+ for (Annotation annotation : pluginAnnotations)
+ loadBeanDefinitionsForExtensionAnnotation(annotation);
+ }
+
+ /**
+ * Registers the {@link Configuration} class itself as a bean definition.
+ */
+ private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
+ Configuration metadata = configClass.getMetadata();
+
+ if (metadata.checkRequired() == true) {
+ RootBeanDefinition requiredAnnotationPostProcessor = new RootBeanDefinition();
+ Class> beanClass = RequiredAnnotationBeanPostProcessor.class;
+ String beanName = beanClass.getName() + "#0";
+ requiredAnnotationPostProcessor.setBeanClass(beanClass);
+ requiredAnnotationPostProcessor.setResourceDescription("ensures @Required methods have been invoked");
+ beanFactory.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
+ }
+
+ GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
+ configBeanDef.setBeanClassName(configClass.getName());
+
+ String configBeanName = configClass.getBeanName();
+
+ // consider the case where it's already been defined (probably in XML)
+ // and potentially has PropertyValues and ConstructorArgs)
+ if (beanFactory.containsBeanDefinition(configBeanName)) {
+ if (log.isInfoEnabled())
+ log.info(format("Copying property and constructor arg values from existing bean definition for "
+ + "@Configuration class %s to new bean definition", configBeanName));
+ AbstractBeanDefinition existing = (AbstractBeanDefinition)beanFactory.getBeanDefinition(configBeanName);
+ configBeanDef.setPropertyValues(existing.getPropertyValues());
+ configBeanDef.setConstructorArgumentValues(existing.getConstructorArgumentValues());
+ configBeanDef.setResource(existing.getResource());
+ }
+
+ if (log.isInfoEnabled())
+ log.info(format("Registering bean definition for @Configuration class %s", configBeanName));
+
+ beanFactory.registerBeanDefinition(configBeanName, configBeanDef);
+ }
+
+
+ /**
+ * Reads a particular {@link ModelMethod}, registering bean definitions
+ * with {@link #beanFactory} based on its contents.
+ *
+ * @see Factory
+ */
+ private void loadBeanDefinitionsForModelMethod(ModelMethod method) {
+ method.getRegistrar().register(method, beanFactory);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void loadBeanDefinitionsForExtensionAnnotation(Annotation anno) {
+ //ExtensionAnnotationUtils.getRegistrarFor(anno).registerBeanDefinitionsWith(beanFactory);
+ // there is a fixed assumption that in order for this annotation to have
+ // been registered in the first place, it must be meta-annotated with @Plugin
+ // assert this as an invariant now
+ Class> annoClass = anno.getClass();
+ Extension extensionAnno = AnnotationUtils.findAnnotation(annoClass, Extension.class);
+ Assert.isTrue(extensionAnno != null,
+ format("%s annotation is not annotated as a @%s",
+ annoClass, Extension.class.getSimpleName()));
+
+ Class extends ExtensionAnnotationBeanDefinitionRegistrar> extHandlerClass = extensionAnno.handler();
+
+ ExtensionAnnotationBeanDefinitionRegistrar extHandler = getInstance(extHandlerClass);
+ extHandler.handle(anno, beanFactory);
+ }
+
+ private static class PluginComparator implements Comparator {
+ public int compare(Annotation a1, Annotation a2) {
+ Integer i1 = getOrder(a1);
+ Integer i2 = getOrder(a2);
+ return i1.compareTo(i2);
+ }
+
+ private Integer getOrder(Annotation a) {
+ Extension plugin = a.annotationType().getAnnotation(Extension.class);
+ if(plugin == null)
+ throw new IllegalArgumentException(
+ "annotation was not annotated with @Plugin: " + a.annotationType());
+ return plugin.order();
+ }
+ }
+
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/ConfigurationParser.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/ConfigurationParser.java
new file mode 100644
index 0000000000..fe7523325e
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/ConfigurationParser.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing;
+
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.model.ConfigurationModel;
+
+
+/**
+ * Parses a {@link Configuration} class definition, usually into a {@link ConfigurationModel}.
+ *
+ * This interface aids in separating the process of reading a class file (via reflection, ASM, etc)
+ * from the process of registering bean definitions based on the content of that class.
+ *
+ * @see org.springframework.config.java.internal.parsing.asm.AsmConfigurationParser
+ * @see org.springframework.config.java.model.ConfigurationModel
+ * @see org.springframework.config.java.internal.factory.support.ConfigurationModelBeanDefinitionReader
+ *
+ * @author Chris Beams
+ */
+public interface ConfigurationParser {
+
+ /**
+ * Parse the Configuration object represented by configurationSource.
+ *
+ * @param configurationSource representation of a Configuration class, may be java.lang.Class,
+ * ASM representation or otherwise
+ *
+ * @see org.springframework.config.java.annotation.Configuration
+ */
+ void parse(Object configurationSource);
+
+ /**
+ * Optionally propagate a custom name for this configurationSource. Usually this id
+ * corresponds to the name of a Configuration bean as declared in a beans XML.
+ *
+ * @param configurationSource representation of a Configuration class, may be java.lang.Class,
+ * ASM representation or otherwise
+ * @param configurationId name of this configuration class, probably corresponding to a
+ * bean id
+ *
+ * @see org.springframework.config.java.annotation.Configuration
+ * @see org.springframework.config.java.process.ConfigurationPostProcessor
+ */
+ void parse(Object configurationSource, String configurationId);
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AnnotationAdapter.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AnnotationAdapter.java
new file mode 100644
index 0000000000..94ec8d30b2
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AnnotationAdapter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+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.
+ *
+ * @author Chris Beams
+ */
+class AnnotationAdapter implements AnnotationVisitor {
+
+ private AnnotationVisitor delegate;
+
+ /**
+ * Creates a new AnnotationAdapter instance that will delegate
+ * all its calls to delegate.
+ *
+ * @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 AnnotationVisitor visitAnnotation(String arg0, String arg1) {
+ return delegate.visitAnnotation(arg0, arg1);
+ }
+
+ 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 visitEnd() {
+ delegate.visitEnd();
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmConfigurationParser.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmConfigurationParser.java
new file mode 100644
index 0000000000..f6f76cad1e
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmConfigurationParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+
+
+import org.objectweb.asm.ClassReader;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.internal.parsing.ConfigurationParser;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.util.Assert;
+
+
+/**
+ * ASM-based implementation of {@link ConfigurationParser}. Avoids reflection and eager classloading
+ * in order to interoperate effectively with tooling (Spring IDE).
+ *
+ * @see org.springframework.config.java.model.AsmConfigurationParserTests
+
+ * @author Chris Beams
+ */
+public class AsmConfigurationParser implements ConfigurationParser {
+
+ /**
+ * Model to be populated during calls to {@link #parse(Object)}
+ */
+ private final ConfigurationModel model;
+
+ /**
+ * Creates a new parser instance that will be used to populate model.
+ *
+ * @param model model to be populated by each successive call to {@link #parse(Object)}
+ */
+ public AsmConfigurationParser(ConfigurationModel model) {
+ this.model = model;
+ }
+
+ /**
+ * Convenience implementation, delegates to {@link #parse(Object, String)},
+ * passing in {@code null} for the configurationId.
+ *
+ * @param configurationSource must be an ASM {@link ClassReader}
+ */
+ public void parse(Object configurationSource) {
+ parse(configurationSource, null);
+ }
+
+ /**
+ * Parse the {@link Configuration @Configuration} class encapsulated by
+ * configurationSource.
+ *
+ * @param configurationSource must be an ASM {@link ClassReader}
+ * @param configurationId may be null, but if populated represents the bean id
+ * (assumes that this configuration class was configured via XML)
+ */
+ public void parse(Object configurationSource, String configurationId) {
+ Assert.isInstanceOf(ClassReader.class, configurationSource,
+ "configurationSource must be an ASM ClassReader");
+
+ ConfigurationClass configClass = new ConfigurationClass();
+ configClass.setBeanName(configurationId);
+
+ parse((ClassReader) configurationSource, configClass);
+ }
+
+ /**
+ * Kicks off visiting configClass with {@link ConfigurationClassVisitor}
+ */
+ private void parse(ClassReader reader, ConfigurationClass configClass) {
+ reader.accept(new ConfigurationClassVisitor(configClass, model), false);
+ model.add(configClass);
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmUtils.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmUtils.java
new file mode 100644
index 0000000000..af57d7dc44
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/AsmUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.commons.EmptyVisitor;
+import org.springframework.config.java.Util;
+
+
+/**
+ * Various utility methods commonly used when interacting with ASM.
+ */
+public 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 pathToClass. Appends '.class'
+ * to pathToClass before attempting to load.
+ *
+ * @throws RuntimeException if pathToClass+.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 bytes 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 is, 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);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassMethodVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassMethodVisitor.java
new file mode 100644
index 0000000000..1393661ce2
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassMethodVisitor.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+import static org.springframework.config.java.Util.*;
+import static org.springframework.config.java.internal.parsing.asm.AsmUtils.*;
+import static org.springframework.config.java.internal.parsing.asm.MutableAnnotationUtils.*;
+import static org.springframework.util.ClassUtils.*;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodAdapter;
+import org.objectweb.asm.Opcodes;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.annotation.Factory;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ModelClass;
+import org.springframework.config.java.model.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}.
+ *
+ * @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 annotations = new ArrayList();
+
+ 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 - lineNo 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 methodDescriptor 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;
+ }
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassVisitor.java
new file mode 100644
index 0000000000..a5a5d63ffe
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/ConfigurationClassVisitor.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+import static org.springframework.config.java.Util.*;
+import static org.springframework.config.java.internal.parsing.asm.MutableAnnotationUtils.*;
+import static org.springframework.util.ClassUtils.*;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.springframework.config.java.annotation.Configuration;
+import org.springframework.config.java.model.ConfigurationClass;
+import org.springframework.config.java.model.ConfigurationModel;
+import org.springframework.config.java.plugin.Extension;
+import org.springframework.util.ClassUtils;
+
+
+/**
+ * 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 innerClasses = new HashMap();
+
+ 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.
+ *
+ * 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);
+ }
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotation.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotation.java
new file mode 100644
index 0000000000..2c967aca96
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+
+/**
+ * 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.asm package.
+ *
+ * @author Chris Beams
+ */
+public interface MutableAnnotation {
+ void setAttributeValue(String attribName, Object attribValue);
+ Class> getAttributeType(String attributeName);
+}
diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotationArrayVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotationArrayVisitor.java
new file mode 100644
index 0000000000..c82acb21b1
--- /dev/null
+++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/parsing/asm/MutableAnnotationArrayVisitor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.config.java.internal.parsing.asm;
+
+import static org.springframework.config.java.Util.*;
+import static org.springframework.config.java.internal.parsing.asm.MutableAnnotationUtils.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.objectweb.asm.AnnotationVisitor;
+
+
+/** TODO: JAVADOC */
+class MutableAnnotationArrayVisitor extends AnnotationAdapter {
+
+ private static final Log log = LogFactory.getLog(MutableAnnotationArrayVisitor.class);
+
+ private final ArrayList
+ *
+ * Conforms strictly to the equals() specification for Annotation