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 }