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