Java

Форк
0
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

17
package com.google.common.collect.testing.features;
18

19
import static com.google.common.collect.testing.Helpers.copyToSet;
20
import static java.util.Collections.disjoint;
21
import static java.util.Collections.unmodifiableList;
22

23
import com.google.common.annotations.GwtIncompatible;
24
import com.google.errorprone.annotations.CanIgnoreReturnValue;
25
import java.lang.annotation.Annotation;
26
import java.lang.reflect.AnnotatedElement;
27
import java.lang.reflect.Method;
28
import java.util.ArrayDeque;
29
import java.util.ArrayList;
30
import java.util.HashMap;
31
import java.util.LinkedHashSet;
32
import java.util.List;
33
import java.util.Locale;
34
import java.util.Map;
35
import java.util.Queue;
36
import 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
44
public final class FeatureUtil {
45
  /** A cache of annotated objects (typically a Class or Method) to its set of annotations. */
46
  private static final Map<AnnotatedElement, List<Annotation>> annotationCache = new HashMap<>();
47

48
  private static final Map<Class<?>, TesterRequirements> classTesterRequirementsCache =
49
      new HashMap<>();
50

51
  private static final Map<Method, TesterRequirements> methodTesterRequirementsCache =
52
      new 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
62
  public static Set<Feature<?>> addImpliedFeatures(Set<Feature<?>> features) {
63
    Queue<Feature<?>> queue = new ArrayDeque<>(features);
64
    while (!queue.isEmpty()) {
65
      Feature<?> feature = queue.remove();
66
      for (Feature<?> implied : feature.getImpliedFeatures()) {
67
        if (features.add(implied)) {
68
          queue.add(implied);
69
        }
70
      }
71
    }
72
    return 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
   */
82
  public static Set<Feature<?>> impliedFeatures(Set<Feature<?>> features) {
83
    Set<Feature<?>> impliedSet = new LinkedHashSet<>();
84
    Queue<Feature<?>> queue = new ArrayDeque<>(features);
85
    while (!queue.isEmpty()) {
86
      Feature<?> feature = queue.remove();
87
      for (Feature<?> implied : feature.getImpliedFeatures()) {
88
        if (!features.contains(implied) && impliedSet.add(implied)) {
89
          queue.add(implied);
90
        }
91
      }
92
    }
93
    return 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
   */
104
  public static TesterRequirements getTesterRequirements(Class<?> testerClass)
105
      throws ConflictingRequirementsException {
106
    synchronized (classTesterRequirementsCache) {
107
      TesterRequirements requirements = classTesterRequirementsCache.get(testerClass);
108
      if (requirements == null) {
109
        requirements = buildTesterRequirements(testerClass);
110
        classTesterRequirementsCache.put(testerClass, requirements);
111
      }
112
      return 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
   */
124
  public static TesterRequirements getTesterRequirements(Method testerMethod)
125
      throws ConflictingRequirementsException {
126
    synchronized (methodTesterRequirementsCache) {
127
      TesterRequirements requirements = methodTesterRequirementsCache.get(testerMethod);
128
      if (requirements == null) {
129
        requirements = buildTesterRequirements(testerMethod);
130
        methodTesterRequirementsCache.put(testerMethod, requirements);
131
      }
132
      return 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
   */
144
  static TesterRequirements buildTesterRequirements(Class<?> testerClass)
145
      throws ConflictingRequirementsException {
146
    TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerClass);
147
    Class<?> baseClass = testerClass.getSuperclass();
148
    if (baseClass == null) {
149
      return declaredRequirements;
150
    } else {
151
      TesterRequirements clonedBaseRequirements =
152
          new TesterRequirements(getTesterRequirements(baseClass));
153
      return 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
   */
165
  static TesterRequirements buildTesterRequirements(Method testerMethod)
166
      throws ConflictingRequirementsException {
167
    TesterRequirements clonedClassRequirements =
168
        new TesterRequirements(getTesterRequirements(testerMethod.getDeclaringClass()));
169
    TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerMethod);
170
    return 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
   */
180
  private static TesterRequirements buildTesterRequirements(Annotation testerAnnotation)
181
      throws ConflictingRequirementsException {
182
    Class<? extends Annotation> annotationClass = testerAnnotation.annotationType();
183
    Feature<?>[] presentFeatures;
184
    Feature<?>[] absentFeatures;
185
    try {
186
      presentFeatures = (Feature<?>[]) annotationClass.getMethod("value").invoke(testerAnnotation);
187
      absentFeatures = (Feature<?>[]) annotationClass.getMethod("absent").invoke(testerAnnotation);
188
    } catch (Exception e) {
189
      throw new IllegalArgumentException("Error extracting features from tester annotation.", e);
190
    }
191
    Set<Feature<?>> allPresentFeatures = addImpliedFeatures(copyToSet(presentFeatures));
192
    Set<Feature<?>> allAbsentFeatures = copyToSet(absentFeatures);
193
    if (!disjoint(allPresentFeatures, allAbsentFeatures)) {
194
      throw new ConflictingRequirementsException(
195
          "Annotation explicitly or "
196
              + "implicitly requires one or more features to be both present "
197
              + "and absent.",
198
          intersection(allPresentFeatures, allAbsentFeatures),
199
          testerAnnotation);
200
    }
201
    return 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
   */
213
  public static TesterRequirements buildDeclaredTesterRequirements(AnnotatedElement classOrMethod)
214
      throws ConflictingRequirementsException {
215
    TesterRequirements requirements = new TesterRequirements();
216

217
    Iterable<Annotation> testerAnnotations = getTesterAnnotations(classOrMethod);
218
    for (Annotation testerAnnotation : testerAnnotations) {
219
      TesterRequirements moreRequirements = buildTesterRequirements(testerAnnotation);
220
      incorporateRequirements(requirements, moreRequirements, testerAnnotation);
221
    }
222

223
    return 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
   */
232
  public static Iterable<Annotation> getTesterAnnotations(AnnotatedElement classOrMethod) {
233
    synchronized (annotationCache) {
234
      List<Annotation> annotations = annotationCache.get(classOrMethod);
235
      if (annotations == null) {
236
        annotations = new ArrayList<>();
237
        for (Annotation a : classOrMethod.getDeclaredAnnotations()) {
238
          if (a.annotationType().isAnnotationPresent(TesterAnnotation.class)) {
239
            annotations.add(a);
240
          }
241
        }
242
        annotations = unmodifiableList(annotations);
243
        annotationCache.put(classOrMethod, annotations);
244
      }
245
      return 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
260
  private static TesterRequirements incorporateRequirements(
261
      TesterRequirements requirements, TesterRequirements moreRequirements, Object source)
262
      throws ConflictingRequirementsException {
263
    Set<Feature<?>> presentFeatures = requirements.getPresentFeatures();
264
    Set<Feature<?>> absentFeatures = requirements.getAbsentFeatures();
265
    Set<Feature<?>> morePresentFeatures = moreRequirements.getPresentFeatures();
266
    Set<Feature<?>> moreAbsentFeatures = moreRequirements.getAbsentFeatures();
267
    checkConflict("absent", absentFeatures, "present", morePresentFeatures, source);
268
    checkConflict("present", presentFeatures, "absent", moreAbsentFeatures, source);
269
    presentFeatures.addAll(morePresentFeatures);
270
    absentFeatures.addAll(moreAbsentFeatures);
271
    return requirements;
272
  }
273

274
  // Used by incorporateRequirements() only
275
  private static void checkConflict(
276
      String earlierRequirement,
277
      Set<Feature<?>> earlierFeatures,
278
      String newRequirement,
279
      Set<Feature<?>> newFeatures,
280
      Object source)
281
      throws ConflictingRequirementsException {
282
    if (!disjoint(newFeatures, earlierFeatures)) {
283
      throw new ConflictingRequirementsException(
284
          String.format(
285
              Locale.ROOT,
286
              "Annotation requires to be %s features that earlier "
287
                  + "annotations required to be %s.",
288
              newRequirement,
289
              earlierRequirement),
290
          intersection(newFeatures, earlierFeatures),
291
          source);
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
301
  public static <T> Set<T> intersection(Set<? extends T> set1, Set<? extends T> set2) {
302
    Set<T> result = copyToSet(set1);
303
    result.retainAll(set2);
304
    return result;
305
  }
306

307
  private FeatureUtil() {}
308
}
309

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.