001/* $Id: DigesterLoader.java 992084 2010-09-02 19:52:17Z simonetripodi $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.digester.annotations;
019
020import java.lang.annotation.Annotation;
021import java.lang.reflect.AnnotatedElement;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024
025import org.apache.commons.digester.Digester;
026import org.apache.commons.digester.Rule;
027import org.apache.commons.digester.RuleSet;
028import org.apache.commons.digester.annotations.handlers.DefaultLoaderHandler;
029import org.apache.commons.digester.annotations.internal.RuleSetCache;
030import org.apache.commons.digester.annotations.reflect.MethodArgument;
031import org.apache.commons.digester.annotations.spi.AnnotationRuleProviderFactory;
032import org.apache.commons.digester.annotations.spi.DigesterLoaderHandlerFactory;
033import org.apache.commons.digester.annotations.utils.AnnotationUtils;
034
035/**
036 * This class manages the creation of Digester instances analyzing target classes
037 * annotated with digester annotations.
038 *
039 * @since 2.1
040 */
041public final class DigesterLoader {
042
043    /**
044     * In-memory LRU cache that stores already analyzed classes and relative
045     * {@link RuleSet}.
046     */
047    private final RuleSetCache cachedRuleSet = new RuleSetCache();
048
049    private final AnnotationRuleProviderFactory annotationRuleProviderFactory;
050
051    private final DigesterLoaderHandlerFactory digesterLoaderHandlerFactory;
052
053    /**
054     * Creates a new {@link DigesterLoader} instance.
055     *
056     * @param annotationRuleProviderFactory
057     * @param digesterLoaderHandlerFactory
058     */
059    protected DigesterLoader(AnnotationRuleProviderFactory annotationRuleProviderFactory,
060            DigesterLoaderHandlerFactory digesterLoaderHandlerFactory) {
061        this.annotationRuleProviderFactory = annotationRuleProviderFactory;
062        this.digesterLoaderHandlerFactory = digesterLoaderHandlerFactory;
063    }
064
065    protected AnnotationRuleProviderFactory getAnnotationRuleProviderFactory() {
066        return annotationRuleProviderFactory;
067    }
068
069    protected DigesterLoaderHandlerFactory getDigesterLoaderHandlerFactory() {
070        return digesterLoaderHandlerFactory;
071    }
072
073    /**
074     * Creates a new digester which rules are defined by analyzing the digester
075     * annotations in the target class.
076     *
077     * @param target the class has to be analyzed.
078     * @return a new Digester instance.
079     */
080    public Digester createDigester(final Class<?> target) {
081        Digester digester = new Digester();
082        digester.setClassLoader(target.getClassLoader());
083        addRules(target, digester);
084        return digester;
085    }
086
087    /**
088     * Add rules to an already created Digester instance, analyzing the digester
089     * annotations in the target class.
090     *
091     * @param target the class has to be analyzed.
092     * @param digester the Digester instance reference.
093     */
094    public void addRules(final Class<?> target, final Digester digester) {
095        RuleSet ruleSet = getRuleSet(target);
096        ruleSet.addRuleInstances(digester);
097    }
098
099    /**
100     * Builds a new {@link RuleSet} analyzing the digester annotations in the
101     * target class.
102     *
103     * It avoids iterate the annotations analysis for already analyzed classes,
104     * using an in-memory LRU cache.
105     *
106     * @param target the class has to be analyzed.
107     * @return a new {@link RuleSet}.
108     */
109    public RuleSet getRuleSet(final Class<?> target) {
110        if (this.cachedRuleSet.containsKey(target)) {
111            return this.cachedRuleSet.get(target);
112        }
113
114        FromAnnotationsRuleSet ruleSet = new FromAnnotationsRuleSet(this);
115        addRulesTo(target, ruleSet);
116        this.cachedRuleSet.put(target, ruleSet);
117
118        return ruleSet;
119    }
120
121    /**
122     * Analyzes the target class and adds the {@link AnnotationRuleProvider}s to
123     * the existing {@link FromAnnotationsRuleSet}.
124     *
125     * @param target the class has to be analyzed.
126     * @param ruleSet the RuleSet where adding the providers.
127     */
128    public void addRulesTo(final Class<?> target, FromAnnotationsRuleSet ruleSet) {
129        if (target == Object.class
130                || target.isInterface()
131                || ruleSet.mapsClass(target)) {
132            return;
133        }
134
135        if (this.cachedRuleSet.containsKey(target)) {
136            ruleSet.addRulesProviderFrom(this.cachedRuleSet.get(target));
137            ruleSet.addMappedClass(target);
138            return;
139        }
140
141        // current analyzed class
142        handle(target, ruleSet);
143
144        // class fields
145        for (Field field : target.getDeclaredFields()) {
146            handle(field, ruleSet);
147        }
148
149        // class methods
150        for (Method method : target.getDeclaredMethods()) {
151            handle(method, ruleSet);
152
153            // method args
154            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
155            Class<?>[] parameterTypes = method.getParameterTypes();
156            for (int i = 0; i < parameterTypes.length; i++) {
157                handle(new MethodArgument(i, parameterTypes[i], parameterAnnotations[i]), ruleSet);
158            }
159        }
160
161        ruleSet.addMappedClass(target);
162        addRulesTo(target.getSuperclass(), ruleSet);
163    }
164
165    /**
166     * Executes an analysis for each annotation present in the element.
167     *
168     * @param element the current element under analysis.
169     * @param ruleSet the ruleSet where add providers.
170     */
171    private void handle(AnnotatedElement element, FromAnnotationsRuleSet ruleSet) {
172        for (Annotation annotation : element.getAnnotations()) {
173            handle(annotation, element, ruleSet);
174        }
175    }
176
177    /**
178     * Handles the current visited element and related annotation, invoking the
179     * right handler putting the rule provider in the rule set.
180     *
181     * @param annotation the current visited annotation.
182     * @param element the current visited element.
183     */
184    @SuppressWarnings("unchecked")
185    private <A extends Annotation, E extends AnnotatedElement, R extends Rule> void handle(A annotation,
186            E element,
187            FromAnnotationsRuleSet ruleSet) {
188        Class<?> annotationType = annotation.annotationType();
189
190        // check if it is one of the @*.List annotation
191        if (annotationType.isAnnotationPresent(DigesterRuleList.class)) {
192            Annotation[] annotations = AnnotationUtils.getAnnotationsArrayValue(annotation);
193            if (annotations != null && annotations.length > 0) {
194                // if it is an annotations array, process them
195                for (Annotation ptr : annotations) {
196                    handle(ptr, element, ruleSet);
197                }
198            }
199        } else if (annotationType.isAnnotationPresent(DigesterRule.class)) {
200            DigesterRule digesterRule = annotationType.getAnnotation(DigesterRule.class);
201
202            if (DefaultLoaderHandler.class == digesterRule.handledBy()) {
203                Class<? extends AnnotationRuleProvider<A, E, R>> providerType =
204                    (Class<? extends AnnotationRuleProvider<A, E, R>>) digesterRule.providedBy();
205                ruleSet.addRuleProvider(AnnotationUtils.getAnnotationPattern(annotation),
206                        providerType,
207                        annotation,
208                        element);
209            } else {
210                Class<? extends DigesterLoaderHandler<Annotation, AnnotatedElement>> handlerType =
211                    (Class<? extends DigesterLoaderHandler<Annotation, AnnotatedElement>>) digesterRule.handledBy();
212                DigesterLoaderHandler<Annotation, AnnotatedElement> handler =
213                    this.digesterLoaderHandlerFactory.newInstance(handlerType);
214
215                // run!
216                handler.handle(annotation, element, ruleSet);
217            }
218        }
219    }
220
221}