View Javadoc
1   /*
2    *  Copyright 2010, 2011 Chris Pheby
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.jadira.bindings.core.provider;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.jadira.bindings.core.annotation.BindingScope;
28  import org.jadira.bindings.core.annotation.DefaultBinding;
29  import org.jadira.bindings.core.binder.ConverterKey;
30  import org.jadira.bindings.core.spi.ConverterProvider;
31  import org.jadira.bindings.core.utils.reflection.TypeHelper;
32  
33  /**
34   * Used to implement a binding provider instance that uses annotations to match methods 
35   * @param <T> The annotation used to identify a to method
36   * @param <F> The annotation used to identify a from method
37   */
38  public class AbstractAnnotationMatchingConverterProvider<T extends Annotation, F extends Annotation> implements ConverterProvider  {
39  
40  	public <I,O> Map<ConverterKey<?, ?>, Method> matchToMethods(Class<?> cls) {
41  
42  		Map<ConverterKey<?, ?>, Method> matchedMethods = new HashMap<ConverterKey<?, ?>, Method>();
43  		
44  		@SuppressWarnings("unchecked")
45  		Class<T> toAnnotation = (Class<T>) TypeHelper.getTypeArguments(
46  				AbstractAnnotationMatchingConverterProvider.class,
47  				this.getClass()).get(0);
48  
49  		Class<?> loopCls = cls;
50  
51  		while (loopCls != Object.class) {
52  			Method[] methods = loopCls.getDeclaredMethods();
53  			for (Method method : methods) {
54  				if (signatureIndicatesToMethodCandidate(method)) {
55  					T toMethodAnnotation = method.getAnnotation(toAnnotation);
56  					if (toMethodAnnotation != null) {
57  						List<Class<? extends Annotation>> qualifiers = determineQualifiers(toMethodAnnotation, method.getAnnotations());
58  						
59  						for (Class<? extends Annotation> nextQualifier : qualifiers) {
60  							@SuppressWarnings("unchecked")
61  							Class<O> returnType = (Class<O>)method.getReturnType();
62  						
63  							
64  							if (Modifier.isStatic(method.getModifiers())) {
65  								@SuppressWarnings("unchecked")
66  								Class<I> inputClass = (Class<I>) method.getParameterTypes()[0];
67  								matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);
68  							} else {
69  								@SuppressWarnings("unchecked")
70  								Class<I> inputClass = (Class<I>) cls;
71  								matchedMethods.put(new ConverterKey<I,O>(inputClass, returnType, nextQualifier), method);								
72  							}
73  							
74  						}
75  					}
76  				}
77  			}
78  			loopCls = loopCls.getSuperclass();
79  		}
80  		return matchedMethods;
81  	}
82  
83  	private boolean signatureIndicatesToMethodCandidate(Method method) {
84  		
85  		if (!Modifier.isPublic(method.getModifiers())) {
86  			return false;
87  		}
88  		if (method.getReturnType().equals(Void.TYPE)) {
89  			return false;
90  		}
91  		if (!isToMatch(method)) {
92  			return false;
93  		}
94  		if ((!(Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 1))
95  			&&
96  			(!(!Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == 0))) {
97  			return false;
98  		}
99  		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 }