1// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.common.options.processor;
15
16import java.lang.annotation.Annotation;
17import java.util.Map;
18import javax.lang.model.element.AnnotationMirror;
19import javax.lang.model.element.AnnotationValue;
20import javax.lang.model.element.Element;
21import javax.lang.model.element.ExecutableElement;
22import javax.lang.model.element.TypeElement;
23import javax.lang.model.type.DeclaredType;
24import javax.lang.model.type.TypeMirror;
25import javax.lang.model.util.Elements;
26import javax.lang.model.util.Types;
27
28/** Convenient utilities for dealing with the javax.lang.model types. */
29public class ProcessorUtils {
30
31  /** Return the AnnotationMirror for the annotation of the given type on the element provided. */
32  static AnnotationMirror getAnnotation(
33      Elements elementUtils,
34      Types typeUtils,
35      Element element,
36      Class<? extends Annotation> annotation)
37      throws OptionProcessorException {
38    TypeElement annotationElement = elementUtils.getTypeElement(annotation.getCanonicalName());
39    if (annotationElement == null) {
40      // This can happen if the annotation is on the -processorpath but not on the -classpath.
41      throw new OptionProcessorException(
42          element, "Unable to find the type of annotation %s.", annotation);
43    }
44    TypeMirror annotationMirror = annotationElement.asType();
45
46    for (AnnotationMirror annot : element.getAnnotationMirrors()) {
47      if (typeUtils.isSameType(annot.getAnnotationType(), annotationMirror)) {
48        return annot;
49      }
50    }
51    // No annotation of this requested type found.
52    throw new OptionProcessorException(
53        element, "No annotation %s found for this element.", annotation);
54  }
55
56  /**
57   * Returns the contents of a {@code Class}-typed field in an annotation.
58   *
59   * <p>Taken & adapted from AutoValueProcessor.java
60   *
61   * <p>This method is needed because directly reading the value of such a field from an
62   * AnnotationMirror throws:
63   *
64   * <pre>
65   * javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror Foo.
66   * </pre>
67   *
68   * @param annotation The annotation to read from.
69   * @param fieldName The name of the field to read, e.g. "exclude".
70   * @return a set of fully-qualified names of classes appearing in 'fieldName' on 'annotation' on
71   *     'element'.
72   */
73  static TypeElement getClassTypeFromAnnotationField(
74      Elements elementUtils, AnnotationMirror annotation, String fieldName)
75      throws OptionProcessorException {
76    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
77        elementUtils.getElementValuesWithDefaults(annotation).entrySet()) {
78      if (entry.getKey().getSimpleName().contentEquals(fieldName)) {
79        Object annotationField = entry.getValue().getValue();
80        if (!(annotationField instanceof DeclaredType)) {
81          throw new IllegalStateException(
82              String.format(
83                  "The fieldName provided should only apply to Class<> type annotation fields, "
84                      + "but the field's value (%s) couldn't get cast to a DeclaredType",
85                  entry));
86        }
87        String qualifiedName =
88            ((TypeElement) ((DeclaredType) annotationField).asElement())
89                .getQualifiedName()
90                .toString();
91        return elementUtils.getTypeElement(qualifiedName);
92      }
93    }
94    // Annotation missing the requested field.
95    throw new OptionProcessorException(
96        null, "No member %s of the %s annotation found for element.", fieldName, annotation);
97  }
98}
99