/*
 * Copyright 2012 The Error Prone 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.errorprone.util;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Streams;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;

/** Annotation-related utilities. */
public final class MoreAnnotations {

  /**
   * Returns declaration annotations of the given symbol, as well as 'top-level' type annotations,
   * including :
   *
   * <ul>
   *   <li>Type annotations of the return type of a method.
   *   <li>Type annotations on the type of a formal parameter or field.
   * </ul>
   *
   * <p>One might expect this to be equivalent to information returned by {@link
   * com.sun.tools.javac.code.Type#getAnnotationMirrors}, but javac doesn't associate type
   * annotation information with types for symbols completed from class files, so that approach
   * doesn't work across compilation boundaries.
   */
  public static Stream<Attribute.Compound> getDeclarationAndTypeAttributes(Symbol sym) {
    Symbol typeAnnotationOwner;
    switch (sym.getKind()) {
      case PARAMETER:
        typeAnnotationOwner = sym.owner;
        break;
      default:
        typeAnnotationOwner = sym;
    }
    return Streams.concat(
        sym.getRawAttributes().stream(),
        typeAnnotationOwner.getRawTypeAttributes().stream()
            .filter(anno -> isAnnotationOnType(sym, anno.position)));
  }

  private static boolean isAnnotationOnType(Symbol sym, TypeAnnotationPosition position) {
    if (!position.location.isEmpty()) {
      return false;
    }
    switch (sym.getKind()) {
      case LOCAL_VARIABLE:
        return position.type == TargetType.LOCAL_VARIABLE;
      case FIELD:
        return position.type == TargetType.FIELD;
      case METHOD:
        return position.type == TargetType.METHOD_RETURN;
      case PARAMETER:
        switch (position.type) {
          case METHOD_FORMAL_PARAMETER:
            return ((MethodSymbol) sym.owner).getParameters().indexOf(sym)
                == position.parameter_index;
          default:
            return false;
        }
      case CLASS:
        // There are no type annotations on the top-level type of the class being declared, only
        // on other types in the signature (e.g. `class Foo extends Bar<@A Baz> {}`).
        return false;
      default:
        throw new AssertionError(sym.getKind());
    }
  }

  /**
   * Returns the value of the annotation element-value pair with the given name if it is not
   * explicitly set.
   */
  public static Optional<Attribute> getValue(Attribute.Compound attribute, String name) {
    return attribute.getElementValues().entrySet().stream()
        .filter(e -> e.getKey().getSimpleName().contentEquals(name))
        .map(Map.Entry::getValue)
        .findFirst();
  }

  /** Converts the given attribute to an integer value. */
  public static Optional<Integer> asIntegerValue(Attribute a) {
    class Visitor extends SimpleAnnotationValueVisitor8<Integer, Void> {
      @Override
      public Integer visitInt(int i, Void unused) {
        return i;
      }
    }
    return Optional.ofNullable(a.accept(new Visitor(), null));
  }

  /** Converts the given attribute to an string value. */
  public static Optional<String> asStringValue(Attribute a) {
    class Visitor extends SimpleAnnotationValueVisitor8<String, Void> {
      @Override
      public String visitString(String s, Void unused) {
        return s;
      }
    }
    return Optional.ofNullable(a.accept(new Visitor(), null));
  }

  /** Converts the given attribute to an enum value. */
  public static <T extends Enum<T>> Optional<T> asEnumValue(Class<T> clazz, Attribute a) {
    class Visitor extends SimpleAnnotationValueVisitor8<T, Void> {
      @Override
      public T visitEnumConstant(VariableElement c, Void unused) {
        return Enum.valueOf(clazz, c.getSimpleName().toString());
      }
    }
    return Optional.ofNullable(a.accept(new Visitor(), null));
  }

  /** Converts the given attribute to one or more strings. */
  public static Stream<String> asStrings(Attribute v) {
    return MoreObjects.firstNonNull(
        v.accept(
            new SimpleAnnotationValueVisitor8<Stream<String>, Void>() {
              @Override
              public Stream<String> visitString(String s, Void unused) {
                return Stream.of(s);
              }

              @Override
              public Stream<String> visitArray(List<? extends AnnotationValue> list, Void unused) {
                return list.stream().flatMap(a -> a.accept(this, null)).filter(x -> x != null);
              }
            },
            null),
        Stream.empty());
  }

  private MoreAnnotations() {}
}

@@ -639,16 +639,121 @@
 .util;%0A%0A
+import static java.util.stream.Collectors.groupingBy;%0Aimport static java.util.stream.Collectors.toList;%0A%0A
 import c
@@ -787,16 +787,16 @@
 bjects;%0A
-
 import c
@@ -872,16 +872,68 @@
 ribute;%0A
+import com.sun.tools.javac.code.Attribute.Compound;%0A
 import c
@@ -1117,16 +1117,48 @@
 sition;%0A
+import java.util.LinkedHashMap;%0A
 import j
@@ -2118,26 +2118,16 @@
  Stream%3C
-Attribute.
 Compound
@@ -2404,16 +2404,20 @@
         
+    
 sym.getR
@@ -2441,16 +2441,20 @@
 ream(),%0A
+    
         
@@ -2497,32 +2497,36 @@
 utes().stream()%0A
+    
             .fil
@@ -2577,16 +2577,187 @@
 ition)))
+%0A        .collect(%0A            groupingBy(c -%3E c.type.asElement().getQualifiedName(), LinkedHashMap::new, toList()))%0A        .values().stream()%0A        .map(c -%3E c.get(0))
 ;%0A  %7D%0A%0A 
@@ -3095,16 +3095,40 @@
 .FIELD;%0A
+      case CONSTRUCTOR:%0A
       ca
@@ -3696,32 +3696,32 @@
 %0A      default:%0A
-
         throw ne
@@ -3737,16 +3737,97 @@
 onError(
+%0A            %22unsupported element kind in MoreAnnotation#isAnnotationOnType: %22 + 
 sym.getK

Improve MoreAnnotations' handling of constructors RELNOTES: N/A