/**
   * If the checker class is annotated with {@link TypeQualifiers}, return an immutable set with the
   * same set of classes as the annotation. If the class is not so annotated, return an empty set.
   *
   * <p>Subclasses may override this method to return an immutable set of their supported type
   * qualifiers.
   *
   * @return the type qualifiers supported this processor, or an empty set if none
   * @see TypeQualifiers
   */
  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
    Class<?> classType = this.getClass();
    TypeQualifiers typeQualifiersAnnotation = classType.getAnnotation(TypeQualifiers.class);
    if (typeQualifiersAnnotation == null) return Collections.emptySet();

    Set<Class<? extends Annotation>> typeQualifiers = new HashSet<Class<? extends Annotation>>();
    for (Class<? extends Annotation> qualifier : typeQualifiersAnnotation.value()) {
      typeQualifiers.add(qualifier);
    }
    return Collections.unmodifiableSet(typeQualifiers);
  }
  /**
   * Invokes the constructor belonging to the class named by {@code name} having the given parameter
   * types on the given arguments. Returns {@code null} if the class cannot be found, or the
   * constructor does not exist or cannot be invoked on the given arguments.
   *
   * @param <T> the type to which the constructor belongs
   * @param name the name of the class to which the constructor belongs
   * @param paramTypes the types of the constructor's parameters
   * @param args the arguments on which to invoke the constructor
   * @return the result of the constructor invocation on {@code args}, or null if the constructor
   *     does not exist or could not be invoked
   */
  @SuppressWarnings("unchecked")
  public static <T> T invokeConstructorFor(String name, Class<?>[] paramTypes, Object[] args) {

    // Load the class.
    Class<T> cls = null;
    try {
      cls = (Class<T>) Class.forName(name);
    } catch (Exception e) {
      // no class is found, simply return null
      return null;
    }

    assert cls != null : "reflectively loading " + name + " failed";

    // Invoke the constructor.
    try {
      Constructor<T> ctor = cls.getConstructor(paramTypes);
      return ctor.newInstance(args);
    } catch (Throwable t) {
      if (t instanceof InvocationTargetException) {
        Throwable err = t.getCause();
        String msg;
        if (err instanceof CheckerError) {
          msg = err.getMessage();
        } else {
          msg = err.toString();
        }
        SourceChecker.errorAbort(
            "InvocationTargetException when invoking constructor for class "
                + name
                + "; Underlying cause: "
                + msg,
            t);
      } else {
        SourceChecker.errorAbort(
            "Unexpected "
                + t.getClass().getSimpleName()
                + " for "
                + "class "
                + name
                + " when invoking the constructor; parameter types: "
                + Arrays.toString(paramTypes),
            // + " and args: " + Arrays.toString(args),
            t);
      }
      return null; // dead code
    }
  }
  /**
   * Returns the type qualifier hierarchy graph to be used by this processor.
   *
   * <p>The implementation builds the type qualifier hierarchy for the {@link
   * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current
   * implementation returns an instance of {@code GraphQualifierHierarchy}.
   *
   * <p>Subclasses may override this method to express any relationships that cannot be inferred
   * using meta-annotations (e.g. due to lack of meta-annotations).
   *
   * @return an annotation relation tree representing the supported qualifiers
   */
  protected QualifierHierarchy createQualifierHierarchy() {
    MultiGraphQualifierHierarchy.MultiGraphFactory factory = this.createQualifierHierarchyFactory();
    Elements elements = processingEnv.getElementUtils();

    for (Class<? extends Annotation> typeQualifier : getSupportedTypeQualifiers()) {
      AnnotationMirror typeQualifierAnno = AnnotationUtils.fromClass(elements, typeQualifier);
      assert typeQualifierAnno != null : "Loading annotation \"" + typeQualifier + "\" failed!";
      factory.addQualifier(typeQualifierAnno);
      // Polymorphic qualifiers can't declare their supertypes.
      // An error is raised if one is present.
      if (typeQualifier.getAnnotation(PolymorphicQualifier.class) != null) {
        if (typeQualifier.getAnnotation(SubtypeOf.class) != null) {
          // This is currently not supported. At some point we might add
          // polymorphic qualifiers with upper and lower bounds.
          errorAbort(
              "BaseTypeChecker: "
                  + typeQualifier
                  + " is polymorphic and specifies super qualifiers. "
                  + "Remove the @checkers.quals.SubtypeOf or @checkers.quals.PolymorphicQualifier annotation from it.");
        }
        continue;
      }
      if (typeQualifier.getAnnotation(SubtypeOf.class) == null) {
        errorAbort(
            "BaseTypeChecker: "
                + typeQualifier
                + " does not specify its super qualifiers. "
                + "Add an @checkers.quals.SubtypeOf annotation to it.");
      }
      Class<? extends Annotation>[] superQualifiers =
          typeQualifier.getAnnotation(SubtypeOf.class).value();
      for (Class<? extends Annotation> superQualifier : superQualifiers) {
        AnnotationMirror superAnno = null;
        superAnno = AnnotationUtils.fromClass(elements, superQualifier);
        factory.addSubtype(typeQualifierAnno, superAnno);
      }
    }

    QualifierHierarchy hierarchy = factory.build();
    if (hierarchy.getTypeQualifiers().size() < 1) {
      errorAbort(
          "BaseTypeChecker: invalid qualifier hierarchy: hierarchy requires at least one annotation: "
              + hierarchy.getTypeQualifiers());
    }

    return hierarchy;
  }
  /**
   * Returns the appropriate visitor that type checks the compilation unit according to the type
   * system rules.
   *
   * <p>This implementation uses the checker naming convention to create the appropriate visitor. If
   * no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively invokes
   * the constructor that accepts this checker and the compilation unit tree (in that order) as
   * arguments.
   *
   * <p>Subclasses have to override this method to create the appropriate visitor if they do not
   * follow the checker naming convention.
   *
   * @param root the compilation unit currently being visited
   * @return the type-checking visitor
   */
  @Override
  protected SourceVisitor<?, ?> createSourceVisitor(CompilationUnitTree root) {

    // Try to reflectively load the visitor.
    Class<?> checkerClass = this.getClass();

    while (checkerClass != BaseTypeChecker.class) {
      final String classToLoad =
          checkerClass.getName().replace("Checker", "Visitor").replace("Subchecker", "Visitor");
      BaseTypeVisitor<?> result =
          invokeConstructorFor(
              classToLoad,
              new Class<?>[] {checkerClass, CompilationUnitTree.class},
              new Object[] {this, root});
      if (result != null) return result;
      checkerClass = checkerClass.getSuperclass();
    }

    // If a visitor couldn't be loaded reflectively, return the default.
    return new BaseTypeVisitor<BaseTypeChecker>(this, root);
  }
  /**
   * Constructs an instance of the appropriate type factory for the implemented type system.
   *
   * <p>The default implementation uses the checker naming convention to create the appropriate type
   * factory. If no factory is found, it returns {@link BasicAnnotatedTypeFactory}. It reflectively
   * invokes the constructor that accepts this checker and compilation unit tree (in that order) as
   * arguments.
   *
   * <p>Subclasses have to override this method to create the appropriate visitor if they do not
   * follow the checker naming convention.
   *
   * @param root the currently visited compilation unit
   * @return the appropriate type factory
   */
  @Override
  public AnnotatedTypeFactory createFactory(CompilationUnitTree root) {

    // Try to reflectively load the type factory.
    Class<?> checkerClass = this.getClass();
    while (checkerClass != BaseTypeChecker.class) {
      final String classToLoad =
          checkerClass
              .getName()
              .replace("Checker", "AnnotatedTypeFactory")
              .replace("Subchecker", "AnnotatedTypeFactory");

      AnnotatedTypeFactory result =
          invokeConstructorFor(
              classToLoad,
              new Class<?>[] {checkerClass, CompilationUnitTree.class},
              new Object[] {this, root});
      if (result != null) return result;
      checkerClass = checkerClass.getSuperclass();
    }
    return new BasicAnnotatedTypeFactory<BaseTypeChecker>(this, root);
  }