comparison graal/com.oracle.jvmci.options.processor/src/com/oracle/jvmci/options/processor/OptionProcessor.java @ 21554:b1530a6cce8c

renamed com.oracle.graal.[debug|options|hotspotvmconfig]* modules to com.oracle.jvmci.[debug|options|hotspotvmconfig]* modules (JBS:GRAAL-53)
author Doug Simon <doug.simon@oracle.com>
date Tue, 26 May 2015 23:21:15 +0200
parents graal/com.oracle.graal.options.processor/src/com/oracle/graal/options/processor/OptionProcessor.java@71e05c3a1e12
children d563baeca9df
comparison
equal deleted inserted replaced
21553:0910a9497b02 21554:b1530a6cce8c
1 /*
2 * Copyright (c) 2013, 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 com.oracle.jvmci.options.processor;
24
25 import java.io.*;
26 import java.util.*;
27
28 import javax.annotation.processing.*;
29 import javax.lang.model.*;
30 import javax.lang.model.element.*;
31 import javax.lang.model.type.*;
32 import javax.lang.model.util.*;
33 import javax.tools.Diagnostic.Kind;
34 import javax.tools.*;
35
36 import com.oracle.jvmci.options.*;
37
38
39 /**
40 * Processes static fields annotated with {@link Option}. An {@link Options} service is generated
41 * for each top level class containing at least one such field. These service objects can be
42 * retrieved as follows:
43 *
44 * <pre>
45 * ServiceLoader&lt;Options&gt; sl = ServiceLoader.load(Options.class);
46 * for (Options opts : sl) {
47 * for (OptionDescriptor desc : sl) {
48 * // use desc
49 * }
50 * }
51 * </pre>
52 */
53 @SupportedAnnotationTypes({"com.oracle.jvmci.options.Option"})
54 public class OptionProcessor extends AbstractProcessor {
55
56 @Override
57 public SourceVersion getSupportedSourceVersion() {
58 return SourceVersion.latest();
59 }
60
61 private final Set<Element> processed = new HashSet<>();
62
63 private void processElement(Element element, OptionsInfo info) {
64
65 if (!element.getModifiers().contains(Modifier.STATIC)) {
66 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element);
67 return;
68 }
69
70 Option annotation = element.getAnnotation(Option.class);
71 assert annotation != null;
72 assert element instanceof VariableElement;
73 assert element.getKind() == ElementKind.FIELD;
74 VariableElement field = (VariableElement) element;
75 String fieldName = field.getSimpleName().toString();
76
77 Elements elements = processingEnv.getElementUtils();
78 Types types = processingEnv.getTypeUtils();
79
80 TypeMirror fieldType = field.asType();
81 if (fieldType.getKind() != TypeKind.DECLARED) {
82 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be of type " + OptionValue.class.getName(), element);
83 return;
84 }
85 DeclaredType declaredFieldType = (DeclaredType) fieldType;
86
87 TypeMirror optionValueType = elements.getTypeElement(OptionValue.class.getName()).asType();
88 if (!types.isSubtype(fieldType, types.erasure(optionValueType))) {
89 String msg = String.format("Option field type %s is not a subclass of %s", fieldType, optionValueType);
90 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element);
91 return;
92 }
93
94 if (!field.getModifiers().contains(Modifier.STATIC)) {
95 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element);
96 return;
97 }
98
99 String help = annotation.help();
100 if (help.length() != 0) {
101 char firstChar = help.charAt(0);
102 if (!Character.isUpperCase(firstChar)) {
103 processingEnv.getMessager().printMessage(Kind.ERROR, "Option help text must start with upper case letter", element);
104 return;
105 }
106 }
107
108 String optionName = annotation.name();
109 if (optionName.equals("")) {
110 optionName = fieldName;
111 }
112
113 DeclaredType declaredOptionValueType = declaredFieldType;
114 while (!types.isSameType(types.erasure(declaredOptionValueType), types.erasure(optionValueType))) {
115 List<? extends TypeMirror> directSupertypes = types.directSupertypes(declaredFieldType);
116 assert !directSupertypes.isEmpty();
117 declaredOptionValueType = (DeclaredType) directSupertypes.get(0);
118 }
119
120 assert !declaredOptionValueType.getTypeArguments().isEmpty();
121 String optionType = declaredOptionValueType.getTypeArguments().get(0).toString();
122 if (optionType.startsWith("java.lang.")) {
123 optionType = optionType.substring("java.lang.".length());
124 }
125
126 Element enclosing = element.getEnclosingElement();
127 String declaringClass = "";
128 String separator = "";
129 Set<Element> originatingElementsList = info.originatingElements;
130 originatingElementsList.add(field);
131 while (enclosing != null) {
132 if (enclosing.getKind() == ElementKind.CLASS || enclosing.getKind() == ElementKind.INTERFACE) {
133 if (enclosing.getModifiers().contains(Modifier.PRIVATE)) {
134 String msg = String.format("Option field cannot be declared in a private %s %s", enclosing.getKind().name().toLowerCase(), enclosing);
135 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element);
136 return;
137 }
138 originatingElementsList.add(enclosing);
139 declaringClass = enclosing.getSimpleName() + separator + declaringClass;
140 separator = ".";
141 } else {
142 assert enclosing.getKind() == ElementKind.PACKAGE;
143 }
144 enclosing = enclosing.getEnclosingElement();
145 }
146
147 info.options.add(new OptionInfo(optionName, help, optionType, declaringClass, field));
148 }
149
150 private void createFiles(OptionsInfo info) {
151 String pkg = ((PackageElement) info.topDeclaringType.getEnclosingElement()).getQualifiedName().toString();
152 Name topDeclaringClass = info.topDeclaringType.getSimpleName();
153
154 String optionsClassName = topDeclaringClass + "_" + Options.class.getSimpleName();
155 Element[] originatingElements = info.originatingElements.toArray(new Element[info.originatingElements.size()]);
156
157 Filer filer = processingEnv.getFiler();
158 try (PrintWriter out = createSourceFile(pkg, optionsClassName, filer, originatingElements)) {
159
160 out.println("// CheckStyle: stop header check");
161 out.println("// GENERATED CONTENT - DO NOT EDIT");
162 out.println("// Source: " + topDeclaringClass + ".java");
163 out.println("package " + pkg + ";");
164 out.println("");
165 out.println("import java.util.*;");
166 out.println("import " + Options.class.getPackage().getName() + ".*;");
167 out.println("");
168 out.println("public class " + optionsClassName + " implements " + Options.class.getSimpleName() + " {");
169 out.println(" @Override");
170 String desc = OptionDescriptor.class.getSimpleName();
171 out.println(" public Iterator<" + desc + "> iterator() {");
172 out.println(" // CheckStyle: stop line length check");
173 out.println(" List<" + desc + "> options = Arrays.asList(");
174
175 boolean needPrivateFieldAccessor = false;
176 int i = 0;
177 Collections.sort(info.options);
178 for (OptionInfo option : info.options) {
179 String optionValue;
180 if (option.field.getModifiers().contains(Modifier.PRIVATE)) {
181 needPrivateFieldAccessor = true;
182 optionValue = "field(" + option.declaringClass + ".class, \"" + option.field.getSimpleName() + "\")";
183 } else {
184 optionValue = option.declaringClass + "." + option.field.getSimpleName();
185 }
186 String name = option.name;
187 String type = option.type;
188 String help = option.help;
189 String declaringClass = option.declaringClass;
190 Name fieldName = option.field.getSimpleName();
191 String comma = i == info.options.size() - 1 ? "" : ",";
192 out.printf(" new %s(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s)%s\n", desc, name, type, help, declaringClass, fieldName, optionValue, comma);
193 i++;
194 }
195 out.println(" );");
196 out.println(" // CheckStyle: resume line length check");
197 out.println(" return options.iterator();");
198 out.println(" }");
199 if (needPrivateFieldAccessor) {
200 out.println(" private static " + OptionValue.class.getSimpleName() + "<?> field(Class<?> declaringClass, String fieldName) {");
201 out.println(" try {");
202 out.println(" java.lang.reflect.Field field = declaringClass.getDeclaredField(fieldName);");
203 out.println(" field.setAccessible(true);");
204 out.println(" return (" + OptionValue.class.getSimpleName() + "<?>) field.get(null);");
205 out.println(" } catch (Exception e) {");
206 out.println(" throw (InternalError) new InternalError().initCause(e);");
207 out.println(" }");
208 out.println(" }");
209 }
210 out.println("}");
211 }
212
213 try {
214 createProviderFile(pkg, optionsClassName, originatingElements);
215 } catch (IOException e) {
216 processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage(), info.topDeclaringType);
217 }
218 }
219
220 private void createProviderFile(String pkg, String providerClassName, Element... originatingElements) throws IOException {
221 String filename = "META-INF/providers/" + pkg + "." + providerClassName;
222 FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", filename, originatingElements);
223 PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
224 writer.println(Options.class.getName());
225 writer.close();
226 }
227
228 protected PrintWriter createSourceFile(String pkg, String relativeName, Filer filer, Element... originatingElements) {
229 try {
230 // Ensure Unix line endings to comply with Graal code style guide checked by Checkstyle
231 JavaFileObject sourceFile = filer.createSourceFile(pkg + "." + relativeName, originatingElements);
232 return new PrintWriter(sourceFile.openWriter()) {
233
234 @Override
235 public void println() {
236 print("\n");
237 }
238 };
239 } catch (IOException e) {
240 throw new RuntimeException(e);
241 }
242 }
243
244 static class OptionInfo implements Comparable<OptionInfo> {
245
246 final String name;
247 final String help;
248 final String type;
249 final String declaringClass;
250 final VariableElement field;
251
252 public OptionInfo(String name, String help, String type, String declaringClass, VariableElement field) {
253 this.name = name;
254 this.help = help;
255 this.type = type;
256 this.declaringClass = declaringClass;
257 this.field = field;
258 }
259
260 @Override
261 public int compareTo(OptionInfo other) {
262 return name.compareTo(other.name);
263 }
264
265 @Override
266 public String toString() {
267 return declaringClass + "." + field;
268 }
269 }
270
271 static class OptionsInfo {
272
273 final Element topDeclaringType;
274 final List<OptionInfo> options = new ArrayList<>();
275 final Set<Element> originatingElements = new HashSet<>();
276
277 public OptionsInfo(Element topDeclaringType) {
278 this.topDeclaringType = topDeclaringType;
279 }
280 }
281
282 private static Element topDeclaringType(Element element) {
283 Element enclosing = element.getEnclosingElement();
284 if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) {
285 assert element.getKind() == ElementKind.CLASS || element.getKind() == ElementKind.INTERFACE;
286 return element;
287 }
288 return topDeclaringType(enclosing);
289 }
290
291 @Override
292 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
293 if (roundEnv.processingOver()) {
294 return true;
295 }
296
297 Map<Element, OptionsInfo> map = new HashMap<>();
298 for (Element element : roundEnv.getElementsAnnotatedWith(Option.class)) {
299 if (!processed.contains(element)) {
300 processed.add(element);
301 Element topDeclaringType = topDeclaringType(element);
302 OptionsInfo options = map.get(topDeclaringType);
303 if (options == null) {
304 options = new OptionsInfo(topDeclaringType);
305 map.put(topDeclaringType, options);
306 }
307 processElement(element, options);
308 }
309 }
310
311 boolean ok = true;
312 Map<String, OptionInfo> uniqueness = new HashMap<>();
313 for (OptionsInfo info : map.values()) {
314 for (OptionInfo option : info.options) {
315 OptionInfo conflict = uniqueness.put(option.name, option);
316 if (conflict != null) {
317 processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate option names for " + option + " and " + conflict, option.field);
318 ok = false;
319 }
320 }
321 }
322
323 if (ok) {
324 for (OptionsInfo info : map.values()) {
325 createFiles(info);
326 }
327 }
328
329 return true;
330 }
331 }