All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.testing.features.FeatureUtil Maven / Gradle / Ivy

Go to download

Guava testlib is a set of java classes used for more convenient unit testing - particularly to assist the tests for Guava itself.

There is a newer version: 33.3.1-jre
Show newest version
/*
 * 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 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 set1, Set set2) {
    Set result = Helpers.copyToSet(set1);
    result.retainAll(set2);
    return result;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy