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     */
018    package org.apache.commons.digester.annotations;
019    
020    import java.lang.annotation.Annotation;
021    import java.lang.reflect.AnnotatedElement;
022    import java.lang.reflect.Field;
023    import java.lang.reflect.Method;
024    
025    import org.apache.commons.digester.Digester;
026    import org.apache.commons.digester.Rule;
027    import org.apache.commons.digester.RuleSet;
028    import org.apache.commons.digester.annotations.handlers.DefaultLoaderHandler;
029    import org.apache.commons.digester.annotations.internal.RuleSetCache;
030    import org.apache.commons.digester.annotations.reflect.MethodArgument;
031    import org.apache.commons.digester.annotations.spi.AnnotationRuleProviderFactory;
032    import org.apache.commons.digester.annotations.spi.DigesterLoaderHandlerFactory;
033    import 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     */
041    public 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    }