| /* |
| * Copyright (c) 2013, 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.options.processor; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.Filer; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.annotation.processing.SupportedAnnotationTypes; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.PackageElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic.Kind; |
| import javax.tools.JavaFileObject; |
| |
| import org.graalvm.compiler.options.Option; |
| import org.graalvm.compiler.options.OptionDescriptor; |
| import org.graalvm.compiler.options.OptionDescriptors; |
| import org.graalvm.compiler.options.OptionValue; |
| |
| /** |
| * Processes static fields annotated with {@link Option}. An {@link OptionDescriptors} |
| * implementation is generated for each top level class containing at least one such field. The name |
| * of the generated class for top level class {@code com.foo.Bar} is |
| * {@code com.foo.Bar_OptionDescriptors}. |
| */ |
| @SupportedAnnotationTypes({"org.graalvm.compiler.options.Option"}) |
| public class OptionProcessor extends AbstractProcessor { |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latest(); |
| } |
| |
| private final Set<Element> processed = new HashSet<>(); |
| |
| private void processElement(Element element, OptionsInfo info) { |
| |
| if (!element.getModifiers().contains(Modifier.STATIC)) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element); |
| return; |
| } |
| if (element.getModifiers().contains(Modifier.PRIVATE)) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option field cannot be private", element); |
| return; |
| } |
| |
| Option annotation = element.getAnnotation(Option.class); |
| assert annotation != null; |
| assert element instanceof VariableElement; |
| assert element.getKind() == ElementKind.FIELD; |
| VariableElement field = (VariableElement) element; |
| String fieldName = field.getSimpleName().toString(); |
| |
| Elements elements = processingEnv.getElementUtils(); |
| Types types = processingEnv.getTypeUtils(); |
| |
| TypeMirror fieldType = field.asType(); |
| if (fieldType.getKind() != TypeKind.DECLARED) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be of type " + OptionValue.class.getName(), element); |
| return; |
| } |
| DeclaredType declaredFieldType = (DeclaredType) fieldType; |
| |
| TypeMirror optionValueType = elements.getTypeElement(OptionValue.class.getName()).asType(); |
| if (!types.isSubtype(fieldType, types.erasure(optionValueType))) { |
| String msg = String.format("Option field type %s is not a subclass of %s", fieldType, optionValueType); |
| processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); |
| return; |
| } |
| |
| if (!field.getModifiers().contains(Modifier.STATIC)) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element); |
| return; |
| } |
| if (field.getModifiers().contains(Modifier.PRIVATE)) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option field cannot be private", element); |
| return; |
| } |
| |
| String help = annotation.help(); |
| if (help.length() != 0) { |
| char firstChar = help.charAt(0); |
| if (!Character.isUpperCase(firstChar)) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option help text must start with upper case letter", element); |
| return; |
| } |
| } |
| |
| String optionName = annotation.name(); |
| if (optionName.equals("")) { |
| optionName = fieldName; |
| } |
| |
| if (!Character.isUpperCase(optionName.charAt(0))) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Option name must start with capital letter", element); |
| return; |
| } |
| |
| DeclaredType declaredOptionValueType = declaredFieldType; |
| while (!types.isSameType(types.erasure(declaredOptionValueType), types.erasure(optionValueType))) { |
| List<? extends TypeMirror> directSupertypes = types.directSupertypes(declaredFieldType); |
| assert !directSupertypes.isEmpty(); |
| declaredOptionValueType = (DeclaredType) directSupertypes.get(0); |
| } |
| |
| assert !declaredOptionValueType.getTypeArguments().isEmpty(); |
| String optionType = declaredOptionValueType.getTypeArguments().get(0).toString(); |
| if (optionType.startsWith("java.lang.")) { |
| optionType = optionType.substring("java.lang.".length()); |
| } |
| |
| Element enclosing = element.getEnclosingElement(); |
| String declaringClass = ""; |
| String separator = ""; |
| Set<Element> originatingElementsList = info.originatingElements; |
| originatingElementsList.add(field); |
| while (enclosing != null) { |
| if (enclosing.getKind() == ElementKind.CLASS || enclosing.getKind() == ElementKind.INTERFACE) { |
| if (enclosing.getModifiers().contains(Modifier.PRIVATE)) { |
| String msg = String.format("Option field cannot be declared in a private %s %s", enclosing.getKind().name().toLowerCase(), enclosing); |
| processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); |
| return; |
| } |
| originatingElementsList.add(enclosing); |
| declaringClass = enclosing.getSimpleName() + separator + declaringClass; |
| separator = "."; |
| } else { |
| assert enclosing.getKind() == ElementKind.PACKAGE; |
| } |
| enclosing = enclosing.getEnclosingElement(); |
| } |
| |
| info.options.add(new OptionInfo(optionName, help, optionType, declaringClass, field)); |
| } |
| |
| private void createFiles(OptionsInfo info) { |
| String pkg = ((PackageElement) info.topDeclaringType.getEnclosingElement()).getQualifiedName().toString(); |
| Name topDeclaringClass = info.topDeclaringType.getSimpleName(); |
| Element[] originatingElements = info.originatingElements.toArray(new Element[info.originatingElements.size()]); |
| |
| createOptionsDescriptorsFile(info, pkg, topDeclaringClass, originatingElements); |
| } |
| |
| private void createOptionsDescriptorsFile(OptionsInfo info, String pkg, Name topDeclaringClass, Element[] originatingElements) { |
| String optionsClassName = topDeclaringClass + "_" + OptionDescriptors.class.getSimpleName(); |
| |
| Filer filer = processingEnv.getFiler(); |
| try (PrintWriter out = createSourceFile(pkg, optionsClassName, filer, originatingElements)) { |
| |
| out.println("// CheckStyle: stop header check"); |
| out.println("// CheckStyle: stop line length check"); |
| out.println("// GENERATED CONTENT - DO NOT EDIT"); |
| out.println("// Source: " + topDeclaringClass + ".java"); |
| out.println("package " + pkg + ";"); |
| out.println(""); |
| out.println("import java.util.*;"); |
| out.println("import " + OptionDescriptors.class.getPackage().getName() + ".*;"); |
| out.println(""); |
| out.println("public class " + optionsClassName + " implements " + OptionDescriptors.class.getSimpleName() + " {"); |
| |
| String desc = OptionDescriptor.class.getSimpleName(); |
| |
| int i = 0; |
| Collections.sort(info.options); |
| |
| out.println(" @Override"); |
| out.println(" public OptionDescriptor get(String value) {"); |
| out.println(" // CheckStyle: stop line length check"); |
| if (info.options.size() == 1) { |
| out.println(" if (value.equals(\"" + info.options.get(0).name + "\")) {"); |
| } else { |
| out.println(" switch (value) {"); |
| } |
| for (OptionInfo option : info.options) { |
| String name = option.name; |
| String optionValue; |
| if (option.field.getModifiers().contains(Modifier.PRIVATE)) { |
| throw new InternalError(); |
| } else { |
| optionValue = option.declaringClass + "." + option.field.getSimpleName(); |
| } |
| String type = option.type; |
| String help = option.help; |
| String declaringClass = option.declaringClass; |
| Name fieldName = option.field.getSimpleName(); |
| if (info.options.size() == 1) { |
| out.printf(" return %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s);\n", desc, name, type, help, declaringClass, fieldName, optionValue); |
| } else { |
| out.printf(" case \"" + name + "\": return %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s);\n", desc, name, type, help, declaringClass, fieldName, |
| optionValue); |
| } |
| } |
| out.println(" }"); |
| out.println(" // CheckStyle: resume line length check"); |
| out.println(" return null;"); |
| out.println(" }"); |
| out.println(); |
| out.println(" @Override"); |
| out.println(" public Iterator<" + desc + "> iterator() {"); |
| out.println(" // CheckStyle: stop line length check"); |
| out.println(" List<" + desc + "> options = Arrays.asList("); |
| for (OptionInfo option : info.options) { |
| String optionValue; |
| if (option.field.getModifiers().contains(Modifier.PRIVATE)) { |
| throw new InternalError(); |
| } else { |
| optionValue = option.declaringClass + "." + option.field.getSimpleName(); |
| } |
| String name = option.name; |
| String type = option.type; |
| String help = option.help; |
| String declaringClass = option.declaringClass; |
| Name fieldName = option.field.getSimpleName(); |
| String comma = i == info.options.size() - 1 ? "" : ","; |
| out.printf(" %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s)%s\n", desc, name, type, help, declaringClass, fieldName, optionValue, comma); |
| i++; |
| } |
| out.println(" );"); |
| out.println(" // CheckStyle: resume line length check"); |
| out.println(" return options.iterator();"); |
| out.println(" }"); |
| out.println("}"); |
| } |
| } |
| |
| protected PrintWriter createSourceFile(String pkg, String relativeName, Filer filer, Element... originatingElements) { |
| try { |
| // Ensure Unix line endings to comply with code style guide checked by Checkstyle |
| JavaFileObject sourceFile = filer.createSourceFile(pkg + "." + relativeName, originatingElements); |
| return new PrintWriter(sourceFile.openWriter()) { |
| |
| @Override |
| public void println() { |
| print("\n"); |
| } |
| }; |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| static class OptionInfo implements Comparable<OptionInfo> { |
| |
| final String name; |
| final String help; |
| final String type; |
| final String declaringClass; |
| final VariableElement field; |
| |
| OptionInfo(String name, String help, String type, String declaringClass, VariableElement field) { |
| this.name = name; |
| this.help = help; |
| this.type = type; |
| this.declaringClass = declaringClass; |
| this.field = field; |
| } |
| |
| @Override |
| public int compareTo(OptionInfo other) { |
| return name.compareTo(other.name); |
| } |
| |
| @Override |
| public String toString() { |
| return declaringClass + "." + field; |
| } |
| } |
| |
| static class OptionsInfo { |
| |
| final Element topDeclaringType; |
| final List<OptionInfo> options = new ArrayList<>(); |
| final Set<Element> originatingElements = new HashSet<>(); |
| |
| OptionsInfo(Element topDeclaringType) { |
| this.topDeclaringType = topDeclaringType; |
| } |
| } |
| |
| private static Element topDeclaringType(Element element) { |
| Element enclosing = element.getEnclosingElement(); |
| if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) { |
| assert element.getKind() == ElementKind.CLASS || element.getKind() == ElementKind.INTERFACE; |
| return element; |
| } |
| return topDeclaringType(enclosing); |
| } |
| |
| @Override |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| if (roundEnv.processingOver()) { |
| return true; |
| } |
| |
| Map<Element, OptionsInfo> map = new HashMap<>(); |
| for (Element element : roundEnv.getElementsAnnotatedWith(Option.class)) { |
| if (!processed.contains(element)) { |
| processed.add(element); |
| Element topDeclaringType = topDeclaringType(element); |
| OptionsInfo options = map.get(topDeclaringType); |
| if (options == null) { |
| options = new OptionsInfo(topDeclaringType); |
| map.put(topDeclaringType, options); |
| } |
| processElement(element, options); |
| } |
| } |
| |
| boolean ok = true; |
| Map<String, OptionInfo> uniqueness = new HashMap<>(); |
| for (OptionsInfo info : map.values()) { |
| for (OptionInfo option : info.options) { |
| OptionInfo conflict = uniqueness.put(option.name, option); |
| if (conflict != null) { |
| processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate option names for " + option + " and " + conflict, option.field); |
| ok = false; |
| } |
| } |
| } |
| |
| if (ok) { |
| for (OptionsInfo info : map.values()) { |
| createFiles(info); |
| } |
| } |
| |
| return true; |
| } |
| } |