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.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 jdk.vm.ci.options.Option; 56 import jdk.vm.ci.options.OptionDescriptor; 57 import jdk.vm.ci.options.OptionDescriptors; 58 import jdk.vm.ci.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({"jdk.vm.ci.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 83 Option annotation = element.getAnnotation(Option.class); 84 assert annotation != null; 85 assert element instanceof VariableElement; 86 assert element.getKind() == ElementKind.FIELD; 87 VariableElement field = (VariableElement) element; 88 String fieldName = field.getSimpleName().toString(); 89 90 Elements elements = processingEnv.getElementUtils(); 91 Types types = processingEnv.getTypeUtils(); 92 93 TypeMirror fieldType = field.asType(); 94 if (fieldType.getKind() != TypeKind.DECLARED) { 95 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be of type " + OptionValue.class.getName(), element); 96 return; 97 } 98 DeclaredType declaredFieldType = (DeclaredType) fieldType; 99 100 TypeMirror optionValueType = elements.getTypeElement(OptionValue.class.getName()).asType(); 101 if (!types.isSubtype(fieldType, types.erasure(optionValueType))) { 102 String msg = String.format("Option field type %s is not a subclass of %s", fieldType, optionValueType); 103 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); 104 return; 105 } 106 107 if (!field.getModifiers().contains(Modifier.STATIC)) { 108 processingEnv.getMessager().printMessage(Kind.ERROR, "Option field must be static", element); 109 return; 110 } 111 112 String help = annotation.help(); 113 if (help.length() != 0) { 114 char firstChar = help.charAt(0); 115 if (!Character.isUpperCase(firstChar)) { 116 processingEnv.getMessager().printMessage(Kind.ERROR, "Option help text must start with upper case letter", element); 117 return; 118 } 119 } 120 121 String optionName = annotation.name(); 122 if (optionName.equals("")) { 123 optionName = fieldName; 124 } 125 126 DeclaredType declaredOptionValueType = declaredFieldType; 127 while (!types.isSameType(types.erasure(declaredOptionValueType), types.erasure(optionValueType))) { 128 List<? extends TypeMirror> directSupertypes = types.directSupertypes(declaredFieldType); 129 assert !directSupertypes.isEmpty(); 130 declaredOptionValueType = (DeclaredType) directSupertypes.get(0); 131 } 132 133 assert !declaredOptionValueType.getTypeArguments().isEmpty(); 134 String optionType = declaredOptionValueType.getTypeArguments().get(0).toString(); 135 if (optionType.startsWith("java.lang.")) { 136 optionType = optionType.substring("java.lang.".length()); 137 } 138 139 Element enclosing = element.getEnclosingElement(); 140 String declaringClass = ""; 141 String separator = ""; 142 Set<Element> originatingElementsList = info.originatingElements; 143 originatingElementsList.add(field); 144 while (enclosing != null) { 145 if (enclosing.getKind() == ElementKind.CLASS || enclosing.getKind() == ElementKind.INTERFACE) { 146 if (enclosing.getModifiers().contains(Modifier.PRIVATE)) { 147 String msg = String.format("Option field cannot be declared in a private %s %s", enclosing.getKind().name().toLowerCase(), enclosing); 148 processingEnv.getMessager().printMessage(Kind.ERROR, msg, element); 149 return; 150 } 151 originatingElementsList.add(enclosing); 152 declaringClass = enclosing.getSimpleName() + separator + declaringClass; 153 separator = "."; 154 } else { 155 assert enclosing.getKind() == ElementKind.PACKAGE; 156 } 157 enclosing = enclosing.getEnclosingElement(); 158 } 159 160 info.options.add(new OptionInfo(optionName, help, optionType, declaringClass, field)); 161 } 162 163 private void createFiles(OptionsInfo info) { 164 String pkg = ((PackageElement) info.topDeclaringType.getEnclosingElement()).getQualifiedName().toString(); 165 Name topDeclaringClass = info.topDeclaringType.getSimpleName(); 166 Element[] originatingElements = info.originatingElements.toArray(new Element[info.originatingElements.size()]); 167 168 createOptionsDescriptorsFile(info, pkg, topDeclaringClass, originatingElements); 169 } 170 171 private void createOptionsDescriptorsFile(OptionsInfo info, String pkg, Name topDeclaringClass, Element[] originatingElements) { 172 String optionsClassName = topDeclaringClass + "_" + OptionDescriptors.class.getSimpleName(); 173 174 Filer filer = processingEnv.getFiler(); 175 try (PrintWriter out = createSourceFile(pkg, optionsClassName, filer, originatingElements)) { 176 177 out.println("// CheckStyle: stop header check"); 178 out.println("// CheckStyle: stop line length check"); 179 out.println("// GENERATED CONTENT - DO NOT EDIT"); 180 out.println("// Source: " + topDeclaringClass + ".java"); 181 out.println("package " + pkg + ";"); 182 out.println(""); 183 out.println("import java.util.*;"); 184 out.println("import " + OptionDescriptors.class.getPackage().getName() + ".*;"); 185 out.println(""); 186 out.println("public class " + optionsClassName + " implements " + OptionDescriptors.class.getSimpleName() + " {"); 187 188 String desc = OptionDescriptor.class.getSimpleName(); 189 190 boolean needPrivateFieldAccessor = false; 191 int i = 0; 192 Collections.sort(info.options); 193 194 out.println(" @Override"); 195 out.println(" public OptionDescriptor get(String value) {"); 196 out.println(" // CheckStyle: stop line length check"); 197 if (info.options.size() == 1) { 198 out.println(" if (value.equals(\"" + info.options.get(0).name + "\")) {"); 199 } else { 200 out.println(" switch (value) {"); 201 } 202 for (OptionInfo option : info.options) { 203 String name = option.name; 204 String optionValue; 205 if (option.field.getModifiers().contains(Modifier.PRIVATE)) { 206 needPrivateFieldAccessor = true; 207 optionValue = "field(" + option.declaringClass + ".class, \"" + option.field.getSimpleName() + "\")"; 208 } else { 209 optionValue = option.declaringClass + "." + option.field.getSimpleName(); 210 } 211 String type = option.type; 212 String help = option.help; 213 String declaringClass = option.declaringClass; 214 Name fieldName = option.field.getSimpleName(); 215 if (info.options.size() == 1) { 216 out.printf(" return %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s);\n", desc, name, type, help, declaringClass, fieldName, optionValue); 217 } else { 218 out.printf(" case \"" + name + "\": return %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s);\n", desc, name, type, help, declaringClass, fieldName, optionValue); 219 } 220 } 221 out.println(" }"); 222 out.println(" // CheckStyle: resume line length check"); 223 out.println(" return null;"); 224 out.println(" }"); 225 out.println(); 226 out.println(" @Override"); 227 out.println(" public Iterator<" + desc + "> iterator() {"); 228 out.println(" // CheckStyle: stop line length check"); 229 out.println(" List<" + desc + "> options = Arrays.asList("); 230 for (OptionInfo option : info.options) { 231 String optionValue; 232 if (option.field.getModifiers().contains(Modifier.PRIVATE)) { 233 needPrivateFieldAccessor = true; 234 optionValue = "field(" + option.declaringClass + ".class, \"" + option.field.getSimpleName() + "\")"; 235 } else { 236 optionValue = option.declaringClass + "." + option.field.getSimpleName(); 237 } 238 String name = option.name; 239 String type = option.type; 240 String help = option.help; 241 String declaringClass = option.declaringClass; 242 Name fieldName = option.field.getSimpleName(); 243 String comma = i == info.options.size() - 1 ? "" : ","; 244 out.printf(" %s.create(\"%s\", %s.class, \"%s\", %s.class, \"%s\", %s)%s\n", desc, name, type, help, declaringClass, fieldName, optionValue, comma); 245 i++; 246 } 247 out.println(" );"); 248 out.println(" // CheckStyle: resume line length check"); 249 out.println(" return options.iterator();"); 250 out.println(" }"); 251 if (needPrivateFieldAccessor) { 252 out.println(" private static " + OptionValue.class.getSimpleName() + "<?> field(Class<?> declaringClass, String fieldName) {"); 253 out.println(" try {"); 254 out.println(" java.lang.reflect.Field field = declaringClass.getDeclaredField(fieldName);"); 255 out.println(" field.setAccessible(true);"); 256 out.println(" return (" + OptionValue.class.getSimpleName() + "<?>) field.get(null);"); 257 out.println(" } catch (Exception e) {"); 258 out.println(" throw (InternalError) new InternalError().initCause(e);"); 259 out.println(" }"); 260 out.println(" }"); 261 } 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 public 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 public 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 }