guava

Форк
0
301 строка · 12.1 Кб
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 com.google.errorprone.annotations.CanIgnoreReturnValue;
22
import java.lang.annotation.Annotation;
23
import java.lang.reflect.AnnotatedElement;
24
import java.lang.reflect.Method;
25
import java.util.ArrayDeque;
26
import java.util.ArrayList;
27
import java.util.Collections;
28
import java.util.HashMap;
29
import java.util.LinkedHashSet;
30
import java.util.List;
31
import java.util.Locale;
32
import java.util.Map;
33
import java.util.Queue;
34
import java.util.Set;
35

36
/**
37
 * Utilities for collecting and validating tester requirements from annotations.
38
 *
39
 * @author George van den Driessche
40
 */
41
@GwtIncompatible
42
public class FeatureUtil {
43
  /** A cache of annotated objects (typically a Class or Method) to its set of annotations. */
44
  private static Map<AnnotatedElement, List<Annotation>> annotationCache = new HashMap<>();
45

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

49
  private static final Map<Method, TesterRequirements> methodTesterRequirementsCache =
50
      new HashMap<>();
51

52
  /**
53
   * Given a set of features, add to it all the features directly or indirectly implied by any of
54
   * them, and return it.
55
   *
56
   * @param features the set of features to expand
57
   * @return the same set of features, expanded with all implied features
58
   */
59
  @CanIgnoreReturnValue
60
  public static Set<Feature<?>> addImpliedFeatures(Set<Feature<?>> features) {
61
    Queue<Feature<?>> queue = new ArrayDeque<>(features);
62
    while (!queue.isEmpty()) {
63
      Feature<?> feature = queue.remove();
64
      for (Feature<?> implied : feature.getImpliedFeatures()) {
65
        if (features.add(implied)) {
66
          queue.add(implied);
67
        }
68
      }
69
    }
70
    return features;
71
  }
72

73
  /**
74
   * Given a set of features, return a new set of all features directly or indirectly implied by any
75
   * of them.
76
   *
77
   * @param features the set of features whose implications to find
78
   * @return the implied set of features
79
   */
80
  public static Set<Feature<?>> impliedFeatures(Set<Feature<?>> features) {
81
    Set<Feature<?>> impliedSet = new LinkedHashSet<>();
82
    Queue<Feature<?>> queue = new ArrayDeque<>(features);
83
    while (!queue.isEmpty()) {
84
      Feature<?> feature = queue.remove();
85
      for (Feature<?> implied : feature.getImpliedFeatures()) {
86
        if (!features.contains(implied) && impliedSet.add(implied)) {
87
          queue.add(implied);
88
        }
89
      }
90
    }
91
    return impliedSet;
92
  }
93

94
  /**
95
   * Get the full set of requirements for a tester class.
96
   *
97
   * @param testerClass a tester class
98
   * @return all the constraints implicitly or explicitly required by the class or any of its
99
   *     superclasses.
100
   * @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
101
   */
102
  public static TesterRequirements getTesterRequirements(Class<?> testerClass)
103
      throws ConflictingRequirementsException {
104
    synchronized (classTesterRequirementsCache) {
105
      TesterRequirements requirements = classTesterRequirementsCache.get(testerClass);
106
      if (requirements == null) {
107
        requirements = buildTesterRequirements(testerClass);
108
        classTesterRequirementsCache.put(testerClass, requirements);
109
      }
110
      return requirements;
111
    }
112
  }
113

114
  /**
115
   * Get the full set of requirements for a tester class.
116
   *
117
   * @param testerMethod a test method of a tester class
118
   * @return all the constraints implicitly or explicitly required by the method, its declaring
119
   *     class, or any of its superclasses.
120
   * @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
121
   */
122
  public static TesterRequirements getTesterRequirements(Method testerMethod)
123
      throws ConflictingRequirementsException {
124
    synchronized (methodTesterRequirementsCache) {
125
      TesterRequirements requirements = methodTesterRequirementsCache.get(testerMethod);
126
      if (requirements == null) {
127
        requirements = buildTesterRequirements(testerMethod);
128
        methodTesterRequirementsCache.put(testerMethod, requirements);
129
      }
130
      return requirements;
131
    }
132
  }
133

134
  /**
135
   * Construct the full set of requirements for a tester class.
136
   *
137
   * @param testerClass a tester class
138
   * @return all the constraints implicitly or explicitly required by the class or any of its
139
   *     superclasses.
140
   * @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
141
   */
142
  static TesterRequirements buildTesterRequirements(Class<?> testerClass)
143
      throws ConflictingRequirementsException {
144
    TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerClass);
145
    Class<?> baseClass = testerClass.getSuperclass();
146
    if (baseClass == null) {
147
      return declaredRequirements;
148
    } else {
149
      TesterRequirements clonedBaseRequirements =
150
          new TesterRequirements(getTesterRequirements(baseClass));
151
      return incorporateRequirements(clonedBaseRequirements, declaredRequirements, testerClass);
152
    }
153
  }
154

155
  /**
156
   * Construct the full set of requirements for a tester method.
157
   *
158
   * @param testerMethod a test method of a tester class
159
   * @return all the constraints implicitly or explicitly required by the method, its declaring
160
   *     class, or any of its superclasses.
161
   * @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
162
   */
163
  static TesterRequirements buildTesterRequirements(Method testerMethod)
164
      throws ConflictingRequirementsException {
165
    TesterRequirements clonedClassRequirements =
166
        new TesterRequirements(getTesterRequirements(testerMethod.getDeclaringClass()));
167
    TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerMethod);
168
    return incorporateRequirements(clonedClassRequirements, declaredRequirements, testerMethod);
169
  }
170

171
  /**
172
   * Find all the constraints explicitly or implicitly specified by a single tester annotation.
173
   *
174
   * @param testerAnnotation a tester annotation
175
   * @return the requirements specified by the annotation
176
   * @throws ConflictingRequirementsException if the requirements are mutually inconsistent.
177
   */
178
  private static TesterRequirements buildTesterRequirements(Annotation testerAnnotation)
179
      throws ConflictingRequirementsException {
180
    Class<? extends Annotation> annotationClass = testerAnnotation.annotationType();
181
    Feature<?>[] presentFeatures;
182
    Feature<?>[] absentFeatures;
183
    try {
184
      presentFeatures = (Feature[]) annotationClass.getMethod("value").invoke(testerAnnotation);
185
      absentFeatures = (Feature[]) annotationClass.getMethod("absent").invoke(testerAnnotation);
186
    } catch (Exception e) {
187
      throw new IllegalArgumentException("Error extracting features from tester annotation.", e);
188
    }
189
    Set<Feature<?>> allPresentFeatures =
190
        addImpliedFeatures(Helpers.<Feature<?>>copyToSet(presentFeatures));
191
    Set<Feature<?>> allAbsentFeatures =
192
        addImpliedFeatures(Helpers.<Feature<?>>copyToSet(absentFeatures));
193
    if (!Collections.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 = Collections.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 (!Collections.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
  /** Construct a new {@link java.util.Set} that is the intersection of the given sets. */
296
  public static <T> Set<T> intersection(Set<? extends T> set1, Set<? extends T> set2) {
297
    Set<T> result = Helpers.<T>copyToSet(set1);
298
    result.retainAll(set2);
299
    return result;
300
  }
301
}
302

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

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

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

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