content: /*
   * Copyright (C) 2013 Google, Inc.
   * Copyright (C) 2013 Square, 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 com.google.common.annotations.Beta;
  import com.google.common.base.Optional;
  import com.google.common.base.Predicate;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.LinkedHashMultimap;
  import com.google.common.collect.SetMultimap;
  
  import java.lang.annotation.Annotation;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Set;
  
  import javax.lang.model.element.AnnotationMirror;
  import javax.lang.model.element.Element;
  import javax.lang.model.element.ElementVisitor;
  import javax.lang.model.element.ExecutableElement;
  import javax.lang.model.element.Modifier;
  import javax.lang.model.element.PackageElement;
  import javax.lang.model.element.TypeElement;
  import javax.lang.model.element.VariableElement;
  import javax.lang.model.type.TypeKind;
  import javax.lang.model.type.TypeMirror;
  import javax.lang.model.util.ElementFilter;
  import javax.lang.model.util.Elements;
  import javax.lang.model.util.SimpleElementVisitor6;
  
  import static javax.lang.model.element.ElementKind.PACKAGE;
  
  /**
   * Static utility methods pertaining to {@link Element} instances.
   *
   * @author Gregory Kick
   */
  @Beta
  public final class MoreElements {
      /**
       * An alternate implementation of {@link Elements#getPackageOf} that does not require an
       * {@link Elements} instance.
       *
       * @throws NullPointerException if {@code element} is {@code null}
       */
      public static PackageElement getPackage(Element element) {
          while (element.getKind() != PACKAGE) {
              element = element.getEnclosingElement();
          }
          return (PackageElement) element;
      }
  
      private static final ElementVisitor<PackageElement, Void> PACKAGE_ELEMENT_VISITOR =
              new SimpleElementVisitor6<PackageElement, Void>() {
                  @Override
                  protected PackageElement defaultAction(Element e, Void p) {
                      throw new IllegalArgumentException();
                  }
  
                  @Override
                  public PackageElement visitPackage(PackageElement e, Void p) {
                      return e;
                  }
              };
  
      /**
       * Returns the given {@link Element} instance as {@link PackageElement}.
       * <p/>
       * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
       * always be used over that idiom as instructed in the documentation for {@link Element}.
       *
       * @throws NullPointerException     if {@code element} is {@code null}
       * @throws IllegalArgumentException if {@code element} isn't a {@link PackageElement}.
       */
      public static PackageElement asPackage(Element element) {
          return element.accept(PACKAGE_ELEMENT_VISITOR, null);
      }
  
      private static final ElementVisitor<TypeElement, Void> TYPE_ELEMENT_VISITOR =
              new SimpleElementVisitor6<TypeElement, Void>() {
                  @Override
                  protected TypeElement defaultAction(Element e, Void p) {
                      throw new IllegalArgumentException();
                  }
  
                  @Override
                  public TypeElement visitType(TypeElement e, Void p) {
                      return e;
                  }
              };
  
      /**
       * Returns true if the given {@link Element} instance is a {@link TypeElement}.
       * <p/>
       * <p>This method is functionally equivalent to an {@code instanceof} check, but should
       * always be used over that idiom as instructed in the documentation for {@link Element}.
       *
       * @throws NullPointerException if {@code element} is {@code null}
       */
      public static boolean isType(Element element) {
          return element.getKind().isClass() || element.getKind().isInterface();
      }
  
      /**
       * Returns the given {@link Element} instance as {@link TypeElement}.
       * <p/>
       * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
       * always be used over that idiom as instructed in the documentation for {@link Element}.
       *
       * @throws NullPointerException     if {@code element} is {@code null}
       * @throws IllegalArgumentException if {@code element} isn't a {@link TypeElement}.
       */
      public static TypeElement asType(Element element) {
          return element.accept(TYPE_ELEMENT_VISITOR, null);
      }
  
      private static final ElementVisitor<VariableElement, Void> VARIABLE_ELEMENT_VISITOR =
              new SimpleElementVisitor6<VariableElement, Void>() {
                  @Override
                  protected VariableElement defaultAction(Element e, Void p) {
                      throw new IllegalArgumentException();
                  }
  
                  @Override
                  public VariableElement visitVariable(VariableElement e, Void p) {
                      return e;
                  }
              };
  
      /**
       * Returns the given {@link Element} instance as {@link VariableElement}.
       * <p/>
       * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
       * always be used over that idiom as instructed in the documentation for {@link Element}.
       *
       * @throws NullPointerException     if {@code element} is {@code null}
       * @throws IllegalArgumentException if {@code element} isn't a {@link VariableElement}.
       */
      public static VariableElement asVariable(Element element) {
          return element.accept(VARIABLE_ELEMENT_VISITOR, null);
      }
  
      private static final ElementVisitor<ExecutableElement, Void> EXECUTABLE_ELEMENT_VISITOR =
              new SimpleElementVisitor6<ExecutableElement, Void>() {
                  @Override
                  protected ExecutableElement defaultAction(Element e, Void p) {
                      throw new IllegalArgumentException();
                  }
  
                  @Override
                  public ExecutableElement visitExecutable(ExecutableElement e, Void p) {
                      return e;
                  }
              };
  
      /**
       * Returns the given {@link Element} instance as {@link ExecutableElement}.
       * <p/>
       * <p>This method is functionally equivalent to an {@code instanceof} check and a cast, but should
       * always be used over that idiom as instructed in the documentation for {@link Element}.
       *
       * @throws NullPointerException     if {@code element} is {@code null}
       * @throws IllegalArgumentException if {@code element} isn't a {@link ExecutableElement}.
       */
      public static ExecutableElement asExecutable(Element element) {
          return element.accept(EXECUTABLE_ELEMENT_VISITOR, null);
      }
  
      /**
       * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose
       * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name
       * as that of {@code annotationClass}. This method is a safer alternative to calling
       * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
       * annotation proxies.
       */
      public static boolean isAnnotationPresent(Element element,
                                                Class<? extends Annotation> annotationClass) {
          return getAnnotationMirror(element, annotationClass).isPresent();
      }
  
      /**
       * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on
       * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a
       * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
       * annotation proxies.
       */
      public static Optional<AnnotationMirror> getAnnotationMirror(Element element,
                                                                   Class<? extends Annotation> annotationClass) {
          String annotationClassName = annotationClass.getCanonicalName();
          for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
              TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());
              if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) {
                  return Optional.of(annotationMirror);
              }
          }
          return Optional.absent();
      }
  
      /**
       * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
       * The predicate returns {@code true} if the input {@link Element} has all of the given
       * {@code modifiers}, perhaps in addition to others.
       * <p/>
       * <p>Here is an example how one could get a List of static methods of a class:
       * <pre>{@code
       * FluentIterable.from(ElementFilter.methodsIn(clazzElement.getEnclosedElements()))
       *     .filter(MoreElements.hasModifiers(Modifier.STATIC).toList();
       * }</pre>
       */
      public static Predicate<Element> hasModifiers(Modifier... modifiers) {
          return hasModifiers(ImmutableSet.copyOf(modifiers));
      }
  
      /**
       * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
       * The predicate returns {@code true} if the input {@link Element} has all of the given
       * {@code modifiers}, perhaps in addition to others.
       * <p/>
       * <p>Here is an example how one could get a List of methods with certain modifiers of a class:
       * <pre>{@code
       * Set<Modifier> modifiers = ...;
       * FluentIterable.from(ElementFilter.methodsIn(clazzElement.getEnclosedElements()))
       *     .filter(MoreElements.hasModifiers(modifiers).toList();}
       * </pre>
       */
      public static Predicate<Element> hasModifiers(final Set<Modifier> modifiers) {
          return new Predicate<Element>() {
              @Override
              public boolean apply(Element input) {
                  return input.getModifiers().containsAll(modifiers);
              }
          };
      }
  
      /**
       * Returns the set of all non-private methods from {@code type}, including methods that it
       * inherits from its ancestors. Inherited methods that are overridden are not included in the
       * result. So if {@code type} defines {@code public String toString()}, the returned set will
       * contain that method, but not the {@code toString()} method defined by {@code Object}.
       * <p/>
       * <p>The returned set may contain more than one method with the same signature, if
       * {@code type} inherits those methods from different ancestors. For example, if it
       * inherits from unrelated interfaces {@code One} and {@code Two} which each define
       * {@code void foo();}, and if it does not itself override the {@code foo()} method,
       * then both {@code One.foo()} and {@code Two.foo()} will be in the returned set.
       *
       * @param type         the type whose own and inherited methods are to be returned
       * @param elementUtils an {@link Elements} object, typically returned by
       *                     {@link javax.annotation.processing.AbstractProcessor#processingEnv processingEnv}<!--
       *                     -->.{@link javax.annotation.processing.ProcessingEnvironment.getElementUtils()
       *                     getElementUtils()}
       */
      public static ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(
              TypeElement type, Elements elementUtils) {
  
          SetMultimap<String, ExecutableElement> methodMap = LinkedHashMultimap.create();
          getLocalAndInheritedMethods(getPackage(type), type, methodMap);
          // Find methods that are overridden. We do this using `Elements.overrides`, which means
          // that it is inherently a quadratic operation, since we have to compare every method against
          // every other method. We reduce the performance impact by (a) grouping methods by name, since
          // a method cannot override another method with a different name, and (b) making sure that
          // methods in ancestor types precede those in descendant types, which means we only have to
          // check a method against the ones that follow it in that order.
          Set<ExecutableElement> overridden = new LinkedHashSet<ExecutableElement>();
          for (String methodName : methodMap.keySet()) {
              List<ExecutableElement> methodList = ImmutableList.copyOf(methodMap.get(methodName));
              for (int i = 0; i < methodList.size(); i++) {
                  ExecutableElement methodI = methodList.get(i);
                  for (int j = i + 1; j < methodList.size(); j++) {
                      ExecutableElement methodJ = methodList.get(j);
                      if (elementUtils.overrides(methodJ, methodI, type)) {
                          overridden.add(methodI);
                      }
                  }
              }
          }
          Set<ExecutableElement> methods = new LinkedHashSet<ExecutableElement>(methodMap.values());
          methods.removeAll(overridden);
          return ImmutableSet.copyOf(methods);
      }
  
      // Add to `methods` the instance methods from `type` that are visible to code in the
      // package `pkg`. This means all the instance methods from `type` itself and all instance methods
      // it inherits from its ancestors, except private methods and package-private methods in other
      // packages. This method does not take overriding into account, so it will add both an ancestor
      // method and a descendant method that overrides it.
      // `methods` is a multimap from a method name to all of the methods with that name, including
      // methods that override or overload one another. Within those methods, those in ancestor types
      // always precede those in descendant types.
      private static void getLocalAndInheritedMethods(
              PackageElement pkg, TypeElement type, SetMultimap<String, ExecutableElement> methods) {
  
          for (TypeMirror superInterface : type.getInterfaces()) {
              getLocalAndInheritedMethods(pkg, MoreTypes.asTypeElement(superInterface), methods);
          }
          if (type.getSuperclass().getKind() != TypeKind.NONE) {
              // Visit the superclass after superinterfaces so we will always see the implementation of a
              // method after any interfaces that declared it.
              getLocalAndInheritedMethods(pkg, MoreTypes.asTypeElement(type.getSuperclass()), methods);
          }
          for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) {
              if (!method.getModifiers().contains(Modifier.STATIC)
                      && methodVisibleFromPackage(method, pkg)) {
                  methods.put(method.getSimpleName().toString(), method);
              }
          }
      }
  
      private static boolean methodVisibleFromPackage(ExecutableElement method, PackageElement pkg) {
          // We use Visibility.ofElement rather than .effectiveVisibilityOfElement because it doesn't
          // really matter whether the containing class is visible. If you inherit a public method
          // then you have a public method, regardless of whether you inherit it from a public class.
          Visibility visibility = Visibility.ofElement(method);
          switch (visibility) {
              case PRIVATE:
                  return false;
              case DEFAULT:
                  return getPackage(method).equals(pkg);
              default:
                  return true;
          }
      }
  
      private MoreElements() {
      }
  }