1 /*
   2  * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.processor;
  26 
  27 import java.io.IOException;
  28 import java.io.OutputStreamWriter;
  29 import java.io.PrintWriter;
  30 import java.util.ArrayList;
  31 import java.util.HashMap;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.NoSuchElementException;
  35 import java.util.Set;
  36 import java.util.regex.Matcher;
  37 import java.util.regex.Pattern;
  38 
  39 import javax.annotation.processing.FilerException;
  40 import javax.annotation.processing.ProcessingEnvironment;
  41 import javax.annotation.processing.RoundEnvironment;
  42 import javax.lang.model.element.AnnotationMirror;
  43 import javax.lang.model.element.AnnotationValue;
  44 import javax.lang.model.element.Element;
  45 import javax.lang.model.element.ExecutableElement;
  46 import javax.lang.model.element.TypeElement;
  47 import javax.lang.model.type.TypeMirror;
  48 import javax.lang.model.util.ElementFilter;
  49 import javax.tools.Diagnostic.Kind;
  50 import javax.tools.FileObject;
  51 import javax.tools.StandardLocation;
  52 
  53 /**
  54  * {@link javax.annotation.processing.AbstractProcessor} subclass that provides extra functionality.
  55  */
  56 @SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS", //
  57                 reason = "We want this type to be found when someone is writing a new Graal annotation processor")
  58 public abstract class AbstractProcessor extends javax.annotation.processing.AbstractProcessor {
  59 
  60     /**
  61      * Gets the processing environment available to this processor.
  62      */
  63     public ProcessingEnvironment env() {
  64         return processingEnv;
  65     }
  66 
  67     @Override
  68     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  69         // In JDK 8, each annotation processing round has its own Elements object
  70         // so this cache must be cleared at the start of each round. As of JDK9,
  71         // a single Elements is preserved across all annotation processing rounds.
  72         // However, since both behaviors are compliant with the annotation processing
  73         // specification, we unconditionally clear the cache to be safe.
  74         types.clear();
  75 
  76         return doProcess(annotations, roundEnv);
  77     }
  78 
  79     protected abstract boolean doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
  80 
  81     private final Map<String, TypeElement> types = new HashMap<>();
  82 
  83     /**
  84      * Gets the {@link TypeMirror} for a given class name.
  85      *
  86      * @throws NoClassDefFoundError if the class cannot be resolved
  87      */
  88     public TypeMirror getType(String className) {
  89         return getTypeElement(className).asType();
  90     }
  91 
  92     /**
  93      * Gets the {@link TypeMirror} for a given class name.
  94      *
  95      * @return {@code null} if the class cannot be resolved
  96      */
  97     public TypeMirror getTypeOrNull(String className) {
  98         TypeElement element = getTypeElementOrNull(className);
  99         if (element == null) {
 100             return null;
 101         }
 102         return element.asType();
 103     }
 104 
 105     /**
 106      * Gets the {@link TypeElement} for a given class name.
 107      *
 108      * @throws NoClassDefFoundError if the class cannot be resolved
 109      */
 110     public TypeElement getTypeElement(String className) {
 111         TypeElement type = getTypeElementOrNull(className);
 112         if (type == null) {
 113             throw new NoClassDefFoundError(className);
 114         }
 115         return type;
 116     }
 117 
 118     /**
 119      * Gets the {@link TypeElement} for a given class name.
 120      *
 121      * @returns {@code null} if the class cannot be resolved
 122      */
 123     public TypeElement getTypeElementOrNull(String className) {
 124         TypeElement type = types.get(className);
 125         if (type == null) {
 126             type = processingEnv.getElementUtils().getTypeElement(className);
 127             if (type == null) {
 128                 return null;
 129             }
 130             types.put(className, type);
 131         }
 132         return type;
 133     }
 134 
 135     /**
 136      * Converts a given {@link TypeMirror} to a {@link TypeElement}.
 137      *
 138      * @throws ClassCastException if type cannot be converted to a {@link TypeElement}
 139      */
 140     public TypeElement asTypeElement(TypeMirror type) {
 141         Element element = processingEnv.getTypeUtils().asElement(type);
 142         if (element == null) {
 143             throw new ClassCastException(type + " cannot be converted to a " + TypeElement.class.getName());
 144         }
 145         return (TypeElement) element;
 146     }
 147 
 148     /**
 149      * Regular expression for a qualified class name that assumes package names start with lowercase
 150      * and non-package components start with uppercase.
 151      */
 152     private static final Pattern QUALIFIED_CLASS_NAME_RE = Pattern.compile("(?:[a-z]\\w*\\.)+([A-Z].*)");
 153 
 154     /**
 155      * Gets the non-package component of a qualified class name.
 156      *
 157      * @throws IllegalArgumentException if {@code className} does not match
 158      *             {@link #QUALIFIED_CLASS_NAME_RE}
 159      */
 160     public static String getSimpleName(String className) {
 161         Matcher m = QUALIFIED_CLASS_NAME_RE.matcher(className);
 162         if (m.matches()) {
 163             return m.group(1);
 164         }
 165         throw new IllegalArgumentException("Class name \"" + className + "\" does not match pattern " + QUALIFIED_CLASS_NAME_RE);
 166     }
 167 
 168     /**
 169      * Gets the package component of a qualified class name.
 170      *
 171      * @throws IllegalArgumentException if {@code className} does not match
 172      *             {@link #QUALIFIED_CLASS_NAME_RE}
 173      */
 174     public static String getPackageName(String className) {
 175         String simpleName = getSimpleName(className);
 176         return className.substring(0, className.length() - simpleName.length() - 1);
 177     }
 178 
 179     /**
 180      * Gets the annotation of type {@code annotationType} directly present on {@code element}.
 181      *
 182      * @return {@code null} if an annotation of type {@code annotationType} is not on
 183      *         {@code element}
 184      */
 185     public AnnotationMirror getAnnotation(Element element, TypeMirror annotationType) {
 186         List<AnnotationMirror> mirrors = getAnnotations(element, annotationType);
 187         return mirrors.isEmpty() ? null : mirrors.get(0);
 188     }
 189 
 190     /**
 191      * Gets all annotations directly present on {@code element}.
 192      */
 193     public List<AnnotationMirror> getAnnotations(Element element, TypeMirror typeMirror) {
 194         List<AnnotationMirror> result = new ArrayList<>();
 195         for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
 196             if (processingEnv.getTypeUtils().isSameType(mirror.getAnnotationType(), typeMirror)) {
 197                 result.add(mirror);
 198             }
 199         }
 200         return result;
 201     }
 202 
 203     /**
 204      * Gets the value of the {@code name} element of {@code annotation} and converts it to a value
 205      * of type {@code type}.
 206      *
 207      * @param type the expected type of the element value. This must be a subclass of one of the
 208      *            types described by {@link AnnotationValue}.
 209      * @throws NoSuchElementException if {@code annotation} has no element named {@code name}
 210      * @throws ClassCastException if the value of the specified element cannot be converted to
 211      *             {@code type}
 212      */
 213     public static <T> T getAnnotationValue(AnnotationMirror annotation, String name, Class<T> type) {
 214         ExecutableElement valueMethod = null;
 215         for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
 216             if (method.getSimpleName().toString().equals(name)) {
 217                 valueMethod = method;
 218                 break;
 219             }
 220         }
 221 
 222         if (valueMethod == null) {
 223             return null;
 224         }
 225 
 226         AnnotationValue value = annotation.getElementValues().get(valueMethod);
 227         if (value == null) {
 228             value = valueMethod.getDefaultValue();
 229         }
 230 
 231         return type.cast(value.getValue());
 232     }
 233 
 234     /**
 235      * Gets the value of the {@code name} array-typed element of {@code annotation} and converts it
 236      * to list of values of type {@code type}.
 237      *
 238      * @param componentType the expected component type of the element value. This must be a
 239      *            subclass of one of the types described by {@link AnnotationValue}.
 240      * @throws NoSuchElementException if {@code annotation} has no element named {@code name}
 241      * @throws ClassCastException if the value of the specified element is not an array whose
 242      *             components cannot be converted to {@code componentType}
 243      */
 244     @SuppressWarnings("unchecked")
 245     public static <T> List<T> getAnnotationValueList(AnnotationMirror annotation, String name, Class<T> componentType) {
 246         List<? extends AnnotationValue> values = getAnnotationValue(annotation, name, List.class);
 247         List<T> result = new ArrayList<>();
 248 
 249         if (values != null) {
 250             for (AnnotationValue value : values) {
 251                 result.add(componentType.cast(value.getValue()));
 252             }
 253         }
 254         return result;
 255     }
 256 
 257     /**
 258      * Creates a {@code META-INF/providers/<providerClassName>} file whose contents are a single
 259      * line containing {@code serviceClassName}.
 260      */
 261     public void createProviderFile(String providerClassName, String serviceClassName, Element... originatingElements) {
 262         assert originatingElements.length > 0;
 263         String filename = "META-INF/providers/" + providerClassName;
 264         try {
 265             FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", filename, originatingElements);
 266             PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
 267             writer.println(serviceClassName);
 268             writer.close();
 269         } catch (IOException e) {
 270             processingEnv.getMessager().printMessage(isBug367599(e) ? Kind.NOTE : Kind.ERROR, e.getMessage(), originatingElements[0]);
 271         }
 272     }
 273 
 274     /**
 275      * Determines if a given exception is (most likely) caused by
 276      * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>.
 277      */
 278     private static boolean isBug367599(Throwable t) {
 279         if (t instanceof FilerException) {
 280             for (StackTraceElement ste : t.getStackTrace()) {
 281                 if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
 282                     // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599
 283                     return true;
 284                 }
 285             }
 286         }
 287         return t.getCause() != null && isBug367599(t.getCause());
 288     }
 289 }