View Javadoc

1   /*
2    * Copyright 2010-2013 the original author or authors.
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  
17  package org.callbackparams.combine.annotation;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Method;
22  import java.util.AbstractCollection;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.Map;
27  import org.callbackparams.ParameterizedValue;
28  import org.callbackparams.combine.CombineStrategy;
29  import org.callbackparams.combine.reflect.CallbackRecordsFactory;
30  import org.callbackparams.combine.reflect.Combined;
31  import org.callbackparams.support.ExceptionUtil;
32  
33  /**
34   * This Java5+ extension to {@link CallbackRecordsFactory} supports
35   * the annotations {@link CombineConfig @CombineConfig} and
36   * {@link CallbackRecords @CallbackRecords}. An instance of this class will
37   * be returned by {@link CallbackRecordsFactory#getInstance()} when invoked
38   * inside a Java5+ JVM.
39   * It also sees to that fields that are annotated with
40   * {@link ParameterizedValue @ParameterizedValue} will be combined and made
41   * available as callback ({@link #isValueAlsoAvailableAsCallback(Field f) usually}).
42   *
43   * @author Henrik Kaipe
44   * @see CallbackRecordsFactory#getInstance()
45   */
46  public class AnnotationAwareCallbackRecordsFactory
47  extends CallbackRecordsFactory {
48  
49      @Override
50      public CombineStrategy retrieveCombineStrategy(Class testClass) {
51          final CombineConfig combineConfig = (CombineConfig)
52                  testClass.getAnnotation(CombineConfig.class);
53          
54          if (null == combineConfig) {
55              return super.retrieveCombineStrategy(testClass);
56              
57          } else {
58              final CombineStrategy combineStrategy;
59              try {
60                  combineStrategy = combineConfig.strategy().newInstance();
61              } catch (Exception x) {
62                  throw ExceptionUtil.unchecked(x);
63              }
64              if (0 <= combineConfig.maxCount()) {
65                  combineStrategy.setMaxCount(combineConfig.maxCount());
66              }
67              if (-1 != combineConfig.randomSeed()) {
68                  combineStrategy.setRandomSeed(combineConfig.randomSeed());
69              }
70              return combineStrategy;
71          }
72      }
73  
74      /**
75       * This overridden implementation looks for a method that is annotated with
76       * {@link CallbackRecords} and returns the records provided by that method.
77       * The method must be static or an Error will be thrown.
78       * If there is no such method or if the method is declared in a super-class
79       * and there is a {@link CombineConfig} annotation on a subclass then the
80       * callback-records will instead be determined in the combinatory manner.
81       * If the {@link CallbackRecords} annotated method's class is also annotated
82       * with {@link CombineConfig} then the callback-records will be collected
83       * by both strategies, i.e. combinatory-determined records will be collected
84       * as well as the records returned by the {@link CallbackRecords} method.
85       */
86      @Override
87      public Collection collectCallbackRecordsReflectively(final Class testClass) {
88          final Class combineConfigAnnotatedSuperClass =
89                  findCombineConfigAnnotatedSuperClass(testClass);
90          
91          Collection combinedRecords = null;
92          for (Class c = testClass ; null == combinedRecords
93                  ; c = c.getSuperclass()) {
94              if (combineConfigAnnotatedSuperClass == c) {
95                  combinedRecords = super
96                          .collectCallbackRecordsReflectively(testClass);
97              }
98              
99              for (Method m : c.getDeclaredMethods()) {
100                 if (m.isAnnotationPresent(CallbackRecords.class)) {
101                     try {
102                         m.setAccessible(true);
103                     } catch (SecurityException x) {
104                         /* Never mind - we'll try to invoke it anyway ... */
105                     }
106                     try {
107                         return new LazilyQueedCollection(
108                                 (Collection) m.invoke(null, (Object[])null),
109                                 combinedRecords);
110                     } catch (Exception x) {
111                         throw new Error("Failed to execute the @CallbackRecords "
112                             + "annotated method. Make sure it is a static "
113                             + "method!!", x);
114                     }
115                 }
116             }
117         }
118         
119         return combinedRecords;
120     }
121     
122     private Class findCombineConfigAnnotatedSuperClass(Class testSuperClass) {
123         if (Object.class == testSuperClass) {
124             return Object.class;
125             
126         } else {
127             for (Annotation a : testSuperClass.getDeclaredAnnotations()) {
128                 if (a instanceof CombineConfig) {
129                     return testSuperClass;
130                 }
131             }
132             return findCombineConfigAnnotatedSuperClass(
133                     testSuperClass.getSuperclass());
134         }
135     }
136 
137     @Override
138     protected Map<Field,Class<?>> collectValueInjections(final Class testClass) {
139         Map injections = super.collectValueInjections(testClass);
140         for (Class<?> c = testClass; Object.class != c; c = c.getSuperclass()) {
141             for (Field f : c.getDeclaredFields()) {
142                 if (f.isAnnotationPresent(ParameterizedValue.class)) {
143                     injections.put(f, f.getType());
144                 }
145             }
146         }
147         return injections;
148     }
149 
150     /**
151      * This overridden method makes sure that a parameter value stays out of
152      * the callback-record if its field is annotated with
153      * {@link ParameterizedValue#alsoAvailableAsCallback() @ParameterizedValue(alsoAvailableAsCallback=false)}
154      * @see ParameterizedValue
155      */
156     @Override
157     protected boolean isValueAlsoAvailableAsCallback(Field f) {
158         if (false == super.isValueAlsoAvailableAsCallback(f)) {
159             return false;
160         } else {
161             ParameterizedValue pv = f.getAnnotation(ParameterizedValue.class);
162             return null == pv || pv.alsoAvailableAsCallback();
163         }
164     }
165 
166     /**
167      * The collections passed to the constructor are combined in a lazy way. -
168      * Their iterators are not created until the iterators of the previous
169      * collections have been depleted.
170      */
171     private static class LazilyQueedCollection
172     extends AbstractCollection<Combined[]> {
173         private final Collection[] que;
174 
175         LazilyQueedCollection(Collection... que) {
176             this.que = que;
177         }
178 
179         public Iterator<Combined[]> iterator() {
180             return new Iterator<Combined[]>() {
181 
182                 Iterator currentIterator = Collections.EMPTY_LIST.iterator();
183                 int queIndex = -1;
184                 Object next;
185                 
186                 /* Initiate currentIterator, queIndex and next ... */
187                 {
188                     prepareNext();
189                 }
190                 
191                 public boolean hasNext() {
192                     return null != currentIterator;
193                 }
194                 public Combined[] next() {
195                     try {
196                         return asCombinedArray(next);
197                         
198                     } finally {
199                         prepareNext();
200                     }
201                 }
202                 public void remove() {
203                     throw new UnsupportedOperationException("Not supported yet.");
204                 }
205 
206                 private void prepareNext() {
207                     while (false == currentIterator.hasNext()
208                             && ++queIndex < que.length) {
209                         if (null != que[queIndex]) {
210                             currentIterator = que[queIndex].iterator();
211                         }
212                     }
213                     if (currentIterator.hasNext()) {
214                         next = currentIterator.next();
215                     } else {
216                         next = null;
217                         currentIterator = null;
218                     }
219                 }
220             };
221         }
222 
223         public int size() {
224             int size = 0;
225             for (Collection c : que) {
226                 if (null != c) {
227                     size += c.size();
228                 }
229             }
230             return size;
231         }
232     }
233 }