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