001/*
002 *  Copyright 2010, 2011 Chris Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.bindings.core.provider;
017
018import java.lang.annotation.Annotation;
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.jadira.bindings.core.annotation.BindingScope;
028import org.jadira.bindings.core.annotation.DefaultBinding;
029import org.jadira.bindings.core.binder.ConverterKey;
030import org.jadira.bindings.core.spi.ConverterProvider;
031import org.jadira.bindings.core.utils.reflection.TypeHelper;
032
033/**
034 * Used to implement a binding provider instance that uses annotations to match methods 
035 * @param <T> The annotation used to identify a to method
036 * @param <F> The annotation used to identify a from method
037 */
038public class AbstractAnnotationMatchingConverterProvider<T extends Annotation, F extends Annotation> implements ConverterProvider  {
039
040        public <I,O> Map<ConverterKey<?, ?>, Method> matchToMethods(Class<?> cls) {
041
042                Map<ConverterKey<?, ?>, Method> matchedMethods = new HashMap<ConverterKey<?, ?>, Method>();
043                
044                @SuppressWarnings("unchecked")
045                Class<T> toAnnotation = (Class<T>) TypeHelper.getTypeArguments(
046                                AbstractAnnotationMatchingConverterProvider.class,
047                                this.getClass()).get(0);
048
049                Class<?> loopCls = cls;
050
051                while (loopCls != Object.class) {
052                        Method[] methods = loopCls.getDeclaredMethods();
053                        for (Method method : methods) {
054                                if (signatureIndicatesToMethodCandidate(method)) {
055                                        T toMethodAnnotation = method.getAnnotation(toAnnotation);
056                                        if (toMethodAnnotation != null) {
057                                                List<Class<? extends Annotation>> qualifiers = determineQualifiers(toMethodAnnotation, method.getAnnotations());
058                                                
059                                                for (Class<? extends Annotation> nextQualifier : qualifiers) {
060                                                        @SuppressWarnings("unchecked")
061                                                        Class<O> returnType = (Class<O>)method.getReturnType();
062                                                
063                                                        
064                                                        if (Modifier.isStatic(method.getModifiers())) {
065                                                                @SuppressWarnings("unchecked")
066                                                                Class<I> inputClass = (Class<I>) method.getParameterTypes()[0];
067                                                                matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);
068                                                        } else {
069                                                                @SuppressWarnings("unchecked")
070                                                                Class<I> inputClass = (Class<I>) cls;
071                                                                matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);                                                               
072                                                        }
073                                                        
074                                                }
075                                        }
076                                }
077                        }
078                        loopCls = loopCls.getSuperclass();
079                }
080                return matchedMethods;
081        }
082
083        private boolean signatureIndicatesToMethodCandidate(Method method) {
084                
085                if (!Modifier.isPublic(method.getModifiers())) {
086                        return false;
087                }
088                if (method.getReturnType().equals(Void.TYPE)) {
089                        return false;
090                }
091                if (!isToMatch(method)) {
092                        return false;
093                }
094                if ((!(Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 1))
095                        &&
096                        (!(!Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 0))) {
097                        return false;
098                }
099                return true;
100        }
101
102        public <I,O> Map<ConverterKey<?,?>, Constructor<O>> matchFromConstructors(Class<O> cls) {
103
104                Map<ConverterKey<?, ?>, Constructor<O>> matchedConstructors = new HashMap<ConverterKey<?, ?>, Constructor<O>>();
105                
106                @SuppressWarnings("unchecked")
107                Class<F> fromAnnotation = (Class<F>) TypeHelper.getTypeArguments(
108                                AbstractAnnotationMatchingConverterProvider.class,
109                                this.getClass()).get(1);
110
111                Class<?> loopCls = cls;
112
113                while (loopCls != Object.class) {
114                        
115                        @SuppressWarnings("unchecked")
116                        Constructor<O>[] constructors = (Constructor<O>[])loopCls.getDeclaredConstructors();
117                        
118                        for (Constructor<O> constructor : constructors) {
119                                if (signatureIndicatesFromConstructorCandidate(constructor)) {
120                                        F fromConstructorAnnotation = constructor.getAnnotation(fromAnnotation);
121                                        if (fromConstructorAnnotation != null) {
122                                                List<Class<? extends Annotation>> qualifiers = determineQualifiers(fromConstructorAnnotation, constructor.getAnnotations());
123                                                
124                                                for (Class<? extends Annotation> nextQualifier : qualifiers) {
125                                                        @SuppressWarnings("unchecked")
126                                                        Class<I> paramType = (Class<I>)constructor.getParameterTypes()[0];
127                                                        matchedConstructors.put(new ConverterKey<I,O>((Class<I>)paramType, cls, nextQualifier), constructor);
128                                                }
129                                        }
130                                }
131                        }
132                        loopCls = loopCls.getSuperclass();
133                }
134                return matchedConstructors;
135        }
136        
137        private boolean signatureIndicatesFromConstructorCandidate(Constructor<?> constructor) {
138                
139                if (!Modifier.isPublic(constructor.getModifiers())) {
140                        return false;
141                }
142                if (!(constructor.getParameterTypes().length == 1)) {
143                        return false;
144                }
145                if (!isFromMatch(constructor)) {
146                        return false;
147                }
148                return true;
149        }
150
151        public <I,O> Map<ConverterKey<?,?>, Method> matchFromMethods(Class<?> cls) {
152
153                Map<ConverterKey<?, ?>, Method> matchedMethods = new HashMap<ConverterKey<?, ?>, Method>();
154                
155                @SuppressWarnings("unchecked")
156                Class<F> fromAnnotation = (Class<F>) TypeHelper.getTypeArguments(
157                                AbstractAnnotationMatchingConverterProvider.class,
158                                this.getClass()).get(1);
159
160                Class<?> loopCls = cls;
161
162                while (loopCls != Object.class) {
163                        Method[] methods = loopCls.getDeclaredMethods();
164                        for (Method method : methods) {
165                                if (signatureIndicatesFromMethodCandidate(method)) {
166                                        
167                                        F fromMethodAnnotation = method.getAnnotation(fromAnnotation);
168                                        if (fromMethodAnnotation != null) {
169                                                List<Class<? extends Annotation>> qualifiers = determineQualifiers(fromMethodAnnotation, method.getAnnotations());
170                                                
171                                                for (Class<? extends Annotation> nextQualifier : qualifiers) {
172                                                        @SuppressWarnings("unchecked")
173                                                        Class<I> paramType = (Class<I>)method.getParameterTypes()[0];
174                                                        @SuppressWarnings("unchecked")
175                                                        Class<O> outputClass = (Class<O>)cls;
176                                                        matchedMethods.put(new ConverterKey<I,O>(paramType, outputClass, nextQualifier), method);
177                                                }
178                                        }
179                                }
180                        }
181                        loopCls = loopCls.getSuperclass();
182                }
183                return matchedMethods;
184        }
185
186        private boolean signatureIndicatesFromMethodCandidate(Method method) {
187        
188                if (!Modifier.isPublic(method.getModifiers())) {
189                        return false;
190                }
191                if (method.getReturnType().equals(Void.TYPE)) {
192                        return false;
193                }
194                if (!(Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 1)) {
195                        return false;
196                }
197                if (!isFromMatch(method)) {
198                        return false;
199                }
200                return true;
201        }
202
203        /**
204         * Subclasses can override this template method with their own matching strategy
205         * @param method The method to be determined
206         * @return True if match
207         */
208        protected boolean isToMatch(Method method) {
209                return true;
210        }
211
212        /**
213         * Subclasses can override this template method with their own matching strategy
214         * @param constructor The constructor to be determined
215         * @return True if match
216         */
217        protected boolean isFromMatch(Constructor<?> constructor) {
218                return true;
219        }
220
221        /**
222         * Subclasses can override this template method with their own matching strategy
223         * @param method The method to be determined
224         * @return True if match
225         */
226        protected boolean isFromMatch(Method method) {
227                return true;
228        }
229        
230        /**
231     * Returns the qualifiers for this method, setting the Default qualifier if none are found
232     * Qualifiers can be either explicitly applied to the method, or implicit on account of being found
233     * in the annotation itself (for example @Plus would have a default binding scope of @PlusOperator)
234     * @param bindingAnnotation The Annotation for the binding to check if it specifies a binding scope.
235     * @param allAnnotations Array of Annotations on the target method to check if they specify a scope
236     * @return True if method can be matched for the given scope
237     */
238    protected List<Class<? extends Annotation>> determineQualifiers(Annotation bindingAnnotation, Annotation... allAnnotations) {
239
240        List<Class<? extends Annotation>> result = new ArrayList<Class<? extends Annotation>>();
241
242        // The binding annotation itself is marked with @BindingScope
243        Class<? extends Annotation> bindingAnnotationType = bindingAnnotation.annotationType();
244        if (bindingAnnotationType.getAnnotation(BindingScope.class) != null) {
245            result.add(bindingAnnotationType);
246        }
247
248        // The binding annotation is annotated with annotations marked with @BindingScope
249        for (Annotation next : bindingAnnotation.annotationType().getAnnotations()) {
250            Class<? extends Annotation> nextType = next.annotationType();
251            if (nextType.getAnnotation(BindingScope.class) != null) {
252                result.add(nextType);
253            }
254        }
255
256        // The method used to attach the annotation is annotated with annotations marked with @BindingScope
257        for (Annotation next : allAnnotations) {
258            Class<? extends Annotation> nextType = next.annotationType();
259            if (nextType.getAnnotation(BindingScope.class) != null) {
260                result.add(nextType);
261            }
262        }
263
264        if (result.isEmpty()) {
265                result.add(DefaultBinding.class);
266        }
267        
268        return result;
269    }
270
271}