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.regex.Matcher;
  36 import java.util.regex.Pattern;
  37 
  38 import javax.annotation.processing.FilerException;
  39 import javax.annotation.processing.ProcessingEnvironment;
  40 import javax.lang.model.element.AnnotationMirror;
  41 import javax.lang.model.element.AnnotationValue;
  42 import javax.lang.model.element.Element;
  43 import javax.lang.model.element.ExecutableElement;
  44 import javax.lang.model.element.TypeElement;
  45 import javax.lang.model.type.TypeMirror;
  46 import javax.lang.model.util.ElementFilter;
  47 import javax.tools.Diagnostic.Kind;
  48 import javax.tools.FileObject;
  49 import javax.tools.StandardLocation;
  50 
  51 /**
  52  * {@link javax.annotation.processing.AbstractProcessor} subclass that provides extra functionality.
  53  */
  54 @SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS", //
  55                 reason = "We want this type to be found when someone is writing a new Graal annotation processor")
  56 public abstract class AbstractProcessor extends javax.annotation.processing.AbstractProcessor {
  57 
  58     /**
  59      * Gets the processing environment available to this processor.
  60      */
  61     public ProcessingEnvironment env() {
  62         return processingEnv;
  63     }
  64 
  65     private final Map<String, TypeElement> types = new HashMap<>();
  66 
  67     /**
  68      * Gets the {@link TypeMirror} for a given class name.
  69      *
  70      * @throws NoClassDefFoundError if the class cannot be resolved
  71      */
  72     public TypeMirror getType(String className) {
  73         return getTypeElement(className).asType();
  74     }
  75 
  76     /**
  77      * Gets the {@link TypeMirror} for a given class name.
  78      *
  79      * @return {@code null} if the class cannot be resolved
  80      */
  81     public TypeMirror getTypeOrNull(String className) {
  82         TypeElement element = getTypeElementOrNull(className);
  83         if (element == null) {
  84             return null;
  85         }
  86         return element.asType();
  87     }
  88 
  89     /**
  90      * Gets the {@link TypeElement} for a given class name.
  91      *
  92      * @throws NoClassDefFoundError if the class cannot be resolved
  93      */
  94     public TypeElement getTypeElement(String className) {
  95         TypeElement type = getTypeElementOrNull(className);
  96         if (type == null) {
  97             throw new NoClassDefFoundError(className);
  98         }
  99         return type;
 100     }
 101 
 102     /**
 103      * Gets the {@link TypeElement} for a given class name.
 104      *
 105      * @returns {@code null} if the class cannot be resolved
 106      */
 107     public TypeElement getTypeElementOrNull(String className) {
 108         TypeElement type = types.get(className);
 109         if (type == null) {
 110             type = processingEnv.getElementUtils().getTypeElement(className);
 111             if (type == null) {
 112                 return null;
 113             }
 114             types.put(className, type);
 115         }
 116         return type;
 117     }
 118 
 119     /**
 120      * Converts a given {@link TypeMirror} to a {@link TypeElement}.
 121      *
 122      * @throws ClassCastException if type cannot be converted to a {@link TypeElement}
 123      */
 124     public TypeElement asTypeElement(TypeMirror type) {
 125         Element element = processingEnv.getTypeUtils().asElement(type);
 126         if (element == null) {
 127             throw new ClassCastException(type + " cannot be converted to a " + TypeElement.class.getName());
 128         }
 129         return (TypeElement) element;
 130     }
 131 
 132     /**
 133      * Regular expression for a qualified class name that assumes package names start with lowercase
 134      * and non-package components start with uppercase.
 135      */
 136     private static final Pattern QUALIFIED_CLASS_NAME_RE = Pattern.compile("(?:[a-z]\\w*\\.)+([A-Z].*)");
 137 
 138     /**
 139      * Gets the non-package component of a qualified class name.
 140      *
 141      * @throws IllegalArgumentException if {@code className} does not match
 142      *             {@link #QUALIFIED_CLASS_NAME_RE}
 143      */
 144     public static String getSimpleName(String className) {
 145         Matcher m = QUALIFIED_CLASS_NAME_RE.matcher(className);
 146         if (m.matches()) {
 147             return m.group(1);
 148         }
 149         throw new IllegalArgumentException("Class name \"" + className + "\" does not match pattern " + QUALIFIED_CLASS_NAME_RE);
 150     }
 151 
 152     /**
 153      * Gets the package component of a qualified class name.
 154      *
 155      * @throws IllegalArgumentException if {@code className} does not match
 156      *             {@link #QUALIFIED_CLASS_NAME_RE}
 157      */
 158     public static String getPackageName(String className) {
 159         String simpleName = getSimpleName(className);
 160         return className.substring(0, className.length() - simpleName.length() - 1);
 161     }
 162 
 163     /**
 164      * Gets the annotation of type {@code annotationType} directly present on {@code element}.
 165      *
 166      * @return {@code null} if an annotation of type {@code annotationType} is not on
 167      *         {@code element}
 168      */
 169     public AnnotationMirror getAnnotation(Element element, TypeMirror annotationType) {
 170         List<AnnotationMirror> mirrors = getAnnotations(element, annotationType);
 171         return mirrors.isEmpty() ? null : mirrors.get(0);
 172     }
 173 
 174     /**
 175      * Gets all annotations directly present on {@code element}.
 176      */
 177     public List<AnnotationMirror> getAnnotations(Element element, TypeMirror typeMirror) {
 178         List<AnnotationMirror> result = new ArrayList<>();
 179         for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
 180             if (processingEnv.getTypeUtils().isSameType(mirror.getAnnotationType(), typeMirror)) {
 181                 result.add(mirror);
 182             }
 183         }
 184         return result;
 185     }
 186 
 187     /**
 188      * Gets the value of the {@code name} element of {@code annotation} and converts it to a value
 189      * of type {@code type}.
 190      *
 191      * @param type the expected type of the element value. This must be a subclass of one of the
 192      *            types described by {@link AnnotationValue}.
 193      * @throws NoSuchElementException if {@code annotation} has no element named {@code name}
 194      * @throws ClassCastException if the value of the specified element cannot be converted to
 195      *             {@code type}
 196      */
 197     public static <T> T getAnnotationValue(AnnotationMirror annotation, String name, Class<T> type) {
 198         ExecutableElement valueMethod = null;
 199         for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
 200             if (method.getSimpleName().toString().equals(name)) {
 201                 valueMethod = method;
 202                 break;
 203             }
 204         }
 205 
 206         if (valueMethod == null) {
 207             return null;
 208         }
 209 
 210         AnnotationValue value = annotation.getElementValues().get(valueMethod);
 211         if (value == null) {
 212             value = valueMethod.getDefaultValue();
 213         }
 214 
 215         return type.cast(value.getValue());
 216     }
 217 
 218     /**
 219      * Gets the value of the {@code name} array-typed element of {@code annotation} and converts it
 220      * to list of values of type {@code type}.
 221      *
 222      * @param componentType the expected component type of the element value. This must be a
 223      *            subclass of one of the types described by {@link AnnotationValue}.
 224      * @throws NoSuchElementException if {@code annotation} has no element named {@code name}
 225      * @throws ClassCastException if the value of the specified element is not an array whose
 226      *             components cannot be converted to {@code componentType}
 227      */
 228     @SuppressWarnings("unchecked")
 229     public static <T> List<T> getAnnotationValueList(AnnotationMirror annotation, String name, Class<T> componentType) {
 230         List<? extends AnnotationValue> values = getAnnotationValue(annotation, name, List.class);
 231         List<T> result = new ArrayList<>();
 232 
 233         if (values != null) {
 234             for (AnnotationValue value : values) {
 235                 result.add(componentType.cast(value.getValue()));
 236             }
 237         }
 238         return result;
 239     }
 240 
 241     /**
 242      * Creates a {@code META-INF/providers/<providerClassName>} file whose contents are a single
 243      * line containing {@code serviceClassName}.
 244      */
 245     public void createProviderFile(String providerClassName, String serviceClassName, Element... originatingElements) {
 246         assert originatingElements.length > 0;
 247         String filename = "META-INF/providers/" + providerClassName;
 248         try {
 249             FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", filename, originatingElements);
 250             PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
 251             writer.println(serviceClassName);
 252             writer.close();
 253         } catch (IOException e) {
 254             processingEnv.getMessager().printMessage(isBug367599(e) ? Kind.NOTE : Kind.ERROR, e.getMessage(), originatingElements[0]);
 255         }
 256     }
 257 
 258     /**
 259      * Determines if a given exception is (most likely) caused by
 260      * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>.
 261      */
 262     private static boolean isBug367599(Throwable t) {
 263         if (t instanceof FilerException) {
 264             for (StackTraceElement ste : t.getStackTrace()) {
 265                 if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
 266                     // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599
 267                     return true;
 268                 }
 269             }
 270         }
 271         return t.getCause() != null && isBug367599(t.getCause());
 272     }
 273 }