• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Guava 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 com.google.common.collect.testing.features;
18 
19 import com.google.common.collect.testing.Helpers;
20 
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.AnnotatedElement;
23 import java.lang.reflect.Method;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 
31 /**
32  * Utilities for collecting and validating tester requirements from annotations.
33  *
34  * @author George van den Driessche
35  */
36 public class FeatureUtil {
37   /**
38    * A cache of annotated objects (typically a Class or Method) to its
39    * set of annotations.
40    */
41   private static Map<AnnotatedElement, Annotation[]> annotationCache =
42       new HashMap<AnnotatedElement, Annotation[]>();
43 
44   private static final Map<Class<?>, TesterRequirements>
45       classTesterRequirementsCache =
46           new HashMap<Class<?>, TesterRequirements>();
47 
48   /**
49    * Given a set of features, add to it all the features directly or indirectly
50    * implied by any of them, and return it.
51    * @param features the set of features to expand
52    * @return the same set of features, expanded with all implied features
53    */
addImpliedFeatures(Set<Feature<?>> features)54   public static Set<Feature<?>> addImpliedFeatures(Set<Feature<?>> features) {
55     // The base case of the recursion is an empty set of features, which will
56     // occur when the previous set contained only simple features.
57     if (!features.isEmpty()) {
58       features.addAll(impliedFeatures(features));
59     }
60     return features;
61   }
62 
63   /**
64    * Given a set of features, return a new set of all features directly or
65    * indirectly implied by any of them.
66    * @param features the set of features whose implications to find
67    * @return the implied set of features
68    */
impliedFeatures(Set<Feature<?>> features)69   public static Set<Feature<?>> impliedFeatures(Set<Feature<?>> features) {
70     Set<Feature<?>> implied = new LinkedHashSet<Feature<?>>();
71     for (Feature<?> feature : features) {
72       implied.addAll(feature.getImpliedFeatures());
73     }
74     addImpliedFeatures(implied);
75     return implied;
76   }
77 
78   /**
79    * Get the full set of requirements for a tester class.
80    * @param testerClass a tester class
81    * @return all the constraints implicitly or explicitly required by the class
82    * or any of its superclasses.
83    * @throws ConflictingRequirementsException if the requirements are mutually
84    * inconsistent.
85    */
getTesterRequirements(Class<?> testerClass)86   public static TesterRequirements getTesterRequirements(Class<?> testerClass)
87       throws ConflictingRequirementsException {
88     synchronized (classTesterRequirementsCache) {
89       TesterRequirements requirements =
90           classTesterRequirementsCache.get(testerClass);
91       if (requirements == null) {
92         requirements = buildTesterRequirements(testerClass);
93         classTesterRequirementsCache.put(testerClass, requirements);
94       }
95       return requirements;
96     }
97   }
98 
99   /**
100    * Get the full set of requirements for a tester class.
101    * @param testerMethod a test method of a tester class
102    * @return all the constraints implicitly or explicitly required by the
103    * method, its declaring class, or any of its superclasses.
104    * @throws ConflictingRequirementsException if the requirements are
105    * mutually inconsistent.
106    */
getTesterRequirements(Method testerMethod)107   public static TesterRequirements getTesterRequirements(Method testerMethod)
108       throws ConflictingRequirementsException {
109     return buildTesterRequirements(testerMethod);
110   }
111 
112   /**
113    * Construct the full set of requirements for a tester class.
114    * @param testerClass a tester class
115    * @return all the constraints implicitly or explicitly required by the class
116    * or any of its superclasses.
117    * @throws ConflictingRequirementsException if the requirements are mutually
118    * inconsistent.
119    */
buildTesterRequirements(Class<?> testerClass)120   static TesterRequirements buildTesterRequirements(Class<?> testerClass)
121       throws ConflictingRequirementsException {
122     final TesterRequirements declaredRequirements =
123         buildDeclaredTesterRequirements(testerClass);
124     Class<?> baseClass = testerClass.getSuperclass();
125     if (baseClass == null) {
126       return declaredRequirements;
127     } else {
128       final TesterRequirements clonedBaseRequirements =
129           new TesterRequirements(getTesterRequirements(baseClass));
130       return incorporateRequirements(
131           clonedBaseRequirements, declaredRequirements, testerClass);
132     }
133   }
134 
135   /**
136    * Construct the full set of requirements for a tester method.
137    * @param testerMethod a test method of a tester class
138    * @return all the constraints implicitly or explicitly required by the
139    * method, its declaring class, or any of its superclasses.
140    * @throws ConflictingRequirementsException if the requirements are mutually
141    * inconsistent.
142    */
buildTesterRequirements(Method testerMethod)143   static TesterRequirements buildTesterRequirements(Method testerMethod)
144       throws ConflictingRequirementsException {
145     TesterRequirements clonedClassRequirements = new TesterRequirements(
146         getTesterRequirements(testerMethod.getDeclaringClass()));
147     TesterRequirements declaredRequirements =
148         buildDeclaredTesterRequirements(testerMethod);
149     return incorporateRequirements(
150         clonedClassRequirements, declaredRequirements, testerMethod);
151   }
152 
153   /**
154    * Construct the set of requirements specified by annotations
155    * directly on a tester class or method.
156    * @param classOrMethod a tester class or a test method thereof
157    * @return all the constraints implicitly or explicitly required by
158    *         annotations on the class or method.
159    * @throws ConflictingRequirementsException if the requirements are mutually
160    *         inconsistent.
161    */
buildDeclaredTesterRequirements( AnnotatedElement classOrMethod)162   public static TesterRequirements buildDeclaredTesterRequirements(
163       AnnotatedElement classOrMethod)
164       throws ConflictingRequirementsException {
165     TesterRequirements requirements = new TesterRequirements();
166 
167     Iterable<Annotation> testerAnnotations =
168         getTesterAnnotations(classOrMethod);
169     for (Annotation testerAnnotation : testerAnnotations) {
170       TesterRequirements moreRequirements =
171           buildTesterRequirements(testerAnnotation);
172       incorporateRequirements(
173           requirements, moreRequirements, testerAnnotation);
174     }
175 
176     return requirements;
177   }
178 
179   /**
180    * Find all the tester annotations declared on a tester class or method.
181    * @param classOrMethod a class or method whose tester annotations to find
182    * @return an iterable sequence of tester annotations on the class
183    */
getTesterAnnotations( AnnotatedElement classOrMethod)184   public static Iterable<Annotation> getTesterAnnotations(
185       AnnotatedElement classOrMethod) {
186     List<Annotation> result = new ArrayList<Annotation>();
187 
188     Annotation[] annotations;
189     synchronized (annotationCache) {
190       annotations = annotationCache.get(classOrMethod);
191       if (annotations == null) {
192         annotations = classOrMethod.getDeclaredAnnotations();
193         annotationCache.put(classOrMethod, annotations);
194       }
195     }
196 
197     for (Annotation a : annotations) {
198       Class<? extends Annotation> annotationClass = a.annotationType();
199       if (annotationClass.isAnnotationPresent(TesterAnnotation.class)) {
200         result.add(a);
201       }
202     }
203     return result;
204   }
205 
206   /**
207    * Find all the constraints explicitly or implicitly specified by a single
208    * tester annotation.
209    * @param testerAnnotation a tester annotation
210    * @return the requirements specified by the annotation
211    * @throws ConflictingRequirementsException if the requirements are mutually
212    *         inconsistent.
213    */
buildTesterRequirements( Annotation testerAnnotation)214   private static TesterRequirements buildTesterRequirements(
215       Annotation testerAnnotation)
216       throws ConflictingRequirementsException {
217     Class<? extends Annotation> annotationClass = testerAnnotation.annotationType();
218     final Feature<?>[] presentFeatures;
219     final Feature<?>[] absentFeatures;
220     try {
221       presentFeatures = (Feature[]) annotationClass.getMethod("value")
222           .invoke(testerAnnotation);
223       absentFeatures = (Feature[]) annotationClass.getMethod("absent")
224           .invoke(testerAnnotation);
225     } catch (Exception e) {
226       throw new IllegalArgumentException(
227           "Error extracting features from tester annotation.", e);
228     }
229     Set<Feature<?>> allPresentFeatures =
230         addImpliedFeatures(Helpers.<Feature<?>>copyToSet(presentFeatures));
231     Set<Feature<?>> allAbsentFeatures =
232         addImpliedFeatures(Helpers.<Feature<?>>copyToSet(absentFeatures));
233     Set<Feature<?>> conflictingFeatures =
234         intersection(allPresentFeatures, allAbsentFeatures);
235     if (!conflictingFeatures.isEmpty()) {
236       throw new ConflictingRequirementsException("Annotation explicitly or " +
237           "implicitly requires one or more features to be both present " +
238           "and absent.",
239           conflictingFeatures, testerAnnotation);
240     }
241     return new TesterRequirements(allPresentFeatures, allAbsentFeatures);
242   }
243 
244   /**
245    * Incorporate additional requirements into an existing requirements object.
246    * @param requirements the existing requirements object
247    * @param moreRequirements more requirements to incorporate
248    * @param source the source of the additional requirements
249    *        (used only for error reporting)
250    * @return the existing requirements object, modified to include the
251    *         additional requirements
252    * @throws ConflictingRequirementsException if the additional requirements
253    *         are inconsistent with the existing requirements
254    */
incorporateRequirements( TesterRequirements requirements, TesterRequirements moreRequirements, Object source)255   private static TesterRequirements incorporateRequirements(
256       TesterRequirements requirements, TesterRequirements moreRequirements,
257       Object source) throws ConflictingRequirementsException {
258     Set<Feature<?>> presentFeatures = requirements.getPresentFeatures();
259     Set<Feature<?>> absentFeatures = requirements.getAbsentFeatures();
260     Set<Feature<?>> morePresentFeatures = moreRequirements.getPresentFeatures();
261     Set<Feature<?>> moreAbsentFeatures = moreRequirements.getAbsentFeatures();
262     checkConflict(
263         "absent", absentFeatures,
264         "present", morePresentFeatures, source);
265     checkConflict(
266         "present", presentFeatures,
267         "absent", moreAbsentFeatures, source);
268     presentFeatures.addAll(morePresentFeatures);
269     absentFeatures.addAll(moreAbsentFeatures);
270     return requirements;
271   }
272 
273   // Used by incorporateRequirements() only
checkConflict( String earlierRequirement, Set<Feature<?>> earlierFeatures, String newRequirement, Set<Feature<?>> newFeatures, Object source)274   private static void checkConflict(
275       String earlierRequirement, Set<Feature<?>> earlierFeatures,
276       String newRequirement, Set<Feature<?>> newFeatures,
277       Object source) throws ConflictingRequirementsException {
278     Set<Feature<?>> conflictingFeatures;
279     conflictingFeatures = intersection(newFeatures, earlierFeatures);
280     if (!conflictingFeatures.isEmpty()) {
281       throw new ConflictingRequirementsException(String.format(
282           "Annotation requires to be %s features that earlier " +
283           "annotations required to be %s.",
284               newRequirement, earlierRequirement),
285           conflictingFeatures, source);
286     }
287   }
288 
289   /**
290    * Construct a new {@link java.util.Set} that is the intersection
291    * of the given sets.
292    */
293   // Calls generic varargs method.
294   @SuppressWarnings("unchecked")
intersection( Set<? extends T> set1, Set<? extends T> set2)295   public static <T> Set<T> intersection(
296       Set<? extends T> set1, Set<? extends T> set2) {
297     return intersection(new Set[] {set1, set2});
298   }
299 
300   /**
301    * Construct a new {@link java.util.Set} that is the intersection
302    * of all the given sets.
303    * @param sets the sets to intersect
304    * @return the intersection of the sets
305    * @throws java.lang.IllegalArgumentException if there are no sets to
306    *         intersect
307    */
intersection(Set<? extends T> .... sets)308   public static <T> Set<T> intersection(Set<? extends T> ... sets) {
309     if (sets.length == 0) {
310       throw new IllegalArgumentException(
311           "Can't intersect no sets; would have to return the universe.");
312     }
313     Set<T> results = Helpers.copyToSet(sets[0]);
314     for (int i = 1; i < sets.length; i++) {
315       Set<? extends T> set = sets[i];
316       results.retainAll(set);
317     }
318     return results;
319   }
320 }
321