--- /dev/null 2016-05-31 09:42:47.975716356 -0700 +++ new/src/jdk.vm.compiler/share/classes/org.graalvm.compiler.options.processor/src/org/graalvm/compiler/options/processor/OptionProcessor.java 2016-12-09 00:56:07.499864945 -0800 @@ -0,0 +1,369 @@ +/* + * 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 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 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 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 { + + 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 options = new ArrayList<>(); + final Set 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 annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return true; + } + + Map 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 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; + } +}