com.google.common.collect.testing.features.FeatureUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guava-testlib Show documentation
Show all versions of guava-testlib Show documentation
Guava testlib is a set of java classes used for more convenient
unit testing - particularly to assist the tests for Guava itself.
/*
* Copyright (C) 2008 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect.testing.features;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.testing.Helpers;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* Utilities for collecting and validating tester requirements from annotations.
*
* @author George van den Driessche
*/
@GwtIncompatible
public class FeatureUtil {
/** A cache of annotated objects (typically a Class or Method) to its set of annotations. */
private static Map> annotationCache = new HashMap<>();
private static final Map, TesterRequirements> classTesterRequirementsCache =
new HashMap<>();
private static final Map methodTesterRequirementsCache =
new HashMap<>();
/**
* Given a set of features, add to it all the features directly or indirectly implied by any of
* them, and return it.
*
* @param features the set of features to expand
* @return the same set of features, expanded with all implied features
*/
@CanIgnoreReturnValue
public static Set> addImpliedFeatures(Set> features) {
Queue> queue = new ArrayDeque<>(features);
while (!queue.isEmpty()) {
Feature> feature = queue.remove();
for (Feature> implied : feature.getImpliedFeatures()) {
if (features.add(implied)) {
queue.add(implied);
}
}
}
return features;
}
/**
* Given a set of features, return a new set of all features directly or indirectly implied by any
* of them.
*
* @param features the set of features whose implications to find
* @return the implied set of features
*/
public static Set> impliedFeatures(Set> features) {
Set> impliedSet = new LinkedHashSet<>();
Queue> queue = new ArrayDeque<>(features);
while (!queue.isEmpty()) {
Feature> feature = queue.remove();
for (Feature> implied : feature.getImpliedFeatures()) {
if (!features.contains(implied) && impliedSet.add(implied)) {
queue.add(implied);
}
}
}
return impliedSet;
}
/**
* Get the full set of requirements for a tester class.
*
* @param testerClass a tester class
* @return all the constraints implicitly or explicitly required by the class or any of its
* superclasses.
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
public static TesterRequirements getTesterRequirements(Class> testerClass)
throws ConflictingRequirementsException {
synchronized (classTesterRequirementsCache) {
TesterRequirements requirements = classTesterRequirementsCache.get(testerClass);
if (requirements == null) {
requirements = buildTesterRequirements(testerClass);
classTesterRequirementsCache.put(testerClass, requirements);
}
return requirements;
}
}
/**
* Get the full set of requirements for a tester class.
*
* @param testerMethod a test method of a tester class
* @return all the constraints implicitly or explicitly required by the method, its declaring
* class, or any of its superclasses.
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
public static TesterRequirements getTesterRequirements(Method testerMethod)
throws ConflictingRequirementsException {
synchronized (methodTesterRequirementsCache) {
TesterRequirements requirements = methodTesterRequirementsCache.get(testerMethod);
if (requirements == null) {
requirements = buildTesterRequirements(testerMethod);
methodTesterRequirementsCache.put(testerMethod, requirements);
}
return requirements;
}
}
/**
* Construct the full set of requirements for a tester class.
*
* @param testerClass a tester class
* @return all the constraints implicitly or explicitly required by the class or any of its
* superclasses.
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
static TesterRequirements buildTesterRequirements(Class> testerClass)
throws ConflictingRequirementsException {
TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerClass);
Class> baseClass = testerClass.getSuperclass();
if (baseClass == null) {
return declaredRequirements;
} else {
TesterRequirements clonedBaseRequirements =
new TesterRequirements(getTesterRequirements(baseClass));
return incorporateRequirements(clonedBaseRequirements, declaredRequirements, testerClass);
}
}
/**
* Construct the full set of requirements for a tester method.
*
* @param testerMethod a test method of a tester class
* @return all the constraints implicitly or explicitly required by the method, its declaring
* class, or any of its superclasses.
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
static TesterRequirements buildTesterRequirements(Method testerMethod)
throws ConflictingRequirementsException {
TesterRequirements clonedClassRequirements =
new TesterRequirements(getTesterRequirements(testerMethod.getDeclaringClass()));
TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerMethod);
return incorporateRequirements(clonedClassRequirements, declaredRequirements, testerMethod);
}
/**
* Find all the constraints explicitly or implicitly specified by a single tester annotation.
*
* @param testerAnnotation a tester annotation
* @return the requirements specified by the annotation
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
private static TesterRequirements buildTesterRequirements(Annotation testerAnnotation)
throws ConflictingRequirementsException {
Class extends Annotation> annotationClass = testerAnnotation.annotationType();
Feature>[] presentFeatures;
Feature>[] absentFeatures;
try {
presentFeatures = (Feature[]) annotationClass.getMethod("value").invoke(testerAnnotation);
absentFeatures = (Feature[]) annotationClass.getMethod("absent").invoke(testerAnnotation);
} catch (Exception e) {
throw new IllegalArgumentException("Error extracting features from tester annotation.", e);
}
Set> allPresentFeatures =
addImpliedFeatures(Helpers.>copyToSet(presentFeatures));
Set> allAbsentFeatures =
addImpliedFeatures(Helpers.>copyToSet(absentFeatures));
if (!Collections.disjoint(allPresentFeatures, allAbsentFeatures)) {
throw new ConflictingRequirementsException(
"Annotation explicitly or "
+ "implicitly requires one or more features to be both present "
+ "and absent.",
intersection(allPresentFeatures, allAbsentFeatures),
testerAnnotation);
}
return new TesterRequirements(allPresentFeatures, allAbsentFeatures);
}
/**
* Construct the set of requirements specified by annotations directly on a tester class or
* method.
*
* @param classOrMethod a tester class or a test method thereof
* @return all the constraints implicitly or explicitly required by annotations on the class or
* method.
* @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
*/
public static TesterRequirements buildDeclaredTesterRequirements(AnnotatedElement classOrMethod)
throws ConflictingRequirementsException {
TesterRequirements requirements = new TesterRequirements();
Iterable testerAnnotations = getTesterAnnotations(classOrMethod);
for (Annotation testerAnnotation : testerAnnotations) {
TesterRequirements moreRequirements = buildTesterRequirements(testerAnnotation);
incorporateRequirements(requirements, moreRequirements, testerAnnotation);
}
return requirements;
}
/**
* Find all the tester annotations declared on a tester class or method.
*
* @param classOrMethod a class or method whose tester annotations to find
* @return an iterable sequence of tester annotations on the class
*/
public static Iterable getTesterAnnotations(AnnotatedElement classOrMethod) {
synchronized (annotationCache) {
List annotations = annotationCache.get(classOrMethod);
if (annotations == null) {
annotations = new ArrayList<>();
for (Annotation a : classOrMethod.getDeclaredAnnotations()) {
if (a.annotationType().isAnnotationPresent(TesterAnnotation.class)) {
annotations.add(a);
}
}
annotations = Collections.unmodifiableList(annotations);
annotationCache.put(classOrMethod, annotations);
}
return annotations;
}
}
/**
* Incorporate additional requirements into an existing requirements object.
*
* @param requirements the existing requirements object
* @param moreRequirements more requirements to incorporate
* @param source the source of the additional requirements (used only for error reporting)
* @return the existing requirements object, modified to include the additional requirements
* @throws ConflictingRequirementsException if the additional requirements are inconsistent with
* the existing requirements
*/
@CanIgnoreReturnValue
private static TesterRequirements incorporateRequirements(
TesterRequirements requirements, TesterRequirements moreRequirements, Object source)
throws ConflictingRequirementsException {
Set> presentFeatures = requirements.getPresentFeatures();
Set> absentFeatures = requirements.getAbsentFeatures();
Set> morePresentFeatures = moreRequirements.getPresentFeatures();
Set> moreAbsentFeatures = moreRequirements.getAbsentFeatures();
checkConflict("absent", absentFeatures, "present", morePresentFeatures, source);
checkConflict("present", presentFeatures, "absent", moreAbsentFeatures, source);
presentFeatures.addAll(morePresentFeatures);
absentFeatures.addAll(moreAbsentFeatures);
return requirements;
}
// Used by incorporateRequirements() only
private static void checkConflict(
String earlierRequirement,
Set> earlierFeatures,
String newRequirement,
Set> newFeatures,
Object source)
throws ConflictingRequirementsException {
if (!Collections.disjoint(newFeatures, earlierFeatures)) {
throw new ConflictingRequirementsException(
String.format(
Locale.ROOT,
"Annotation requires to be %s features that earlier "
+ "annotations required to be %s.",
newRequirement,
earlierRequirement),
intersection(newFeatures, earlierFeatures),
source);
}
}
/** Construct a new {@link java.util.Set} that is the intersection of the given sets. */
public static Set intersection(Set extends T> set1, Set extends T> set2) {
Set result = Helpers.copyToSet(set1);
result.retainAll(set2);
return result;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy