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