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 }