content: /*
   * Copyright (C) 2014 Google, Inc.
   *
   * 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.aitorvs.autoparcel.internal.common;
  
  import static com.google.common.base.Preconditions.checkNotNull;
  import static com.google.common.base.Preconditions.checkState;
  import static com.google.common.collect.Iterables.getOnlyElement;
  import static javax.lang.model.type.TypeKind.ARRAY;
  import static javax.lang.model.type.TypeKind.DECLARED;
  import static javax.lang.model.type.TypeKind.EXECUTABLE;
  import static javax.lang.model.type.TypeKind.TYPEVAR;
  import static javax.lang.model.type.TypeKind.WILDCARD;
  
  import com.google.common.base.Equivalence;
  import com.google.common.base.Objects;
  import com.google.common.base.Optional;
  import com.google.common.base.Predicate;
  import com.google.common.base.Throwables;
  import com.google.common.collect.FluentIterable;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableSet;
  
  import java.lang.reflect.Method;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Set;
  
  import javax.lang.model.element.Element;
  import javax.lang.model.element.ElementKind;
  import javax.lang.model.element.ExecutableElement;
  import javax.lang.model.element.TypeElement;
  import javax.lang.model.element.TypeParameterElement;
  import javax.lang.model.element.VariableElement;
  import javax.lang.model.type.ArrayType;
  import javax.lang.model.type.DeclaredType;
  import javax.lang.model.type.ErrorType;
  import javax.lang.model.type.ExecutableType;
  import javax.lang.model.type.NoType;
  import javax.lang.model.type.NullType;
  import javax.lang.model.type.PrimitiveType;
  import javax.lang.model.type.TypeKind;
  import javax.lang.model.type.TypeMirror;
  import javax.lang.model.type.TypeVariable;
  import javax.lang.model.type.TypeVisitor;
  import javax.lang.model.type.WildcardType;
  import javax.lang.model.util.Elements;
  import javax.lang.model.util.SimpleTypeVisitor6;
  import javax.lang.model.util.Types;
  
  /**
   * Utilities related to {@link TypeMirror} instances.
   *
   * @author Gregory Kick
   * @since 2.0
   */
  public final class MoreTypes {
      private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = new Equivalence<TypeMirror>() {
          @Override
          protected boolean doEquivalent(TypeMirror a, TypeMirror b) {
              return MoreTypes.equal(a, b, ImmutableSet.<ComparedElements>of());
          }
  
          @Override
          protected int doHash(TypeMirror t) {
              return MoreTypes.hash(t, ImmutableSet.<Element>of());
          }
      };
  
      public static Equivalence<TypeMirror> equivalence() {
          return TYPE_EQUIVALENCE;
      }
  
      // So EQUAL_VISITOR can be a singleton, we maintain visiting state, in particular which types
      // have been seen already, in this object.
      // The logic for handling recursive types like Comparable<T extends Comparable<T>> is very tricky.
      // If we're not careful we'll end up with an infinite recursion. So we record the types that
      // we've already seen during the recursion, and if we see the same pair of types again we just
      // return true provisionally. But "the same pair of types" is itself poorly-defined. We can't
      // just say that it is an equal pair of TypeMirrors, because of course if we knew how to
      // determine that then we wouldn't need the complicated type visitor at all. On the other hand,
      // we can't say that it is an identical pair of TypeMirrors either, because there's no
      // guarantee that the TypeMirrors for the two Ts in Comparable<T extends Comparable<T>> will be
      // represented by the same object, and indeed with the Eclipse compiler they aren't. We could
      // compare the corresponding Elements, since equality is well-defined there, but that's not enough
      // either, because the Element for Set<Object> is the same as the one for Set<String>. So we
      // approximate by comparing the Elements and, if there are any type arguments, requiring them to
      // be identical. This may not be foolproof either but it is sufficient for all the cases we've
      // encountered so far.
      private static final class EqualVisitorParam {
          TypeMirror type;
          Set<ComparedElements> visiting;
      }
  
      private static class ComparedElements {
          final Element a;
          final ImmutableList<TypeMirror> aArguments;
          final Element b;
          final ImmutableList<TypeMirror> bArguments;
  
          ComparedElements(
                  Element a,
                  ImmutableList<TypeMirror> aArguments,
                  Element b,
                  ImmutableList<TypeMirror> bArguments) {
              this.a = a;
              this.aArguments = aArguments;
              this.b = b;
              this.bArguments = bArguments;
          }
  
          @Override
          public boolean equals(Object o) {
              if (o instanceof ComparedElements) {
                  ComparedElements that = (ComparedElements) o;
                  int nArguments = aArguments.size();
                  if (!this.a.equals(that.a)
                          || !this.b.equals(that.b)
                          || nArguments != bArguments.size()) {
                      // The arguments must be the same size, but we check anyway.
                      return false;
                  }
                  for (int i = 0; i < nArguments; i++) {
                      if (aArguments.get(i) != bArguments.get(i)) {
                          return false;
                      }
                  }
                  return true;
              } else {
                  return false;
              }
          }
  
          @Override
          public int hashCode() {
              return a.hashCode() * 31 + b.hashCode();
          }
      }
  
      private static final TypeVisitor<Boolean, EqualVisitorParam> EQUAL_VISITOR =
              new SimpleTypeVisitor6<Boolean, EqualVisitorParam>() {
                  @Override
                  protected Boolean defaultAction(TypeMirror a, EqualVisitorParam p) {
                      return a.getKind().equals(p.type.getKind());
                  }
  
                  @Override
                  public Boolean visitArray(ArrayType a, EqualVisitorParam p) {
                      if (p.type.getKind().equals(ARRAY)) {
                          ArrayType b = (ArrayType) p.type;
                          return equal(a.getComponentType(), b.getComponentType(), p.visiting);
                      }
                      return false;
                  }
  
                  @Override
                  public Boolean visitDeclared(DeclaredType a, EqualVisitorParam p) {
                      if (p.type.getKind().equals(DECLARED)) {
                          DeclaredType b = (DeclaredType) p.type;
                          Element aElement = a.asElement();
                          Element bElement = b.asElement();
                          Set<ComparedElements> newVisiting = visitingSetPlus(
                                  p.visiting, aElement, a.getTypeArguments(), bElement, b.getTypeArguments());
                          if (newVisiting.equals(p.visiting)) {
                              // We're already visiting this pair of elements.
                              // This can happen for example with Enum in Enum<E extends Enum<E>>. Return a
                              // provisional true value since if the Elements are not in fact equal the original
                              // visitor of Enum will discover that. We have to check both Elements being compared
                              // though to avoid missing the fact that one of the types being compared
                              // differs at exactly this point.
                              return true;
                          }
                          return aElement.equals(bElement)
                                  && equal(a.getEnclosingType(), a.getEnclosingType(), newVisiting)
                                  && equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
  
                      }
                      return false;
                  }
  
                  @Override
                  public Boolean visitError(ErrorType a, EqualVisitorParam p) {
                      return a.equals(p.type);
                  }
  
                  @Override
                  public Boolean visitExecutable(ExecutableType a, EqualVisitorParam p) {
                      if (p.type.getKind().equals(EXECUTABLE)) {
                          ExecutableType b = (ExecutableType) p.type;
                          return equalLists(a.getParameterTypes(), b.getParameterTypes(), p.visiting)
                                  && equal(a.getReturnType(), b.getReturnType(), p.visiting)
                                  && equalLists(a.getThrownTypes(), b.getThrownTypes(), p.visiting)
                                  && equalLists(a.getTypeVariables(), b.getTypeVariables(), p.visiting);
                      }
                      return false;
                  }
  
                  @Override
                  public Boolean visitTypeVariable(TypeVariable a, EqualVisitorParam p) {
                      if (p.type.getKind().equals(TYPEVAR)) {
                          TypeVariable b = (TypeVariable) p.type;
                          TypeParameterElement aElement = (TypeParameterElement) a.asElement();
                          TypeParameterElement bElement = (TypeParameterElement) b.asElement();
                          Set<ComparedElements> newVisiting = visitingSetPlus(p.visiting, aElement, bElement);
                          if (newVisiting.equals(p.visiting)) {
                              // We're already visiting this pair of elements.
                              // This can happen with our friend Eclipse when looking at <T extends Comparable<T>>.
                              // It incorrectly reports the upper bound of T as T itself.
                              return true;
                          }
                          // We use aElement.getBounds() instead of a.getUpperBound() to avoid having to deal with
                          // the different way intersection types (like <T extends Number & Comparable<T>>) are
                          // represented before and after Java 8. We do have an issue that this code may consider
                          // that <T extends Foo & Bar> is different from <T extends Bar & Foo>, but it's very
                          // hard to avoid that, and not likely to be much of a problem in practice.
                          return equalLists(aElement.getBounds(), bElement.getBounds(), newVisiting)
                                  && equal(a.getLowerBound(), b.getLowerBound(), newVisiting)
                                  && a.asElement().getSimpleName().equals(b.asElement().getSimpleName());
                      }
                      return false;
                  }
  
                  @Override
                  public Boolean visitWildcard(WildcardType a, EqualVisitorParam p) {
                      if (p.type.getKind().equals(WILDCARD)) {
                          WildcardType b = (WildcardType) p.type;
                          return equal(a.getExtendsBound(), b.getExtendsBound(), p.visiting)
                                  && equal(a.getSuperBound(), b.getSuperBound(), p.visiting);
                      }
                      return false;
                  }
  
                  @Override
                  public Boolean visitUnknown(TypeMirror a, EqualVisitorParam p) {
                      throw new UnsupportedOperationException();
                  }
  
                  private Set<ComparedElements> visitingSetPlus(
                          Set<ComparedElements> visiting, Element a, Element b) {
                      ImmutableList<TypeMirror> noArguments = ImmutableList.of();
                      return visitingSetPlus(visiting, a, noArguments, b, noArguments);
                  }
  
                  private Set<ComparedElements> visitingSetPlus(
                          Set<ComparedElements> visiting,
                          Element a, List<? extends TypeMirror> aArguments,
                          Element b, List<? extends TypeMirror> bArguments) {
                      ComparedElements comparedElements =
                              new ComparedElements(
                                      a, ImmutableList.<TypeMirror>copyOf(aArguments),
                                      b, ImmutableList.<TypeMirror>copyOf(bArguments));
                      Set<ComparedElements> newVisiting = new HashSet<ComparedElements>(visiting);
                      newVisiting.add(comparedElements);
                      return newVisiting;
                  }
              };
  
      private static final Class<?> INTERSECTION_TYPE;
      private static final Method GET_BOUNDS;
  
      static {
          Class<?> c;
          Method m;
          try {
              c = Class.forName("javax.lang.model.type.IntersectionType");
              m = c.getMethod("getBounds");
          } catch (Exception e) {
              c = null;
              m = null;
          }
          INTERSECTION_TYPE = c;
          GET_BOUNDS = m;
      }
  
      private static boolean equal(TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) {
          // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can
          // assume that if it does return true then the types are equal. This check also avoids getting
          // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in
          // <K extends Comparable<K>> is a distinct but equal K.
          // The javac implementation of ExecutableType, at least in some versions, does not take thrown
          // exceptions into account in its equals implementation, so avoid this optimization for
          // ExecutableType.
          if (Objects.equal(a, b) && !(a instanceof ExecutableType)) {
              return true;
          }
          EqualVisitorParam p = new EqualVisitorParam();
          p.type = b;
          p.visiting = visiting;
          if (INTERSECTION_TYPE != null) {
              if (isIntersectionType(a)) {
                  return equalIntersectionTypes(a, b, visiting);
              } else if (isIntersectionType(b)) {
                  return false;
              }
          }
          return (a == b) || (a != null && b != null && a.accept(EQUAL_VISITOR, p));
      }
  
      private static boolean isIntersectionType(TypeMirror t) {
          return t != null && t.getKind().name().equals("INTERSECTION");
      }
  
      // The representation of an intersection type, as in <T extends Number & Comparable<T>>, changed
      // between Java 7 and Java 8. In Java 7 it was modeled as a fake DeclaredType, and our logic
      // for DeclaredType does the right thing. In Java 8 it is modeled as a new type IntersectionType.
      // In order for our code to run on Java 7 (and Java 6) we can't even mention IntersectionType,
      // so we can't override visitIntersectionType(IntersectionType). Instead, we discover through
      // reflection whether IntersectionType exists, and if it does we extract the bounds of the
      // intersection ((Number, Comparable<T>) in the example) and compare them directly.
      @SuppressWarnings("unchecked")
      private static boolean equalIntersectionTypes(
              TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) {
          if (!isIntersectionType(b)) {
              return false;
          }
          List<? extends TypeMirror> aBounds;
          List<? extends TypeMirror> bBounds;
          try {
              aBounds = (List<? extends TypeMirror>) GET_BOUNDS.invoke(a);
              bBounds = (List<? extends TypeMirror>) GET_BOUNDS.invoke(b);
          } catch (Exception e) {
              throw Throwables.propagate(e);
          }
          return equalLists(aBounds, bBounds, visiting);
      }
  
      private static boolean equalLists(
              List<? extends TypeMirror> a, List<? extends TypeMirror> b,
              Set<ComparedElements> visiting) {
          int size = a.size();
          if (size != b.size()) {
              return false;
          }
          // Use iterators in case the Lists aren't RandomAccess
          Iterator<? extends TypeMirror> aIterator = a.iterator();
          Iterator<? extends TypeMirror> bIterator = b.iterator();
          while (aIterator.hasNext()) {
              if (!bIterator.hasNext()) {
                  return false;
              }
              TypeMirror nextMirrorA = aIterator.next();
              TypeMirror nextMirrorB = bIterator.next();
              if (!equal(nextMirrorA, nextMirrorB, visiting)) {
                  return false;
              }
          }
          return !aIterator.hasNext();
      }
  
      private static final int HASH_SEED = 17;
      private static final int HASH_MULTIPLIER = 31;
  
      private static final TypeVisitor<Integer, Set<Element>> HASH_VISITOR =
              new SimpleTypeVisitor6<Integer, Set<Element>>() {
                  int hashKind(int seed, TypeMirror t) {
                      int result = seed * HASH_MULTIPLIER;
                      result += t.getKind().hashCode();
                      return result;
                  }
  
                  @Override
                  protected Integer defaultAction(TypeMirror e, Set<Element> visiting) {
                      return hashKind(HASH_SEED, e);
                  }
  
                  @Override
                  public Integer visitArray(ArrayType t, Set<Element> visiting) {
                      int result = hashKind(HASH_SEED, t);
                      result *= HASH_MULTIPLIER;
                      result += t.getComponentType().accept(this, visiting);
                      return result;
                  }
  
                  @Override
                  public Integer visitDeclared(DeclaredType t, Set<Element> visiting) {
                      Element element = t.asElement();
                      if (visiting.contains(element)) {
                          return 0;
                      }
                      Set<Element> newVisiting = new HashSet<Element>(visiting);
                      newVisiting.add(element);
                      int result = hashKind(HASH_SEED, t);
                      result *= HASH_MULTIPLIER;
                      result += t.asElement().hashCode();
                      result *= HASH_MULTIPLIER;
                      result += t.getEnclosingType().accept(this, newVisiting);
                      result *= HASH_MULTIPLIER;
                      result += hashList(t.getTypeArguments(), newVisiting);
                      return result;
                  }
  
                  @Override
                  public Integer visitExecutable(ExecutableType t, Set<Element> visiting) {
                      int result = hashKind(HASH_SEED, t);
                      result *= HASH_MULTIPLIER;
                      result += hashList(t.getParameterTypes(), visiting);
                      result *= HASH_MULTIPLIER;
                      result += t.getReturnType().accept(this, visiting);
                      result *= HASH_MULTIPLIER;
                      result += hashList(t.getThrownTypes(), visiting);
                      result *= HASH_MULTIPLIER;
                      result += hashList(t.getTypeVariables(), visiting);
                      return result;
                  }
  
                  @Override
                  public Integer visitTypeVariable(TypeVariable t, Set<Element> visiting) {
                      int result = hashKind(HASH_SEED, t);
                      result *= HASH_MULTIPLIER;
                      result += t.getLowerBound().accept(this, visiting);
                      TypeParameterElement element = (TypeParameterElement) t.asElement();
                      for (TypeMirror bound : element.getBounds()) {
                          result *= HASH_MULTIPLIER;
                          result += bound.accept(this, visiting);
                      }
                      return result;
                  }
  
                  @Override
                  public Integer visitWildcard(WildcardType t, Set<Element> visiting) {
                      int result = hashKind(HASH_SEED, t);
                      result *= HASH_MULTIPLIER;
                      result +=
                              (t.getExtendsBound() == null) ? 0 : t.getExtendsBound().accept(this, visiting);
                      result *= HASH_MULTIPLIER;
                      result += (t.getSuperBound() == null) ? 0 : t.getSuperBound().accept(this, visiting);
                      return result;
                  }
  
                  @Override
                  public Integer visitUnknown(TypeMirror t, Set<Element> visiting) {
                      throw new UnsupportedOperationException();
                  }
              };
  
      private static int hashList(List<? extends TypeMirror> mirrors, Set<Element> visiting) {
          int result = HASH_SEED;
          for (TypeMirror mirror : mirrors) {
              result *= HASH_MULTIPLIER;
              result += hash(mirror, visiting);
          }
          return result;
      }
  
      private static int hash(TypeMirror mirror, Set<Element> visiting) {
          return mirror == null ? 0 : mirror.accept(HASH_VISITOR, visiting);
      }
  
      /**
       * Returns the set of {@linkplain TypeElement types} that are referenced by the given
       * {@link TypeMirror}.
       */
      public static ImmutableSet<TypeElement> referencedTypes(TypeMirror type) {
          checkNotNull(type);
          ImmutableSet.Builder<TypeElement> elements = ImmutableSet.builder();
          type.accept(new SimpleTypeVisitor6<Void, ImmutableSet.Builder<TypeElement>>() {
              @Override
              public Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) {
                  t.getComponentType().accept(this, p);
                  return null;
              }
  
              @Override
              public Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) {
                  p.add(MoreElements.asType(t.asElement()));
                  for (TypeMirror typeArgument : t.getTypeArguments()) {
                      typeArgument.accept(this, p);
                  }
                  return null;
              }
  
              @Override
              public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) {
                  t.getLowerBound().accept(this, p);
                  t.getUpperBound().accept(this, p);
                  return null;
              }
  
              @Override
              public Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) {
                  TypeMirror extendsBound = t.getExtendsBound();
                  if (extendsBound != null) {
                      extendsBound.accept(this, p);
                  }
                  TypeMirror superBound = t.getSuperBound();
                  if (superBound != null) {
                      superBound.accept(this, p);
                  }
                  return null;
              }
          }, elements);
          return elements.build();
      }
  
      /**
       * An alternate implementation of {@link Types#asElement} that does not require a {@link Types}
       * instance with the notable difference that it will throw {@link IllegalArgumentException}
       * instead of returning null if the {@link TypeMirror} can not be converted to an {@link Element}.
       *
       * @throws NullPointerException     if {@code typeMirror} is {@code null}
       * @throws IllegalArgumentException if {@code typeMirror} cannot be converted to an
       *                                  {@link Element}
       */
      public static Element asElement(TypeMirror typeMirror) {
          return typeMirror.accept(AS_ELEMENT_VISITOR, null);
      }
  
      private static final TypeVisitor<Element, Void> AS_ELEMENT_VISITOR =
              new SimpleTypeVisitor6<Element, Void>() {
                  @Override
                  protected Element defaultAction(TypeMirror e, Void p) {
                      throw new IllegalArgumentException(e + "cannot be converted to an Element");
                  }
  
                  @Override
                  public Element visitDeclared(DeclaredType t, Void p) {
                      return t.asElement();
                  }
  
                  @Override
                  public Element visitError(ErrorType t, Void p) {
                      return t.asElement();
                  }
  
                  @Override
                  public Element visitTypeVariable(TypeVariable t, Void p) {
                      return t.asElement();
                  }
              };
  
      // TODO(gak): consider removing these two methods as they're pretty trivial now
      public static TypeElement asTypeElement(TypeMirror mirror) {
          return MoreElements.asType(asElement(mirror));
      }
  
      public static ImmutableSet<TypeElement> asTypeElements(Iterable<? extends TypeMirror> mirrors) {
          checkNotNull(mirrors);
          ImmutableSet.Builder<TypeElement> builder = ImmutableSet.builder();
          for (TypeMirror mirror : mirrors) {
              builder.add(asTypeElement(mirror));
          }
          return builder.build();
      }
  
      /**
       * Returns a {@link ArrayType} if the {@link TypeMirror} represents a primitive array or
       * throws an {@link IllegalArgumentException}.
       */
      public static ArrayType asArray(TypeMirror maybeArrayType) {
          return maybeArrayType.accept(new CastingTypeVisitor<ArrayType>() {
              @Override
              public ArrayType visitArray(ArrayType type, String ignore) {
                  return type;
              }
          }, "primitive array");
      }
  
      /**
       * Returns a {@link DeclaredType} if the {@link TypeMirror} represents a declared type such
       * as a class, interface, union/compound, or enum or throws an {@link IllegalArgumentException}.
       */
      public static DeclaredType asDeclared(TypeMirror maybeDeclaredType) {
          return maybeDeclaredType.accept(new CastingTypeVisitor<DeclaredType>() {
              @Override
              public DeclaredType visitDeclared(DeclaredType type, String ignored) {
                  return type;
              }
          }, "declared type");
      }
  
      /**
       * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
       * as may result from missing code, or bad compiles or throws an {@link IllegalArgumentException}.
       */
      public static ErrorType asError(TypeMirror maybeErrorType) {
          return maybeErrorType.accept(new CastingTypeVisitor<ErrorType>() {
              @Override
              public ErrorType visitError(ErrorType type, String p) {
                  return type;
              }
          }, "error type");
      }
  
      /**
       * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
       * as a method, constructor, or initializer or throws an {@link IllegalArgumentException}.
       */
      public static ExecutableType asExecutable(TypeMirror maybeExecutableType) {
          return maybeExecutableType.accept(new CastingTypeVisitor<ExecutableType>() {
              @Override
              public ExecutableType visitExecutable(ExecutableType type, String p) {
                  return type;
              }
          }, "executable type");
      }
  
      /**
       * Returns a {@link NoType} if the {@link TypeMirror} represents an non-type such
       * as void, or package, etc. or throws an {@link IllegalArgumentException}.
       */
      public static NoType asNoType(TypeMirror maybeNoType) {
          return maybeNoType.accept(new CastingTypeVisitor<NoType>() {
              @Override
              public NoType visitNoType(NoType noType, String p) {
                  return noType;
              }
          }, "non-type");
      }
  
      /**
       * Returns a {@link NullType} if the {@link TypeMirror} represents the null type
       * or throws an {@link IllegalArgumentException}.
       */
      public static NullType asNullType(TypeMirror maybeNullType) {
          return maybeNullType.accept(new CastingTypeVisitor<NullType>() {
              @Override
              public NullType visitNull(NullType nullType, String p) {
                  return nullType;
              }
          }, "null");
      }
  
      /**
       * Returns a {@link PrimitiveType} if the {@link TypeMirror} represents a primitive type
       * or throws an {@link IllegalArgumentException}.
       */
      public static PrimitiveType asPrimitiveType(TypeMirror maybePrimitiveType) {
          return maybePrimitiveType.accept(new CastingTypeVisitor<PrimitiveType>() {
              @Override
              public PrimitiveType visitPrimitive(PrimitiveType type, String p) {
                  return type;
              }
          }, "primitive type");
      }
  
      //
      // visitUnionType would go here, but it is a 1.7 API.
      //
  
      /**
       * Returns a {@link TypeVariable} if the {@link TypeMirror} represents a type variable
       * or throws an {@link IllegalArgumentException}.
       */
      public static TypeVariable asTypeVariable(TypeMirror maybeTypeVariable) {
          return maybeTypeVariable.accept(new CastingTypeVisitor<TypeVariable>() {
              @Override
              public TypeVariable visitTypeVariable(TypeVariable type, String p) {
                  return type;
              }
          }, "type variable");
      }
  
      /**
       * Returns a {@link WildcardType} if the {@link TypeMirror} represents a wildcard type
       * or throws an {@link IllegalArgumentException}.
       */
      public static WildcardType asWildcard(WildcardType maybeWildcardType) {
          return maybeWildcardType.accept(new CastingTypeVisitor<WildcardType>() {
              @Override
              public WildcardType visitWildcard(WildcardType type, String p) {
                  return type;
              }
          }, "wildcard type");
      }
  
      /**
       * Returns true if the raw type underlying the given {@link TypeMirror} represents a type that can
       * be referenced by a {@link Class}. If this returns true, then {@link #isTypeOf} is guaranteed to
       * not throw.
       */
      public static boolean isType(TypeMirror type) {
          return type.accept(new SimpleTypeVisitor6<Boolean, Void>() {
              @Override
              protected Boolean defaultAction(TypeMirror type, Void ignored) {
                  return false;
              }
  
              @Override
              public Boolean visitNoType(NoType noType, Void p) {
                  return noType.getKind().equals(TypeKind.VOID);
              }
  
              @Override
              public Boolean visitPrimitive(PrimitiveType type, Void p) {
                  return true;
              }
  
              @Override
              public Boolean visitArray(ArrayType array, Void p) {
                  return true;
              }
  
              @Override
              public Boolean visitDeclared(DeclaredType type, Void ignored) {
                  return MoreElements.isType(type.asElement());
              }
          }, null);
      }
  
      /**
       * Returns true if the raw type underlying the given {@link TypeMirror} represents the
       * same raw type as the given {@link Class} and throws an IllegalArgumentException if the
       * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}
       */
      public static boolean isTypeOf(final Class<?> clazz, TypeMirror type) {
          checkNotNull(clazz);
          return type.accept(new SimpleTypeVisitor6<Boolean, Void>() {
              @Override
              protected Boolean defaultAction(TypeMirror type, Void ignored) {
                  throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
              }
  
              @Override
              public Boolean visitNoType(NoType noType, Void p) {
                  if (noType.getKind().equals(TypeKind.VOID)) {
                      return clazz.equals(Void.TYPE);
                  }
                  throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>.");
              }
  
              @Override
              public Boolean visitPrimitive(PrimitiveType type, Void p) {
                  switch (type.getKind()) {
                      case BOOLEAN:
                          return clazz.equals(Boolean.TYPE);
                      case BYTE:
                          return clazz.equals(Byte.TYPE);
                      case CHAR:
                          return clazz.equals(Character.TYPE);
                      case DOUBLE:
                          return clazz.equals(Double.TYPE);
                      case FLOAT:
                          return clazz.equals(Float.TYPE);
                      case INT:
                          return clazz.equals(Integer.TYPE);
                      case LONG:
                          return clazz.equals(Long.TYPE);
                      case SHORT:
                          return clazz.equals(Short.TYPE);
                      default:
                          throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
                  }
              }
  
              @Override
              public Boolean visitArray(ArrayType array, Void p) {
                  return clazz.isArray()
                          && isTypeOf(clazz.getComponentType(), array.getComponentType());
              }
  
              @Override
              public Boolean visitDeclared(DeclaredType type, Void ignored) {
                  TypeElement typeElement;
                  try {
                      typeElement = MoreElements.asType(type.asElement());
                  } catch (IllegalArgumentException iae) {
                      throw new IllegalArgumentException(type + " does not represent a class or interface.");
                  }
                  return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
              }
          }, null);
      }
  
      /**
       * Returns the non-object superclass of the type with the proper type parameters.
       * An absent Optional is returned if there is no non-Object superclass.
       */
      public static Optional<DeclaredType> nonObjectSuperclass(final Types types, Elements elements,
                                                               DeclaredType type) {
          checkNotNull(types);
          checkNotNull(elements);
          checkNotNull(type);
  
          final TypeMirror objectType =
                  elements.getTypeElement(Object.class.getCanonicalName()).asType();
          // It's guaranteed there's only a single CLASS superclass because java doesn't have multiple
          // class inheritance.
          TypeMirror superclass = getOnlyElement(FluentIterable.from(types.directSupertypes(type))
                  .filter(new Predicate<TypeMirror>() {
                      @Override
                      public boolean apply(TypeMirror input) {
                          return input.getKind().equals(TypeKind.DECLARED)
                                  && (MoreElements.asType(
                                  MoreTypes.asDeclared(input).asElement())).getKind().equals(ElementKind.CLASS)
                                  && !types.isSameType(objectType, input);
                      }
                  }), null);
          return superclass != null
                  ? Optional.of(MoreTypes.asDeclared(superclass))
                  : Optional.<DeclaredType>absent();
      }
  
      /**
       * Resolves a {@link VariableElement} parameter to a method or constructor based on the given
       * container, or a member of a class. For parameters to a method or constructor, the variable's
       * enclosing element must be a supertype of the container type. For example, given a
       * {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
       * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
       */
      public static TypeMirror asMemberOf(Types types, DeclaredType container,
                                          VariableElement variable) {
          if (variable.getKind().equals(ElementKind.PARAMETER)) {
              ExecutableElement methodOrConstructor =
                      MoreElements.asExecutable(variable.getEnclosingElement());
              ExecutableType resolvedMethodOrConstructor = MoreTypes.asExecutable(
                      types.asMemberOf(container, methodOrConstructor));
              List<? extends VariableElement> parameters = methodOrConstructor.getParameters();
              List<? extends TypeMirror> parameterTypes =
                      resolvedMethodOrConstructor.getParameterTypes();
              checkState(parameters.size() == parameterTypes.size());
              for (int i = 0; i < parameters.size(); i++) {
                  // We need to capture the parameter type of the variable we're concerned about,
                  // for later printing.  This is the only way to do it since we can't use
                  // types.asMemberOf on variables of methods.
                  if (parameters.get(i).equals(variable)) {
                      return parameterTypes.get(i);
                  }
              }
              throw new IllegalStateException("Could not find variable: " + variable);
          } else {
              return types.asMemberOf(container, variable);
          }
      }
  
      private static class CastingTypeVisitor<T> extends SimpleTypeVisitor6<T, String> {
          @Override
          protected T defaultAction(TypeMirror e, String label) {
              throw new IllegalArgumentException(e + " does not represent a " + label);
          }
      }
  
      private MoreTypes() {
      }
  }