blob: 01868b5a0f8518da48e27992239f6356f579a194 [file] [log] [blame]
/*
* Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.processor;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/**
* {@link javax.annotation.processing.AbstractProcessor} subclass that provides extra functionality.
*/
@SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS", //
reason = "We want this type to be found when someone is writing a new Graal annotation processor")
public abstract class AbstractProcessor extends javax.annotation.processing.AbstractProcessor {
/**
* Gets the processing environment available to this processor.
*/
public ProcessingEnvironment env() {
return processingEnv;
}
private final Map<String, TypeElement> types = new HashMap<>();
/**
* Gets the {@link TypeMirror} for a given class name.
*
* @throws NoClassDefFoundError if the class cannot be resolved
*/
public TypeMirror getType(String className) {
return getTypeElement(className).asType();
}
/**
* Gets the {@link TypeMirror} for a given class name.
*
* @return {@code null} if the class cannot be resolved
*/
public TypeMirror getTypeOrNull(String className) {
TypeElement element = getTypeElementOrNull(className);
if (element == null) {
return null;
}
return element.asType();
}
/**
* Gets the {@link TypeElement} for a given class name.
*
* @throws NoClassDefFoundError if the class cannot be resolved
*/
public TypeElement getTypeElement(String className) {
TypeElement type = getTypeElementOrNull(className);
if (type == null) {
throw new NoClassDefFoundError(className);
}
return type;
}
/**
* Gets the {@link TypeElement} for a given class name.
*
* @returns {@code null} if the class cannot be resolved
*/
public TypeElement getTypeElementOrNull(String className) {
TypeElement type = types.get(className);
if (type == null) {
type = processingEnv.getElementUtils().getTypeElement(className);
if (type == null) {
return null;
}
types.put(className, type);
}
return type;
}
/**
* Converts a given {@link TypeMirror} to a {@link TypeElement}.
*
* @throws ClassCastException if type cannot be converted to a {@link TypeElement}
*/
public TypeElement asTypeElement(TypeMirror type) {
Element element = processingEnv.getTypeUtils().asElement(type);
if (element == null) {
throw new ClassCastException(type + " cannot be converted to a " + TypeElement.class.getName());
}
return (TypeElement) element;
}
/**
* Regular expression for a qualified class name that assumes package names start with lowercase
* and non-package components start with uppercase.
*/
private static final Pattern QUALIFIED_CLASS_NAME_RE = Pattern.compile("(?:[a-z]\\w*\\.)+([A-Z].*)");
/**
* Gets the non-package component of a qualified class name.
*
* @throws IllegalArgumentException if {@code className} does not match
* {@link #QUALIFIED_CLASS_NAME_RE}
*/
public static String getSimpleName(String className) {
Matcher m = QUALIFIED_CLASS_NAME_RE.matcher(className);
if (m.matches()) {
return m.group(1);
}
throw new IllegalArgumentException("Class name \"" + className + "\" does not match pattern " + QUALIFIED_CLASS_NAME_RE);
}
/**
* Gets the package component of a qualified class name.
*
* @throws IllegalArgumentException if {@code className} does not match
* {@link #QUALIFIED_CLASS_NAME_RE}
*/
public static String getPackageName(String className) {
String simpleName = getSimpleName(className);
return className.substring(0, className.length() - simpleName.length() - 1);
}
/**
* Gets the annotation of type {@code annotationType} directly present on {@code element}.
*
* @return {@code null} if an annotation of type {@code annotationType} is not on
* {@code element}
*/
public AnnotationMirror getAnnotation(Element element, TypeMirror annotationType) {
List<AnnotationMirror> mirrors = getAnnotations(element, annotationType);
return mirrors.isEmpty() ? null : mirrors.get(0);
}
/**
* Gets all annotations directly present on {@code element}.
*/
public List<AnnotationMirror> getAnnotations(Element element, TypeMirror typeMirror) {
List<AnnotationMirror> result = new ArrayList<>();
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (processingEnv.getTypeUtils().isSameType(mirror.getAnnotationType(), typeMirror)) {
result.add(mirror);
}
}
return result;
}
/**
* Gets the value of the {@code name} element of {@code annotation} and converts it to a value
* of type {@code type}.
*
* @param type the expected type of the element value. This must be a subclass of one of the
* types described by {@link AnnotationValue}.
* @throws NoSuchElementException if {@code annotation} has no element named {@code name}
* @throws ClassCastException if the value of the specified element cannot be converted to
* {@code type}
*/
public static <T> T getAnnotationValue(AnnotationMirror annotation, String name, Class<T> type) {
ExecutableElement valueMethod = null;
for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
if (method.getSimpleName().toString().equals(name)) {
valueMethod = method;
break;
}
}
if (valueMethod == null) {
return null;
}
AnnotationValue value = annotation.getElementValues().get(valueMethod);
if (value == null) {
value = valueMethod.getDefaultValue();
}
return type.cast(value.getValue());
}
/**
* Gets the value of the {@code name} array-typed element of {@code annotation} and converts it
* to list of values of type {@code type}.
*
* @param componentType the expected component type of the element value. This must be a
* subclass of one of the types described by {@link AnnotationValue}.
* @throws NoSuchElementException if {@code annotation} has no element named {@code name}
* @throws ClassCastException if the value of the specified element is not an array whose
* components cannot be converted to {@code componentType}
*/
@SuppressWarnings("unchecked")
public static <T> List<T> getAnnotationValueList(AnnotationMirror annotation, String name, Class<T> componentType) {
List<? extends AnnotationValue> values = getAnnotationValue(annotation, name, List.class);
List<T> result = new ArrayList<>();
if (values != null) {
for (AnnotationValue value : values) {
result.add(componentType.cast(value.getValue()));
}
}
return result;
}
/**
* Creates a {@code META-INF/providers/<providerClassName>} file whose contents are a single
* line containing {@code serviceClassName}.
*/
public void createProviderFile(String providerClassName, String serviceClassName, Element... originatingElements) {
assert originatingElements.length > 0;
String filename = "META-INF/providers/" + providerClassName;
try {
FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", filename, originatingElements);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
writer.println(serviceClassName);
writer.close();
} catch (IOException e) {
processingEnv.getMessager().printMessage(isBug367599(e) ? Kind.NOTE : Kind.ERROR, e.getMessage(), originatingElements[0]);
}
}
/**
* Determines if a given exception is (most likely) caused by
* <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>.
*/
private static boolean isBug367599(Throwable t) {
if (t instanceof FilerException) {
for (StackTraceElement ste : t.getStackTrace()) {
if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599
return true;
}
}
}
return t.getCause() != null && isBug367599(t.getCause());
}
}