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}