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