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