blob: 88e226539117d344d811eeb0ac33286f78fa2c20 [file] [log] [blame]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.commons.digester.annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.digester.RuleSet;
import org.apache.commons.digester.annotations.handlers.DefaultLoaderHandler;
import org.apache.commons.digester.annotations.internal.RuleSetCache;
import org.apache.commons.digester.annotations.reflect.MethodArgument;
import org.apache.commons.digester.annotations.spi.AnnotationRuleProviderFactory;
import org.apache.commons.digester.annotations.spi.DigesterLoaderHandlerFactory;
import org.apache.commons.digester.annotations.utils.AnnotationUtils;
/**
* This class manages the creation of Digester instances analyzing target classes
* annotated with digester annotations.
*
* @since 2.1
*/
public final class DigesterLoader {
/**
* In-memory LRU cache that stores already analyzed classes and relative
* {@link RuleSet}.
*/
private final RuleSetCache cachedRuleSet = new RuleSetCache();
private final AnnotationRuleProviderFactory annotationRuleProviderFactory;
private final DigesterLoaderHandlerFactory digesterLoaderHandlerFactory;
/**
* Creates a new {@link DigesterLoader} instance.
*
* @param annotationRuleProviderFactory
* @param digesterLoaderHandlerFactory
*/
protected DigesterLoader(AnnotationRuleProviderFactory annotationRuleProviderFactory,
DigesterLoaderHandlerFactory digesterLoaderHandlerFactory) {
this.annotationRuleProviderFactory = annotationRuleProviderFactory;
this.digesterLoaderHandlerFactory = digesterLoaderHandlerFactory;
}
protected AnnotationRuleProviderFactory getAnnotationRuleProviderFactory() {
return annotationRuleProviderFactory;
}
protected DigesterLoaderHandlerFactory getDigesterLoaderHandlerFactory() {
return digesterLoaderHandlerFactory;
}
/**
* Creates a new digester which rules are defined by analyzing the digester
* annotations in the target class.
*
* @param target the class has to be analyzed.
* @return a new Digester instance.
*/
public Digester createDigester(final Class<?> target) {
Digester digester = new Digester();
digester.setClassLoader(target.getClassLoader());
addRules(target, digester);
return digester;
}
/**
* Add rules to an already created Digester instance, analyzing the digester
* annotations in the target class.
*
* @param target the class has to be analyzed.
* @param digester the Digester instance reference.
*/
public void addRules(final Class<?> target, final Digester digester) {
RuleSet ruleSet = getRuleSet(target);
ruleSet.addRuleInstances(digester);
}
/**
* Builds a new {@link RuleSet} analyzing the digester annotations in the
* target class.
*
* It avoids iterate the annotations analysis for already analyzed classes,
* using an in-memory LRU cache.
*
* @param target the class has to be analyzed.
* @return a new {@link RuleSet}.
*/
public RuleSet getRuleSet(final Class<?> target) {
if (this.cachedRuleSet.containsKey(target)) {
return this.cachedRuleSet.get(target);
}
FromAnnotationsRuleSet ruleSet = new FromAnnotationsRuleSet(this);
addRulesTo(target, ruleSet);
this.cachedRuleSet.put(target, ruleSet);
return ruleSet;
}
/**
* Analyzes the target class and adds the {@link AnnotationRuleProvider}s to
* the existing {@link FromAnnotationsRuleSet}.
*
* @param target the class has to be analyzed.
* @param ruleSet the RuleSet where adding the providers.
*/
public void addRulesTo(final Class<?> target, FromAnnotationsRuleSet ruleSet) {
if (target == Object.class
|| target.isInterface()
|| ruleSet.mapsClass(target)) {
return;
}
if (this.cachedRuleSet.containsKey(target)) {
ruleSet.addRulesProviderFrom(this.cachedRuleSet.get(target));
ruleSet.addMappedClass(target);
return;
}
// current analyzed class
handle(target, ruleSet);
// class fields
for (Field field : target.getDeclaredFields()) {
handle(field, ruleSet);
}
// class methods
for (Method method : target.getDeclaredMethods()) {
handle(method, ruleSet);
// method args
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
handle(new MethodArgument(i, parameterTypes[i], parameterAnnotations[i]), ruleSet);
}
}
ruleSet.addMappedClass(target);
addRulesTo(target.getSuperclass(), ruleSet);
}
/**
* Executes an analysis for each annotation present in the element.
*
* @param element the current element under analysis.
* @param ruleSet the ruleSet where add providers.
*/
private void handle(AnnotatedElement element, FromAnnotationsRuleSet ruleSet) {
for (Annotation annotation : element.getAnnotations()) {
handle(annotation, element, ruleSet);
}
}
/**
* Handles the current visited element and related annotation, invoking the
* right handler putting the rule provider in the rule set.
*
* @param annotation the current visited annotation.
* @param element the current visited element.
*/
@SuppressWarnings("unchecked")
private <A extends Annotation, E extends AnnotatedElement, R extends Rule> void handle(A annotation,
E element,
FromAnnotationsRuleSet ruleSet) {
Class<?> annotationType = annotation.annotationType();
// check if it is one of the @*.List annotation
if (annotationType.isAnnotationPresent(DigesterRuleList.class)) {
Annotation[] annotations = AnnotationUtils.getAnnotationsArrayValue(annotation);
if (annotations != null && annotations.length > 0) {
// if it is an annotations array, process them
for (Annotation ptr : annotations) {
handle(ptr, element, ruleSet);
}
}
} else if (annotationType.isAnnotationPresent(DigesterRule.class)) {
DigesterRule digesterRule = annotationType.getAnnotation(DigesterRule.class);
if (DefaultLoaderHandler.class == digesterRule.handledBy()) {
Class<? extends AnnotationRuleProvider<A, E, R>> providerType =
(Class<? extends AnnotationRuleProvider<A, E, R>>) digesterRule.providedBy();
ruleSet.addRuleProvider(AnnotationUtils.getAnnotationPattern(annotation),
providerType,
annotation,
element);
} else {
Class<? extends DigesterLoaderHandler<Annotation, AnnotatedElement>> handlerType =
(Class<? extends DigesterLoaderHandler<Annotation, AnnotatedElement>>) digesterRule.handledBy();
DigesterLoaderHandler<Annotation, AnnotatedElement> handler =
this.digesterLoaderHandlerFactory.newInstance(handlerType);
// run!
handler.handle(annotation, element, ruleSet);
}
}
}
}