1 /* 2 * Copyright (c) 2013, 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 24 25 package org.graalvm.compiler.options.processor; 26 27 import java.io.BufferedReader; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.io.PrintWriter; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 import javax.annotation.processing.Filer; 40 import javax.annotation.processing.RoundEnvironment; 41 import javax.annotation.processing.SupportedAnnotationTypes; 42 import javax.lang.model.SourceVersion; 43 import javax.lang.model.element.AnnotationMirror; 44 import javax.lang.model.element.Element; 45 import javax.lang.model.element.ElementKind; 46 import javax.lang.model.element.Modifier; 47 import javax.lang.model.element.Name; 48 import javax.lang.model.element.PackageElement; 49 import javax.lang.model.element.TypeElement; 50 import javax.lang.model.element.VariableElement; 51 import javax.lang.model.type.DeclaredType; 52 import javax.lang.model.type.TypeKind; 53 import javax.lang.model.type.TypeMirror; 54 import javax.lang.model.util.Types; 55 import javax.tools.Diagnostic.Kind; 56 import javax.tools.FileObject; 57 import javax.tools.JavaFileObject; 58 import javax.tools.StandardLocation; 59 60 import org.graalvm.compiler.processor.AbstractProcessor; 61 62 /** 63 * Processes static fields annotated with {@code Option}. An {@code OptionDescriptors} 64 * implementation is generated for each top level class containing at least one such field. The name 65 * of the generated class for top level class {@code com.foo.Bar} is 66 * {@code com.foo.Bar_OptionDescriptors}. 67 */ 68 @SupportedAnnotationTypes({"org.graalvm.compiler.options.Option"}) 69 public class OptionProcessor extends AbstractProcessor { 70 71 private static final String OPTION_CLASS_NAME = "org.graalvm.compiler.options.Option"; 72 private static final String OPTION_KEY_CLASS_NAME = "org.graalvm.compiler.options.OptionKey"; 73 private static final String OPTION_TYPE_CLASS_NAME = "org.graalvm.compiler.options.OptionType"; 74 private static final String OPTION_DESCRIPTOR_CLASS_NAME = "org.graalvm.compiler.options.OptionDescriptor"; 75 private static final String OPTION_DESCRIPTORS_CLASS_NAME = "org.graalvm.compiler.options.OptionDescriptors"; 76 77 @Override 78 public SourceVersion getSupportedSourceVersion() { 79 return SourceVersion.latest(); 80 } 81 82 private final Set<Element> processed = new HashSet<>(); 83 84 private TypeMirror optionTypeMirror; 85 private TypeMirror optionKeyTypeMirror; 86 87 private void processElement(Element element, OptionsInfo info) { 88 89 if (!element.getModifiers().contains(Modifier.STATIC)) { 90 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element); 91 return; 92 } 93 if (element.getModifiers().contains(Modifier.PRIVATE)) { 94 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field cannot be private", element); 95 return; 96 } 97 98 AnnotationMirror annotation = getAnnotation(element, optionTypeMirror); 99 assert annotation != null; 100 assert element instanceof VariableElement; 101 assert element.getKind() == ElementKind.FIELD; 102 VariableElement field = (VariableElement) element; 103 String fieldName = field.getSimpleName().toString(); 104 105 Types types = processingEnv.getTypeUtils(); 106 107 TypeMirror fieldType = field.asType(); 108 if (fieldType.getKind() != TypeKind.DECLARED) { 109 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be of type " + OPTION_KEY_CLASS_NAME, element); 110 return; 111 } 112 DeclaredType declaredFieldType = (DeclaredType) fieldType; 113 114 if (!types.isSubtype(fieldType, types.erasure(optionKeyTypeMirror))) { 115 String msg = String.format("Option field type %s is not a subclass of %s", fieldType, optionKeyTypeMirror); 116 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); 117 return; 118 } 119 120 if (!field.getModifiers().contains(Modifier.STATIC)) { 121 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element); 122 return; 123 } 124 if (field.getModifiers().contains(Modifier.PRIVATE)) { 125 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field cannot be private", element); 126 return; 127 } 128 129 String optionName = getAnnotationValue(annotation, "name", String.class); 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 an upper case letter", element); 136 return; 137 } 138 139 DeclaredType declaredOptionKeyType = declaredFieldType; 140 while (!types.isSameType(types.erasure(declaredOptionKeyType), types.erasure(optionKeyTypeMirror))) { 141 List<? extends TypeMirror> directSupertypes = types.directSupertypes(declaredFieldType); 142 assert !directSupertypes.isEmpty(); 143 declaredOptionKeyType = (DeclaredType) directSupertypes.get(0); 144 } 145 146 assert !declaredOptionKeyType.getTypeArguments().isEmpty(); 147 String optionType = declaredOptionKeyType.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 PackageElement enclosingPackage = null; 158 while (enclosing != null) { 159 if (enclosing.getKind() == ElementKind.CLASS || enclosing.getKind() == ElementKind.INTERFACE) { 160 if (enclosing.getModifiers().contains(Modifier.PRIVATE)) { 161 String msg = String.format("Option field cannot be declared in a private %s %s", enclosing.getKind().name().toLowerCase(), enclosing); 162 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); 163 return; 164 } 165 originatingElementsList.add(enclosing); 166 declaringClass = enclosing.getSimpleName() + separator + declaringClass; 167 separator = "."; 168 } else if (enclosing.getKind() == ElementKind.PACKAGE) { 169 enclosingPackage = (PackageElement) enclosing; 170 } 171 enclosing = enclosing.getEnclosingElement(); 172 } 173 if (enclosingPackage == null) { 174 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field cannot be declared in the unnamed package", element); 175 return; 176 } 177 List<String> helpValue = getAnnotationValueList(annotation, "help", String.class); 178 String help = ""; 179 List<String> extraHelp = new ArrayList<>(); 180 181 if (helpValue.size() == 1) { 182 help = helpValue.get(0); 183 if (help.startsWith("file:")) { 184 String path = help.substring("file:".length()); 185 Filer filer = processingEnv.getFiler(); 186 try { 187 FileObject file; 188 try { 189 file = filer.getResource(StandardLocation.SOURCE_PATH, enclosingPackage.getQualifiedName(), path); 190 } catch (IllegalArgumentException | IOException e) { 191 // Handle the case when a compiler doesn't support the SOURCE_PATH location 192 file = filer.getResource(StandardLocation.CLASS_OUTPUT, enclosingPackage.getQualifiedName(), path); 193 } 194 try (BufferedReader br = new BufferedReader(new InputStreamReader(file.openInputStream()))) { 195 help = br.readLine(); 196 if (help == null) { 197 help = ""; 198 } 199 String line = br.readLine(); 200 while (line != null) { 201 extraHelp.add(line); 202 line = br.readLine(); 203 } 204 } 205 } catch (IOException e) { 206 String msg = String.format("Error reading %s containing the help text for option field: %s", path, e); 207 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); 208 return; 209 } 210 } 211 } else if (helpValue.size() > 1) { 212 help = helpValue.get(0); 213 extraHelp = helpValue.subList(1, helpValue.size()); 214 } 215 if (help.length() != 0) { 216 char firstChar = help.charAt(0); 217 if (!Character.isUpperCase(firstChar)) { 218 processingEnv.getMessager().printMessage(Kind.ERROR, "Option help text must start with an upper case letter", element); 219 return; 220 } 221 } 222 223 String optionTypeName = getAnnotationValue(annotation, "type", VariableElement.class).getSimpleName().toString(); 224 info.options.add(new OptionInfo(optionName, optionTypeName, help, extraHelp, optionType, declaringClass, field)); 225 } 226 227 private void createFiles(OptionsInfo info) { 228 String pkg = ((PackageElement) info.topDeclaringType.getEnclosingElement()).getQualifiedName().toString(); 229 Name topDeclaringClass = info.topDeclaringType.getSimpleName(); 230 Element[] originatingElements = info.originatingElements.toArray(new Element[info.originatingElements.size()]); 231 232 createOptionsDescriptorsFile(info, pkg, topDeclaringClass, originatingElements); 233 } 234 235 private void createOptionsDescriptorsFile(OptionsInfo info, String pkg, Name topDeclaringClass, Element[] originatingElements) { 236 String optionsClassName = topDeclaringClass + "_" + getSimpleName(OPTION_DESCRIPTORS_CLASS_NAME); 237 238 Filer filer = processingEnv.getFiler(); 239 try (PrintWriter out = createSourceFile(pkg, optionsClassName, filer, originatingElements)) { 240 241 out.println("// CheckStyle: stop header check"); 242 out.println("// CheckStyle: stop line length check"); 243 out.println("// GENERATED CONTENT - DO NOT EDIT"); 244 out.println("// Source: " + topDeclaringClass + ".java"); 245 out.println("package " + pkg + ";"); 246 out.println(""); 247 out.println("import java.util.*;"); 248 out.println("import " + getPackageName(OPTION_DESCRIPTORS_CLASS_NAME) + ".*;"); 249 out.println("import " + OPTION_TYPE_CLASS_NAME + ";"); 250 out.println(""); 251 out.println("public class " + optionsClassName + " implements " + getSimpleName(OPTION_DESCRIPTORS_CLASS_NAME) + " {"); 252 253 String desc = getSimpleName(OPTION_DESCRIPTOR_CLASS_NAME); 254 255 Collections.sort(info.options); 256 257 out.println(" @Override"); 258 out.println(" public OptionDescriptor get(String value) {"); 259 out.println(" switch (value) {"); 260 out.println(" // CheckStyle: stop line length check"); 261 for (OptionInfo option : info.options) { 262 String name = option.name; 263 String optionField; 264 if (option.field.getModifiers().contains(Modifier.PRIVATE)) { 265 throw new InternalError(); 266 } else { 267 optionField = option.declaringClass + "." + option.field.getSimpleName(); 268 } 269 out.println(" case \"" + name + "\": {"); 270 String optionType = option.optionType; 271 String type = option.type; 272 String help = option.help; 273 List<String> extraHelp = option.extraHelp; 274 String declaringClass = option.declaringClass; 275 Name fieldName = option.field.getSimpleName(); 276 out.printf(" return " + desc + ".create(\n"); 277 out.printf(" /*name*/ \"%s\",\n", name); 278 out.printf(" /*optionType*/ %s.%s,\n", getSimpleName(OPTION_TYPE_CLASS_NAME), optionType); 279 out.printf(" /*optionValueType*/ %s.class,\n", type); 280 out.printf(" /*help*/ \"%s\",\n", help); 281 if (extraHelp.size() != 0) { 282 out.printf(" /*extraHelp*/ new String[] {\n"); 283 for (String line : extraHelp) { 284 out.printf(" \"%s\",\n", line.replace("\\", "\\\\").replace("\"", "\\\"")); 285 } 286 out.printf(" },\n"); 287 } 288 out.printf(" /*declaringClass*/ %s.class,\n", declaringClass); 289 out.printf(" /*fieldName*/ \"%s\",\n", fieldName); 290 out.printf(" /*option*/ %s);\n", optionField); 291 out.println(" }"); 292 } 293 out.println(" // CheckStyle: resume line length check"); 294 out.println(" }"); 295 out.println(" return null;"); 296 out.println(" }"); 297 out.println(); 298 out.println(" @Override"); 299 out.println(" public Iterator<" + desc + "> iterator() {"); 300 out.println(" return new Iterator<OptionDescriptor>() {"); 301 out.println(" int i = 0;"); 302 out.println(" @Override"); 303 out.println(" public boolean hasNext() {"); 304 out.println(" return i < " + info.options.size() + ";"); 305 out.println(" }"); 306 out.println(" @Override"); 307 out.println(" public OptionDescriptor next() {"); 308 out.println(" switch (i++) {"); 309 for (int i = 0; i < info.options.size(); i++) { 310 OptionInfo option = info.options.get(i); 311 out.println(" case " + i + ": return get(\"" + option.name + "\");"); 312 } 313 out.println(" }"); 314 out.println(" throw new NoSuchElementException();"); 315 out.println(" }"); 316 out.println(" };"); 317 out.println(" }"); 318 out.println("}"); 319 } 320 } 321 322 protected PrintWriter createSourceFile(String pkg, String relativeName, Filer filer, Element... originatingElements) { 323 try { 324 // Ensure Unix line endings to comply with code style guide checked by Checkstyle 325 JavaFileObject sourceFile = filer.createSourceFile(pkg + "." + relativeName, originatingElements); 326 return new PrintWriter(sourceFile.openWriter()) { 327 328 @Override 329 public void println() { 330 print("\n"); 331 } 332 }; 333 } catch (IOException e) { 334 throw new RuntimeException(e); 335 } 336 } 337 338 static class OptionInfo implements Comparable<OptionInfo> { 339 340 final String name; 341 final String optionType; 342 final String help; 343 final List<String> extraHelp; 344 final String type; 345 final String declaringClass; 346 final VariableElement field; 347 348 OptionInfo(String name, String optionType, String help, List<String> extraHelp, String type, String declaringClass, VariableElement field) { 349 this.name = name; 350 this.optionType = optionType; 351 this.help = help; 352 this.extraHelp = extraHelp; 353 this.type = type; 354 this.declaringClass = declaringClass; 355 this.field = field; 356 } 357 358 @Override 359 public int compareTo(OptionInfo other) { 360 return name.compareTo(other.name); 361 } 362 363 @Override 364 public String toString() { 365 return declaringClass + "." + field; 366 } 367 } 368 369 static class OptionsInfo { 370 371 final Element topDeclaringType; 372 final List<OptionInfo> options = new ArrayList<>(); 373 final Set<Element> originatingElements = new HashSet<>(); 374 375 OptionsInfo(Element topDeclaringType) { 376 this.topDeclaringType = topDeclaringType; 377 } 378 } 379 380 private static Element topDeclaringType(Element element) { 381 Element enclosing = element.getEnclosingElement(); 382 if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) { 383 assert element.getKind() == ElementKind.CLASS || element.getKind() == ElementKind.INTERFACE; 384 return element; 385 } 386 return topDeclaringType(enclosing); 387 } 388 389 @Override 390 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 391 if (roundEnv.processingOver()) { 392 return true; 393 } 394 395 TypeElement optionTypeElement = getTypeElement(OPTION_CLASS_NAME); 396 397 optionTypeMirror = optionTypeElement.asType(); 398 optionKeyTypeMirror = getTypeElement(OPTION_KEY_CLASS_NAME).asType(); 399 400 Map<Element, OptionsInfo> map = new HashMap<>(); 401 for (Element element : roundEnv.getElementsAnnotatedWith(optionTypeElement)) { 402 if (!processed.contains(element)) { 403 processed.add(element); 404 Element topDeclaringType = topDeclaringType(element); 405 OptionsInfo options = map.get(topDeclaringType); 406 if (options == null) { 407 options = new OptionsInfo(topDeclaringType); 408 map.put(topDeclaringType, options); 409 } 410 if (!element.getEnclosingElement().getSimpleName().toString().endsWith("Options")) { 411 processingEnv.getMessager().printMessage(Kind.ERROR, "Option declaring classes must have a name that ends with 'Options'", element.getEnclosingElement()); 412 } 413 processElement(element, options); 414 } 415 } 416 417 boolean ok = true; 418 Map<String, OptionInfo> uniqueness = new HashMap<>(); 419 for (OptionsInfo info : map.values()) { 420 for (OptionInfo option : info.options) { 421 OptionInfo conflict = uniqueness.put(option.name, option); 422 if (conflict != null) { 423 processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate option names for " + option + " and " + conflict, option.field); 424 ok = false; 425 } 426 } 427 } 428 429 if (ok) { 430 for (OptionsInfo info : map.values()) { 431 createFiles(info); 432 } 433 } 434 435 return true; 436 } 437 }