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 }