| package annotations.io; | |
| /*>>> | |
| import org.checkerframework.checker.nullness.qual.*; | |
| */ | |
| import static java.io.StreamTokenizer.TT_EOF; | |
| import static java.io.StreamTokenizer.TT_NUMBER; | |
| import static java.io.StreamTokenizer.TT_WORD; | |
| import java.io.FileReader; | |
| import java.io.IOException; | |
| import java.io.LineNumberReader; | |
| import java.io.Reader; | |
| import java.io.StreamTokenizer; | |
| import java.io.StringReader; | |
| import java.text.Collator; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.LinkedHashMap; | |
| import java.util.LinkedHashSet; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.TreeSet; | |
| import java.util.regex.Pattern; | |
| import com.sun.source.tree.Tree.Kind; | |
| import com.sun.tools.javac.code.TypeAnnotationPosition; | |
| import annotations.Annotation; | |
| import annotations.AnnotationBuilder; | |
| import annotations.AnnotationFactory; | |
| import annotations.Annotations; | |
| import annotations.ArrayBuilder; | |
| import annotations.el.ABlock; | |
| import annotations.el.AClass; | |
| import annotations.el.ADeclaration; | |
| import annotations.el.AElement; | |
| import annotations.el.AExpression; | |
| import annotations.el.AField; | |
| import annotations.el.AMethod; | |
| import annotations.el.AScene; | |
| import annotations.el.ATypeElement; | |
| import annotations.el.ATypeElementWithType; | |
| import annotations.el.AnnotationDef; | |
| import annotations.el.BoundLocation; | |
| import annotations.el.InnerTypeLocation; | |
| import annotations.el.LocalLocation; | |
| import annotations.el.RelativeLocation; | |
| import annotations.el.TypeIndexLocation; | |
| import annotations.field.AnnotationAFT; | |
| import annotations.field.AnnotationFieldType; | |
| import annotations.field.ArrayAFT; | |
| import annotations.field.BasicAFT; | |
| import annotations.field.ClassTokenAFT; | |
| import annotations.field.EnumAFT; | |
| import annotations.field.ScalarAFT; | |
| import annotations.util.coll.VivifyingMap; | |
| import plume.ArraysMDE; | |
| import plume.FileIOException; | |
| import plume.Pair; | |
| import type.ArrayType; | |
| import type.BoundedType; | |
| import type.BoundedType.BoundKind; | |
| import type.DeclaredType; | |
| import type.Type; | |
| /** | |
| * IndexFileParser provides static methods | |
| * {@link #parse(LineNumberReader, AScene)}, | |
| * {@link #parseFile(String, AScene)}, and | |
| * {@link #parseString(String, AScene)}. | |
| * Each of these parses an index file into a {@link AScene}. | |
| * <p> | |
| * | |
| * If there are any problems, it throws a ParseException internally, or a | |
| * FileIOException externally. | |
| */ | |
| public final class IndexFileParser { | |
| private static final String[] typeSelectors = { "bound", "identifier", | |
| "type", "typeAlternative", "typeArgument", "typeParameter", | |
| "underlyingType" }; | |
| private static boolean abbreviate = true; | |
| // The input | |
| private final StreamTokenizer st; | |
| // The output | |
| private final AScene scene; | |
| private String curPkgPrefix; | |
| /** | |
| * Holds definitions we've seen so far. Maps from annotation name to | |
| * the definition itself. Maps from both the qualified name and the | |
| * unqualified name. If the unqualified name is not unique, it maps | |
| * to null and the qualified name should be used instead. */ | |
| private final HashMap<String, AnnotationDef> defs; | |
| public static void setAbbreviate(boolean b) { | |
| abbreviate = b; | |
| } | |
| private int expectNonNegative(int i) throws ParseException { | |
| if (i >= 0) { | |
| return i; | |
| } else { | |
| throw new ParseException("Expected a nonnegative integer, got " + st); | |
| } | |
| } | |
| /** True if the next thing from st is the given character. */ | |
| private boolean checkChar(char c) { | |
| return st.ttype == c; | |
| } | |
| /** True if the next thing from st is the given string token. */ | |
| private boolean checkKeyword(String s) { | |
| return st.ttype == TT_WORD && st.sval.equals(s); | |
| } | |
| /** | |
| * Return true if the next thing to be read from st is the given string. | |
| * In that case, also read past the given string. | |
| * If the result is false, reads nothing from st. | |
| */ | |
| private boolean matchChar(char c) throws IOException { | |
| if (checkChar(c)) { | |
| st.nextToken(); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Return true if the next thing to be read from st is the given string. | |
| * In that case, also read past the given string. | |
| * If the result is false, reads nothing from st. | |
| */ | |
| private boolean matchKeyword(String s) throws IOException { | |
| if (checkKeyword(s)) { | |
| st.nextToken(); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** Reads from st. If the result is not c, throws an exception. */ | |
| private void expectChar(char c) throws IOException, ParseException { | |
| if (! matchChar(c)) { | |
| // Alternately, could use st.toString(). | |
| String found; | |
| switch (st.ttype) { | |
| case StreamTokenizer.TT_WORD: found = st.sval; break; | |
| case StreamTokenizer.TT_NUMBER: found = "" + st.nval; break; | |
| case StreamTokenizer.TT_EOL: found = "end of line"; break; | |
| case StreamTokenizer.TT_EOF: found = "end of file"; break; | |
| default: found = "'" + ((char) st.ttype) + "'"; break; | |
| } | |
| throw new ParseException("Expected '" + c + "', found " + found); | |
| } | |
| } | |
| /** Reads from st. If the result is not s, throws an exception. */ | |
| private void expectKeyword(String s) throws IOException, | |
| ParseException { | |
| if (! matchKeyword(s)) { | |
| throw new ParseException("Expected `" + s + "'"); | |
| } | |
| } | |
| private static final Set<String> knownKeywords; | |
| static { | |
| String[] knownKeywords_array = | |
| { "abstract", "assert", "boolean", "break", "byte", "case", | |
| "catch", "char", "class", "const", "continue", | |
| "default", "do", "double", "else", "enum", "extends", | |
| "false", "final", "finally", "float", "for", "if", | |
| "goto", "implements", "import", "instanceof", "int", | |
| "interface", "long", "native", "new", "null", | |
| "package", "private", "protected", "public", "return", | |
| "short", "static", "strictfp", "super", "switch", | |
| "synchronized", "this", "throw", "throws", "transient", | |
| "true", "try", "void", "volatile", "while", }; | |
| knownKeywords = new LinkedHashSet<String>(); | |
| Collections.addAll(knownKeywords, knownKeywords_array); | |
| } | |
| private boolean isValidIdentifier(String x) { | |
| if (x.length() == 0 || !Character.isJavaIdentifierStart(x.charAt(0)) | |
| || knownKeywords.contains(x)) | |
| return false; | |
| for (int i = 1; i < x.length(); i++) { | |
| if (!Character.isJavaIdentifierPart(x.charAt(i))) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| private String checkIdentifier() { | |
| if (st.sval == null) { | |
| return null; | |
| } else { | |
| String val = st.sval; | |
| if (st.ttype == TT_WORD && isValidIdentifier(val)) { | |
| return st.sval; | |
| } else { | |
| return null; | |
| } | |
| } | |
| } | |
| private String matchIdentifier() throws IOException { | |
| String x = checkIdentifier(); | |
| if (x != null) { | |
| st.nextToken(); | |
| return x; | |
| } else { | |
| return null; | |
| } | |
| } | |
| private String expectIdentifier() throws IOException, ParseException { | |
| String id = matchIdentifier(); | |
| if (id == null) { throw new ParseException("Expected an identifier"); } | |
| return id; | |
| } | |
| private String checkPrimitiveType() { | |
| if (st.sval == null) { | |
| return null; | |
| } else { | |
| String val = st.sval; | |
| if (st.ttype == TT_WORD && primitiveTypes.containsKey(val)) { | |
| return st.sval; | |
| } else { | |
| return null; | |
| } | |
| } | |
| } | |
| private String matchPrimitiveType() throws IOException { | |
| String x = checkPrimitiveType(); | |
| if (x != null) { | |
| st.nextToken(); | |
| return x; | |
| } else { | |
| return null; | |
| } | |
| } | |
| // an identifier, or a sequence of dot-separated identifiers | |
| private String expectQualifiedName() throws IOException, ParseException { | |
| String name = expectIdentifier(); | |
| while (matchChar('.')) { | |
| name += '.' + expectIdentifier(); | |
| } | |
| return name; | |
| } | |
| private int checkNNInteger() { | |
| if (st.ttype == TT_NUMBER) { | |
| int x = (int) st.nval; | |
| if (x == st.nval && x >= -1) // shouldn't give us a huge number | |
| return x; | |
| } | |
| return -1; | |
| } | |
| private int matchNNInteger() throws IOException { | |
| int x = checkNNInteger(); | |
| if (x >= -1) { | |
| st.nextToken(); | |
| return x; | |
| } else { | |
| return -1; | |
| } | |
| } | |
| // Mapping from primitive types and void to their corresponding | |
| // class objects. Class.forName doesn't directly support these. | |
| // Using this map we can go from "void.class" to the correct | |
| // Class object. | |
| private static final Map<String, Class<?>> primitiveTypes; | |
| static { | |
| Map<String, Class<?>> pt = new LinkedHashMap<String, Class<?>>(); | |
| pt.put("byte", byte.class); | |
| pt.put("short", short.class); | |
| pt.put("int", int.class); | |
| pt.put("long", long.class); | |
| pt.put("float", float.class); | |
| pt.put("double", double.class); | |
| pt.put("char", char.class); | |
| pt.put("boolean", boolean.class); | |
| pt.put("void", void.class); | |
| primitiveTypes = pt; | |
| } | |
| /** Parse scalar annotation value. */ | |
| // HMMM can a (readonly) Integer be casted to a writable Object? | |
| private Object parseScalarAFV(ScalarAFT aft) throws IOException, ParseException { | |
| if (aft instanceof BasicAFT) { | |
| Object val; | |
| BasicAFT baft = (BasicAFT) aft; | |
| Class<?> type = baft.type; | |
| if (type == boolean.class) { | |
| if (matchKeyword("true")) { | |
| val = true; | |
| } else if (matchKeyword("false")) { | |
| val = false; | |
| } else { | |
| throw new ParseException("Expected `true' or `false'"); | |
| } | |
| } else if (type == char.class) { | |
| if (st.ttype == '\'' && st.sval.length() == 1) { | |
| val = st.sval.charAt(0); | |
| } else { | |
| throw new ParseException("Expected a character literal"); | |
| } | |
| st.nextToken(); | |
| } else if (type == String.class) { | |
| if (st.ttype == '"') { | |
| val = st.sval; | |
| } else { | |
| throw new ParseException("Expected a string literal"); | |
| } | |
| st.nextToken(); | |
| } else { | |
| if (st.ttype == TT_NUMBER) { | |
| double n = st.nval; | |
| // TODO validate the literal better | |
| // HMMM StreamTokenizer can't handle all floating point | |
| // numbers; in particular, scientific notation is a problem | |
| if (type == byte.class) { | |
| val = (byte) n; | |
| } else if (type == short.class) { | |
| val = (short) n; | |
| } else if (type == int.class) { | |
| val = (int) n; | |
| } else if (type == long.class) { | |
| val = (long) n; | |
| } else if (type == float.class) { | |
| val = (float) n; | |
| } else if (type == double.class) { | |
| val = n; | |
| } else { | |
| throw new AssertionError(); | |
| } | |
| st.nextToken(); | |
| } else { | |
| throw new ParseException( | |
| "Expected a number literal"); | |
| } | |
| } | |
| assert aft.isValidValue(val); | |
| return val; | |
| } else if (aft instanceof ClassTokenAFT) { | |
| // Expect the class name in the format that Class.forName accepts, | |
| // which is some very strange format. | |
| // Example inputs followed by their Java source ".class" equivalent: | |
| // [[I.class for int[][].class | |
| // [java.util.Map for Map[].class | |
| // java.util.Map for Map.class | |
| // Have to use fully-qualified names, i.e. "Object" alone won't work. | |
| // Also note use of primitiveTypes map for primitives and void. | |
| int arrays = 0; | |
| StringBuilder type = new StringBuilder(); | |
| while (matchChar('[')) { | |
| // Array dimensions as prefix | |
| ++arrays; | |
| } | |
| while (!matchKeyword("class")) { | |
| if (st.ttype >= 0) { | |
| type.append((char) st.ttype); | |
| } else if (st.ttype == TT_WORD) { | |
| type.append(st.sval); | |
| } else { | |
| throw new ParseException("Found something that doesn't belong in a signature"); | |
| } | |
| st.nextToken(); | |
| } | |
| // Drop the '.' before the "class" | |
| type.deleteCharAt(type.length()-1); | |
| // expectKeyword("class"); | |
| // Add arrays as prefix in the type. | |
| while (arrays-->0) { | |
| type.insert(0, '['); | |
| } | |
| try { | |
| String sttype = type.toString(); | |
| Class<?> tktype; | |
| if (primitiveTypes.containsKey(sttype)) { | |
| tktype = primitiveTypes.get(sttype); | |
| } else { | |
| tktype = Class.forName(sttype); | |
| } | |
| assert aft.isValidValue(tktype); | |
| return tktype; | |
| } catch (ClassNotFoundException e) { | |
| throw new ParseException("Could not load class: " + type, e); | |
| } | |
| } else if (aft instanceof EnumAFT) { | |
| String name = expectQualifiedName(); | |
| assert aft.isValidValue(name); | |
| return name; | |
| } else if (aft instanceof AnnotationAFT) { | |
| AnnotationAFT aaft = (AnnotationAFT) aft; | |
| AnnotationDef d = parseAnnotationHead(); | |
| if (! d.name.equals(aaft.annotationDef.name)) { | |
| throw new ParseException("Got an " + d.name | |
| + " subannotation where an " + aaft.annotationDef.name | |
| + " was expected"); | |
| } | |
| AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(d); | |
| // interested in this annotation, | |
| // so should be interested in subannotations | |
| assert ab != null; | |
| AnnotationBuilder ab2 = (AnnotationBuilder) ab; | |
| Annotation suba = parseAnnotationBody(d, ab2); | |
| assert aft.isValidValue(suba); | |
| return suba; | |
| } else { | |
| throw new AssertionError("IndexFileParser.parseScalarAFV: unreachable code."); | |
| } | |
| } | |
| private void parseAndAddArrayAFV(ArrayAFT aaft, ArrayBuilder arrb) throws IOException, ParseException { | |
| ScalarAFT comp; | |
| if (aaft.elementType != null) { | |
| comp = aaft.elementType; | |
| } else { | |
| throw new IllegalArgumentException("array AFT has null elementType"); | |
| } | |
| if (matchChar('{')) { | |
| // read an array | |
| while (!matchChar('}')) { | |
| arrb.appendElement(parseScalarAFV(comp)); | |
| if (!checkChar('}')) { | |
| expectChar(','); | |
| } | |
| } | |
| } else { | |
| // not an array, so try reading just one value as an array | |
| arrb.appendElement(parseScalarAFV(comp)); | |
| } | |
| arrb.finish(); | |
| } | |
| // parses a field such as "f1=5" in "@A(f1=5, f2=10)". | |
| private void parseAnnotationField(AnnotationDef d, AnnotationBuilder ab) throws IOException, ParseException { | |
| String fieldName; | |
| if (d.fieldTypes.size() == 1 | |
| && d.fieldTypes.containsKey("value")) { | |
| fieldName = "value"; | |
| if (matchKeyword("value")) { | |
| expectChar('='); | |
| } | |
| } else { | |
| fieldName = expectIdentifier(); | |
| expectChar('='); | |
| } | |
| // HMMM let's hope the builder checks for duplicate fields | |
| // because we can't do it any more | |
| AnnotationFieldType aft1 = d.fieldTypes.get(fieldName); | |
| if (aft1 == null) { | |
| throw new ParseException("The annotation type " + d.name | |
| + " has no field called " + fieldName); | |
| } | |
| AnnotationFieldType aft = (AnnotationFieldType) aft1; | |
| if (aft instanceof ArrayAFT) { | |
| ArrayAFT aaft = (ArrayAFT) aft; | |
| if (aaft.elementType == null) { | |
| // Array of unknown element type--must be zero-length | |
| expectChar('{'); | |
| expectChar('}'); | |
| ab.addEmptyArrayField(fieldName); | |
| } else { | |
| parseAndAddArrayAFV(aaft, ab.beginArrayField(fieldName, aaft)); | |
| } | |
| } else if (aft instanceof ScalarAFT) { | |
| ScalarAFT saft = (ScalarAFT) aft; | |
| Object value = parseScalarAFV(saft); | |
| ab.addScalarField(fieldName, saft, value); | |
| } else { | |
| throw new AssertionError(); | |
| } | |
| } | |
| // reads the "@A" part of an annotation such as "@A(f1=5, f2=10)". | |
| private AnnotationDef parseAnnotationHead() throws IOException, | |
| ParseException { | |
| expectChar('@'); | |
| String name = expectQualifiedName(); | |
| AnnotationDef d = defs.get(name); | |
| if (d == null) { | |
| // System.err.println("No definition for annotation type " + name); | |
| // System.err.printf(" defs contains %d entries%n", defs.size()); | |
| // for (Map.Entry<String,AnnotationDef> entry : defs.entrySet()) { | |
| // System.err.printf(" defs entry: %s => %s%n", entry.getKey(), entry.getValue()); | |
| // } | |
| throw new ParseException("No definition for annotation type " + name); | |
| } | |
| return d; | |
| } | |
| private Annotation parseAnnotationBody(AnnotationDef d, AnnotationBuilder ab) throws IOException, ParseException { | |
| if (matchChar('(')) { | |
| parseAnnotationField(d, ab); | |
| while (matchChar(',')) { | |
| parseAnnotationField(d, ab); | |
| } | |
| expectChar(')'); | |
| } | |
| Annotation ann = ab.finish(); | |
| if (! ann.def.equals(d)) { | |
| throw new ParseException( | |
| "parseAnnotationBody: Annotation def isn't as it should be.\n" + d + "\n" + ann.def); | |
| } | |
| if (ann.def().fieldTypes.size() != d.fieldTypes.size()) { | |
| throw new ParseException( | |
| "At least one annotation field is missing"); | |
| } | |
| return ann; | |
| } | |
| private void parseAnnotations(AElement e) | |
| throws IOException, ParseException { | |
| while (checkChar('@')) { | |
| AnnotationDef d = parseAnnotationHead(); | |
| AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(d); | |
| if (ab == null) { | |
| // don't care about the result | |
| // but need to skip over it anyway | |
| @SuppressWarnings("unused") | |
| Object trash = parseAnnotationBody(d, AnnotationFactory.saf | |
| .beginAnnotation(d)); | |
| } else { | |
| Annotation a = parseAnnotationBody(d, ab); | |
| for (Annotation other : e.tlAnnotationsHere) { | |
| if (a.def.name.equals(other.def.name)) { | |
| System.err.println( | |
| "WARNING: duplicate annotation of type " | |
| + a.def().name); | |
| continue; | |
| } | |
| } | |
| Annotation tla = a; | |
| if (! tla.def.equals(d)) { | |
| throw new ParseException("Bad def"); | |
| } | |
| e.tlAnnotationsHere.add(tla); | |
| } | |
| } | |
| } | |
| private ScalarAFT parseScalarAFT() throws IOException, ParseException { | |
| for (BasicAFT baft : BasicAFT.bafts.values()) { | |
| if (matchKeyword(baft.toString())) { | |
| return baft; | |
| } | |
| } | |
| // wasn't a BasicAFT | |
| if (matchKeyword("Class")) { | |
| return ClassTokenAFT.ctaft/* dumpParameterization() */; | |
| } else if (matchKeyword("enum")) { | |
| String name = expectQualifiedName(); | |
| if (abbreviate) { | |
| int i = name.lastIndexOf('.'); | |
| if (i >= 0) { | |
| String baseName = name.substring(i+1); | |
| Set<String> set1 = scene.imports.get(name); | |
| Set<String> set2 = scene.imports.get(baseName); | |
| if (set1 == null) { | |
| set1 = new TreeSet<String>(); | |
| scene.imports.put(name, set1); | |
| } | |
| if (set2 == null) { | |
| set2 = new TreeSet<String>(); | |
| scene.imports.put(name, set2); | |
| } | |
| set1.add(name); | |
| set2.add(name); | |
| name = baseName; | |
| } | |
| } | |
| return new EnumAFT(name); | |
| } else if (matchKeyword("annotation-field")) { | |
| String name = expectQualifiedName(); | |
| AnnotationDef ad = defs.get(name); | |
| if (ad == null) { | |
| throw new ParseException("Annotation type " + name + " used as a field before it is defined"); | |
| } | |
| return new AnnotationAFT((AnnotationDef) ad); | |
| } else { | |
| throw new ParseException( | |
| "Expected the beginning of an annotation field type: " | |
| + "a primitive type, `String', `Class', `enum', or `annotation-field'. Got '" | |
| + st.sval + "'."); | |
| } | |
| } | |
| private AnnotationFieldType parseAFT() throws IOException, | |
| ParseException { | |
| if (matchKeyword("unknown")) { | |
| // Handle unknown[]; see AnnotationBuilder#addEmptyArrayField | |
| expectChar('['); | |
| expectChar(']'); | |
| return new ArrayAFT(null); | |
| } | |
| ScalarAFT baseAFT = parseScalarAFT(); | |
| // only one level of array is permitted | |
| if (matchChar('[')) { | |
| expectChar(']'); | |
| return new ArrayAFT(baseAFT); | |
| } else { | |
| return baseAFT; | |
| } | |
| } | |
| private void parseAnnotationDef() throws IOException, ParseException { | |
| expectKeyword("annotation"); | |
| expectChar('@'); | |
| String basename = expectIdentifier(); | |
| String fullName = curPkgPrefix + basename; | |
| AnnotationDef ad = new AnnotationDef(fullName); | |
| expectChar(':'); | |
| parseAnnotations(ad); | |
| Map<String, AnnotationFieldType> fields = | |
| new LinkedHashMap<String, AnnotationFieldType>(); | |
| // yuck; it would be nicer to do a positive match | |
| while (st.ttype != TT_EOF && !checkKeyword("annotation") | |
| && !checkKeyword("class") && !checkKeyword("package")) { | |
| AnnotationFieldType type = parseAFT(); | |
| String name = expectIdentifier(); | |
| if (fields.containsKey(name)) { | |
| throw new ParseException("Duplicate definition of field " | |
| + name); | |
| } | |
| fields.put(name, type); | |
| } | |
| ad.setFieldTypes(fields); | |
| // Now add the definition to the map of all definitions. | |
| addDef(ad, basename); | |
| } | |
| // Add the definition to the map of all definitions. | |
| // also see addDef(AnnotationDef, String). | |
| public void addDef(AnnotationDef ad) throws ParseException { | |
| String basename = ad.name; | |
| int dotPos = basename.lastIndexOf('.'); | |
| if (dotPos != -1) { | |
| basename = basename.substring(dotPos + 1); | |
| } | |
| addDef(ad, basename); | |
| } | |
| // Add the definition to the map of all definitions. | |
| public void addDef(AnnotationDef ad, String basename) throws ParseException { | |
| // System.out.println("addDef:" + ad); | |
| if (defs.containsKey(ad.name)) { | |
| // TODO: permit identical re-definition | |
| System.err.println("Duplicate definition of annotation type " + ad.name); | |
| } | |
| defs.put(ad.name, ad); | |
| // Add short name; but if it's already there, remove it to avoid ambiguity. | |
| if (! basename.equals(ad.name)) { | |
| if (defs.containsKey(basename)) { | |
| // not "defs.remove(basename)" because then a subsequent | |
| // one could get added, which would be wrong. | |
| defs.put(basename, null); | |
| } else { | |
| defs.put(basename, ad); | |
| } | |
| } | |
| } | |
| private void parseInnerTypes(ATypeElement e) | |
| throws IOException, ParseException { | |
| parseInnerTypes(e, 0); | |
| } | |
| private void parseInnerTypes(ATypeElement e, int offset) | |
| throws IOException, ParseException { | |
| while (matchKeyword("inner-type")) { | |
| ArrayList<Integer> locNumbers = | |
| new ArrayList<Integer>(); | |
| locNumbers.add(offset + expectNonNegative(matchNNInteger())); | |
| // TODO: currently, we simply read the binary representation. | |
| // Should we read a higher-level format? | |
| while (matchChar(',')) { | |
| locNumbers.add(expectNonNegative(matchNNInteger())); | |
| } | |
| InnerTypeLocation loc; | |
| try { | |
| loc = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(locNumbers)); | |
| } catch (AssertionError ex) { | |
| throw new ParseException(ex.getMessage(), ex); | |
| } | |
| AElement it = e.innerTypes.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(it); | |
| } | |
| } | |
| private void parseBounds(VivifyingMap<BoundLocation, ATypeElement> bounds) | |
| throws IOException, ParseException { | |
| while (checkKeyword("typeparam") || checkKeyword("bound")) { | |
| if (matchKeyword("typeparam")) { | |
| int paramIndex = expectNonNegative(matchNNInteger()); | |
| BoundLocation bl = new BoundLocation(paramIndex, -1); | |
| ATypeElement b = bounds.vivify(bl); | |
| expectChar(':'); | |
| parseAnnotations(b); | |
| // does this make sense? | |
| parseInnerTypes(b); | |
| } else if (matchKeyword("bound")) { | |
| // expectChar(','); | |
| int paramIndex = expectNonNegative(matchNNInteger()); | |
| expectChar('&'); | |
| int boundIndex = expectNonNegative(matchNNInteger()); | |
| BoundLocation bl = new BoundLocation(paramIndex, boundIndex); | |
| ATypeElement b = bounds.vivify(bl); | |
| expectChar(':'); | |
| parseAnnotations(b); | |
| // does this make sense? | |
| parseInnerTypes(b); | |
| } else { | |
| throw new Error("impossible"); | |
| } | |
| } | |
| } | |
| private void parseExtends(AClass cls) throws IOException, ParseException { | |
| expectKeyword("extends"); | |
| TypeIndexLocation idx = new TypeIndexLocation(-1); | |
| ATypeElement ext = cls.extendsImplements.vivify(idx); | |
| expectChar(':'); | |
| parseAnnotations(ext); | |
| parseInnerTypes(ext); | |
| } | |
| private void parseImplements(AClass cls) throws IOException, ParseException { | |
| expectKeyword("implements"); | |
| int implIndex = expectNonNegative(matchNNInteger()); | |
| TypeIndexLocation idx = new TypeIndexLocation(implIndex); | |
| ATypeElement impl = cls.extendsImplements.vivify(idx); | |
| expectChar(':'); | |
| parseAnnotations(impl); | |
| parseInnerTypes(impl); | |
| } | |
| private void parseField(AClass c) throws IOException, | |
| ParseException { | |
| expectKeyword("field"); | |
| String name = expectIdentifier(); | |
| AField f = c.fields.vivify(name); | |
| expectChar(':'); | |
| parseAnnotations(f); | |
| if (checkKeyword("type") && matchKeyword("type")) { | |
| expectChar(':'); | |
| parseAnnotations(f.type); | |
| parseInnerTypes(f.type); | |
| } | |
| f.init = c.fieldInits.vivify(name); | |
| parseExpression(f.init); | |
| parseASTInsertions(f); | |
| } | |
| private void parseStaticInit(AClass c) throws IOException, | |
| ParseException { | |
| expectKeyword("staticinit"); | |
| expectChar('*'); | |
| int blockIndex = expectNonNegative(matchNNInteger()); | |
| expectChar(':'); | |
| ABlock staticinit = c.staticInits.vivify(blockIndex); | |
| parseBlock(staticinit); | |
| } | |
| private void parseInstanceInit(AClass c) throws IOException, | |
| ParseException { | |
| expectKeyword("instanceinit"); | |
| expectChar('*'); | |
| int blockIndex = expectNonNegative(matchNNInteger()); | |
| expectChar(':'); | |
| ABlock instanceinit = c.instanceInits.vivify(blockIndex); | |
| parseBlock(instanceinit); | |
| } | |
| private void parseMethod(AClass c) throws IOException, | |
| ParseException { | |
| expectKeyword("method"); | |
| // special case: method could be <init> or <clinit> | |
| String key; | |
| if (matchChar('<')) { | |
| String basename = expectIdentifier(); | |
| if (!(basename.equals("init") || basename.equals("clinit"))) { | |
| throw new ParseException( | |
| "The only special methods allowed are <init> and <clinit>"); | |
| } | |
| expectChar('>'); | |
| key = "<" + basename + ">"; | |
| } else { | |
| key = expectIdentifier(); | |
| // too bad className is private in AClass and thus must be | |
| // extracted from what toString() returns | |
| if (Pattern.matches("AClass: (?:[^. ]+\\.)*" + key, | |
| c.toString())) { // ugh | |
| key = "<init>"; | |
| } | |
| } | |
| expectChar('('); | |
| key += '('; | |
| while (!matchChar(':')) { | |
| if (st.ttype >= 0) { | |
| key += st.ttype == 46 ? '/' :(char) st.ttype; | |
| } else if (st.ttype == TT_WORD) { | |
| key += st.sval; | |
| } else { | |
| throw new ParseException("Found something that doesn't belong in a signature"); | |
| } | |
| st.nextToken(); | |
| } | |
| AMethod m = c.methods.vivify(key); | |
| parseAnnotations(m); | |
| parseMethod(m); | |
| } | |
| private void parseMethod(AMethod m) throws IOException, ParseException { | |
| parseBounds(m.bounds); | |
| // Permit return value, receiver, and parameters in any order. | |
| while (checkKeyword("return") || checkKeyword("receiver") || checkKeyword("parameter")) { | |
| if (matchKeyword("return")) { | |
| expectChar(':'); | |
| parseAnnotations(m.returnType); | |
| parseInnerTypes(m.returnType); | |
| } else if (matchKeyword("parameter")) { | |
| // make "#" optional | |
| if (checkChar('#')) { | |
| matchChar('#'); | |
| } | |
| int idx = expectNonNegative(matchNNInteger()); | |
| AField p = m.parameters.vivify(idx); | |
| expectChar(':'); | |
| parseAnnotations(p); | |
| if (checkKeyword("type") && matchKeyword("type")) { | |
| expectChar(':'); | |
| parseAnnotations(p.type); | |
| parseInnerTypes(p.type); | |
| } | |
| } else if (matchKeyword("receiver")) { | |
| expectChar(':'); | |
| parseAnnotations(m.receiver.type); | |
| parseInnerTypes(m.receiver.type); | |
| } else { | |
| throw new Error("This can't happen"); | |
| } | |
| } | |
| parseBlock(m.body); | |
| parseASTInsertions(m); | |
| } | |
| private void parseLambda(AMethod m) throws IOException, ParseException { | |
| while (checkKeyword("parameter")) { | |
| matchKeyword("parameter"); | |
| // make "#" optional | |
| if (checkChar('#')) { | |
| matchChar('#'); | |
| } | |
| int idx = expectNonNegative(matchNNInteger()); | |
| AField p = m.parameters.vivify(idx); | |
| expectChar(':'); | |
| parseAnnotations(p); | |
| if (checkKeyword("type") && matchKeyword("type")) { | |
| expectChar(':'); | |
| parseAnnotations(p.type); | |
| parseInnerTypes(p.type); | |
| } | |
| } | |
| // parseBlock(m.body, true); | |
| parseASTInsertions(m); | |
| } | |
| private void parseBlock(ABlock bl) throws IOException, | |
| ParseException { | |
| boolean matched = true; | |
| while (matched) { | |
| matched = false; | |
| while (checkKeyword("local")) { | |
| matchKeyword("local"); | |
| matched = true; | |
| LocalLocation loc; | |
| if (checkNNInteger() != -1) { | |
| // the local variable is specified by bytecode index/range | |
| int index = expectNonNegative(matchNNInteger()); | |
| expectChar('#'); | |
| int scopeStart = expectNonNegative(matchNNInteger()); | |
| expectChar('+'); | |
| int scopeLength = expectNonNegative(matchNNInteger()); | |
| loc = new LocalLocation(index, scopeStart, scopeLength); | |
| } else { | |
| // look for a valid identifier for the local variable | |
| String lvar = expectIdentifier(); | |
| int varIndex; | |
| if (checkChar('*')) { | |
| expectChar('*'); | |
| varIndex = expectNonNegative(matchNNInteger()); | |
| } else { | |
| // default the variable index to 0, the most common case | |
| varIndex = 0; | |
| } | |
| loc = new LocalLocation(lvar, varIndex); | |
| } | |
| AField l = bl.locals.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(l); | |
| if (checkKeyword("type") && matchKeyword("type")) { | |
| expectChar(':'); | |
| parseAnnotations(l.type); | |
| parseInnerTypes(l.type); | |
| } | |
| } | |
| matched = parseExpression(bl) || matched; | |
| } | |
| } | |
| private boolean parseExpression(AExpression exp) throws IOException, | |
| ParseException { | |
| boolean matched = true; | |
| boolean evermatched = false; | |
| while (matched) { | |
| matched = false; | |
| while (checkKeyword("typecast")) { | |
| matchKeyword("typecast"); | |
| matched = true; | |
| evermatched = true; | |
| RelativeLocation loc; | |
| if (checkChar('#')) { | |
| expectChar('#'); | |
| int offset = expectNonNegative(matchNNInteger()); | |
| int type_index = 0; | |
| if (checkChar(',')) { | |
| expectChar(','); | |
| type_index = expectNonNegative(matchNNInteger()); | |
| } | |
| loc = RelativeLocation.createOffset(offset, type_index); | |
| } else { | |
| expectChar('*'); | |
| int index = expectNonNegative(matchNNInteger()); | |
| int type_index = 0; | |
| if (checkChar(',')) { | |
| expectChar(','); | |
| type_index = expectNonNegative(matchNNInteger()); | |
| } | |
| loc = RelativeLocation.createIndex(index, type_index); | |
| } | |
| ATypeElement t = exp.typecasts.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(t); | |
| parseInnerTypes(t); | |
| } | |
| while (checkKeyword("instanceof")) { | |
| matchKeyword("instanceof"); | |
| matched = true; | |
| evermatched = true; | |
| RelativeLocation loc; | |
| if (checkChar('#')) { | |
| expectChar('#'); | |
| int offset = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createOffset(offset, 0); | |
| } else { | |
| expectChar('*'); | |
| int index = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createIndex(index, 0); | |
| } | |
| ATypeElement i = exp.instanceofs.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(i); | |
| parseInnerTypes(i); | |
| } | |
| while (checkKeyword("new")) { | |
| matchKeyword("new"); | |
| matched = true; | |
| evermatched = true; | |
| RelativeLocation loc; | |
| if (checkChar('#')) { | |
| expectChar('#'); | |
| int offset = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createOffset(offset, 0); | |
| } else { | |
| expectChar('*'); | |
| int index = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createIndex(index, 0); | |
| } | |
| ATypeElement n = exp.news.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(n); | |
| parseInnerTypes(n); | |
| } | |
| while (checkKeyword("call")) { | |
| matchKeyword("call"); | |
| matched = true; | |
| evermatched = true; | |
| int i; | |
| boolean isOffset = checkChar('#'); | |
| expectChar(isOffset ? '#' : '*'); | |
| i = expectNonNegative(matchNNInteger()); | |
| expectChar(':'); | |
| while (checkKeyword("typearg")) { | |
| matchKeyword("typearg"); | |
| if (checkChar('#')) { matchChar('#'); } | |
| int type_index = expectNonNegative(matchNNInteger()); | |
| RelativeLocation loc = isOffset | |
| ? RelativeLocation.createOffset(i, type_index) | |
| : RelativeLocation.createIndex(i, type_index); | |
| ATypeElement t = exp.calls.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(t); | |
| parseInnerTypes(t); | |
| } | |
| } | |
| while (checkKeyword("reference")) { | |
| matchKeyword("reference"); | |
| matched = true; | |
| evermatched = true; | |
| ATypeElement t; | |
| RelativeLocation loc; | |
| int i; | |
| boolean isOffset = checkChar('#'); | |
| if (isOffset) { | |
| expectChar('#'); | |
| i = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createOffset(i, 0); | |
| } else { | |
| expectChar('*'); | |
| i = expectNonNegative(matchNNInteger()); | |
| loc = RelativeLocation.createIndex(i, 0); | |
| } | |
| expectChar(':'); | |
| t = exp.refs.vivify(loc); | |
| parseAnnotations(t); | |
| parseInnerTypes(t); | |
| while (checkKeyword("typearg")) { | |
| matchKeyword("typearg"); | |
| if (checkChar('#')) { matchChar('#'); } | |
| int type_index = expectNonNegative(matchNNInteger()); | |
| loc = isOffset | |
| ? RelativeLocation.createOffset(i, type_index) | |
| : RelativeLocation.createIndex(i, type_index); | |
| t = exp.refs.vivify(loc); | |
| expectChar(':'); | |
| parseAnnotations(t); | |
| parseInnerTypes(t); | |
| } | |
| } | |
| while (checkKeyword("lambda")) { | |
| matchKeyword("lambda"); | |
| matched = true; | |
| evermatched = true; | |
| RelativeLocation loc; | |
| if (checkChar('#')) { | |
| expectChar('#'); | |
| int offset = expectNonNegative(matchNNInteger()); | |
| int type_index = 0; | |
| if (checkChar(',')) { | |
| expectChar(','); | |
| type_index = expectNonNegative(matchNNInteger()); | |
| } | |
| loc = RelativeLocation.createOffset(offset, type_index); | |
| } else { | |
| expectChar('*'); | |
| int index = expectNonNegative(matchNNInteger()); | |
| int type_index = 0; | |
| if (checkChar(',')) { | |
| expectChar(','); | |
| type_index = expectNonNegative(matchNNInteger()); | |
| } | |
| loc = RelativeLocation.createIndex(index, type_index); | |
| } | |
| AMethod m = exp.funs.vivify(loc); | |
| expectChar(':'); | |
| // parseAnnotations(m); | |
| parseLambda(m); | |
| // parseMethod(m); | |
| } | |
| } | |
| return evermatched; | |
| } | |
| private static boolean isTypeSelector(String selector) { | |
| return Arrays.<String>binarySearch(typeSelectors, selector, Collator.getInstance()) >= 0; | |
| } | |
| private static boolean selectsExpression(ASTPath astPath) { | |
| int n = astPath.size(); | |
| if (--n >= 0) { | |
| ASTPath.ASTEntry entry = astPath.get(n); | |
| while (--n >= 0 && entry.getTreeKind() == Kind.MEMBER_SELECT | |
| && entry.childSelectorIs(ASTPath.EXPRESSION)) { | |
| entry = astPath.get(n); | |
| } | |
| return !isTypeSelector(entry.getChildSelector()); | |
| } | |
| return false; | |
| } | |
| private boolean parseASTInsertions(ADeclaration decl) | |
| throws IOException, ParseException { | |
| boolean matched = false; | |
| while (checkKeyword("insert-annotation")) { | |
| matched = true; | |
| matchKeyword("insert-annotation"); | |
| ASTPath astPath = parseASTPath(); | |
| expectChar(':'); | |
| // if path doesn't indicate a type, a cast must be generated | |
| if (selectsExpression(astPath)) { | |
| ATypeElementWithType i = decl.insertTypecasts.vivify(astPath); | |
| parseAnnotations(i); | |
| i.setType(new DeclaredType()); | |
| parseInnerTypes(i); | |
| } else { | |
| // astPath = fixNewArrayType(astPath); // handle special case | |
| // ATypeElement i = decl.insertAnnotations.vivify(astPath); | |
| // parseAnnotations(i); | |
| // parseInnerTypes(i); | |
| int offset = 0; | |
| Pair<ASTPath, InnerTypeLocation> pair = | |
| splitNewArrayType(astPath); // handle special case | |
| ATypeElement i; | |
| if (pair == null) { | |
| i = decl.insertAnnotations.vivify(astPath); | |
| } else { | |
| i = decl.insertAnnotations.vivify(pair.a); | |
| if (pair.b != null) { | |
| i = i.innerTypes.vivify(pair.b); | |
| offset = pair.b.location.size(); | |
| } | |
| } | |
| parseAnnotations(i); | |
| parseInnerTypes(i, offset); | |
| } | |
| } | |
| while (checkKeyword("insert-typecast")) { | |
| matched = true; | |
| matchKeyword("insert-typecast"); | |
| ASTPath astPath = parseASTPath(); | |
| expectChar(':'); | |
| ATypeElementWithType i = decl.insertTypecasts.vivify(astPath); | |
| parseAnnotations(i); | |
| Type type = parseType(); | |
| i.setType(type); | |
| parseInnerTypes(i); | |
| } | |
| return matched; | |
| } | |
| // Due to the unfortunate representation of new array expressions, | |
| // ASTPaths to their inner array types break the usual rule that | |
| // an ASTPath corresponds to an AST node. This method restores the | |
| // invariant by separating out the inner type information. | |
| private Pair<ASTPath, InnerTypeLocation> splitNewArrayType(ASTPath astPath) { | |
| ASTPath outerPath = astPath; | |
| InnerTypeLocation loc = null; | |
| int last = astPath.size() - 1; | |
| if (last > 0) { | |
| ASTPath.ASTEntry entry = astPath.get(last); | |
| if (entry.getTreeKind() == Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE)) { | |
| int a = entry.getArgument(); | |
| if (a > 0) { | |
| outerPath = astPath.getParentPath().extend(new ASTPath.ASTEntry(Kind.NEW_ARRAY, ASTPath.TYPE, 0)); | |
| loc = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(Collections.nCopies(2 * a, 0))); | |
| } | |
| } | |
| } | |
| return Pair.of(outerPath, loc); | |
| } | |
| private ASTPath fixNewArrayType(ASTPath astPath) { | |
| ASTPath outerPath = astPath; | |
| int last = astPath.size() - 1; | |
| if (last > 0) { | |
| ASTPath.ASTEntry entry = astPath.get(last); | |
| if (entry.getTreeKind() == Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE)) { | |
| int a = entry.getArgument(); | |
| outerPath = astPath.getParentPath().extend(new ASTPath.ASTEntry(Kind.NEW_ARRAY, ASTPath.TYPE, 0)); | |
| while (--a >= 0) { | |
| outerPath = outerPath.extend(new ASTPath.ASTEntry(Kind.ARRAY_TYPE, ASTPath.TYPE)); | |
| } | |
| } | |
| } | |
| return outerPath; | |
| } | |
| /** | |
| * Parses an AST path. | |
| * @return the AST path | |
| */ | |
| private ASTPath parseASTPath() throws IOException, ParseException { | |
| ASTPath astPath = ASTPath.empty().extend(parseASTEntry()); | |
| while (matchChar(',')) { | |
| astPath = astPath.extend(parseASTEntry()); | |
| } | |
| return astPath; | |
| } | |
| /** | |
| * Parses and returns the next AST entry. | |
| * @return a new AST entry | |
| * @throws ParseException if the next entry type is invalid | |
| */ | |
| private ASTPath.ASTEntry parseASTEntry() throws IOException, ParseException { | |
| ASTPath.ASTEntry entry; | |
| if (matchKeyword("AnnotatedType")) { | |
| entry = newASTEntry(Kind.ANNOTATED_TYPE, new String[] {ASTPath.ANNOTATION, ASTPath.UNDERLYING_TYPE}, | |
| new String[] {ASTPath.ANNOTATION}); | |
| } else if (matchKeyword("ArrayAccess")) { | |
| entry = newASTEntry(Kind.ARRAY_ACCESS, new String[] {ASTPath.EXPRESSION, ASTPath.INDEX}); | |
| } else if (matchKeyword("ArrayType")) { | |
| entry = newASTEntry(Kind.ARRAY_TYPE, new String[] {ASTPath.TYPE}); | |
| } else if (matchKeyword("Assert")) { | |
| entry = newASTEntry(Kind.ASSERT, new String[] {ASTPath.CONDITION, ASTPath.DETAIL}); | |
| } else if (matchKeyword("Assignment")) { | |
| entry = newASTEntry(Kind.ASSIGNMENT, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("Binary")) { | |
| // Always use Kind.PLUS for Binary | |
| entry = newASTEntry(Kind.PLUS, new String[] {ASTPath.LEFT_OPERAND, ASTPath.RIGHT_OPERAND}); | |
| } else if (matchKeyword("Block")) { | |
| entry = newASTEntry(Kind.BLOCK, new String[] {ASTPath.STATEMENT}, new String[] {ASTPath.STATEMENT}); | |
| } else if (matchKeyword("Case")) { | |
| entry = newASTEntry(Kind.CASE, new String[] {ASTPath.EXPRESSION, ASTPath.STATEMENT}, | |
| new String[] {ASTPath.STATEMENT}); | |
| } else if (matchKeyword("Catch")) { | |
| entry = newASTEntry(Kind.CATCH, new String[] {ASTPath.PARAMETER, ASTPath.BLOCK}); | |
| } else if (matchKeyword("Class")) { | |
| entry = newASTEntry(Kind.CLASS, | |
| new String[] {ASTPath.BOUND, ASTPath.INITIALIZER, ASTPath.TYPE_PARAMETER}, | |
| new String[] {ASTPath.BOUND, ASTPath.INITIALIZER, ASTPath.TYPE_PARAMETER}); | |
| } else if (matchKeyword("CompoundAssignment")) { | |
| // Always use Kind.PLUS_ASSIGNMENT for CompoundAssignment | |
| entry = newASTEntry(Kind.PLUS_ASSIGNMENT, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("ConditionalExpression")) { | |
| entry = newASTEntry(Kind.CONDITIONAL_EXPRESSION, | |
| new String[] {ASTPath.CONDITION, ASTPath.TRUE_EXPRESSION, ASTPath.FALSE_EXPRESSION}); | |
| } else if (matchKeyword("DoWhileLoop")) { | |
| entry = newASTEntry(Kind.DO_WHILE_LOOP, new String[] {ASTPath.CONDITION, ASTPath.STATEMENT}); | |
| } else if (matchKeyword("EnhancedForLoop")) { | |
| entry = newASTEntry(Kind.ENHANCED_FOR_LOOP, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION, ASTPath.STATEMENT}); | |
| } else if (matchKeyword("ExpressionStatement")) { | |
| entry = newASTEntry(Kind.EXPRESSION_STATEMENT, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("ForLoop")) { | |
| entry = newASTEntry(Kind.FOR_LOOP, new String[] {ASTPath.INITIALIZER, ASTPath.CONDITION, ASTPath.UPDATE, ASTPath.STATEMENT}, | |
| new String[] {ASTPath.INITIALIZER, ASTPath.UPDATE}); | |
| } else if (matchKeyword("If")) { | |
| entry = newASTEntry(Kind.IF, new String[] {ASTPath.CONDITION, ASTPath.THEN_STATEMENT, ASTPath.ELSE_STATEMENT}); | |
| } else if (matchKeyword("InstanceOf")) { | |
| entry = newASTEntry(Kind.INSTANCE_OF, new String[] {ASTPath.EXPRESSION, ASTPath.TYPE}); | |
| } else if (matchKeyword("LabeledStatement")) { | |
| entry = newASTEntry(Kind.LABELED_STATEMENT, new String[] {ASTPath.STATEMENT}); | |
| } else if (matchKeyword("LambdaExpression")) { | |
| entry = newASTEntry(Kind.LAMBDA_EXPRESSION, new String[] {ASTPath.PARAMETER, ASTPath.BODY}, | |
| new String[] {ASTPath.PARAMETER}); | |
| } else if (matchKeyword("MemberReference")) { | |
| entry = newASTEntry(Kind.MEMBER_REFERENCE, new String[] {ASTPath.QUALIFIER_EXPRESSION, ASTPath.TYPE_ARGUMENT}, | |
| new String[] {ASTPath.TYPE_ARGUMENT}); | |
| } else if (matchKeyword("MemberSelect")) { | |
| entry = newASTEntry(Kind.MEMBER_SELECT, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("Method")) { | |
| entry = newASTEntry(Kind.METHOD, new String[] {ASTPath.BODY, ASTPath.TYPE, ASTPath.PARAMETER, ASTPath.TYPE_PARAMETER}, | |
| new String[] {ASTPath.PARAMETER, ASTPath.TYPE_PARAMETER}); | |
| } else if (matchKeyword("MethodInvocation")) { | |
| entry = newASTEntry(Kind.METHOD_INVOCATION, new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.METHOD_SELECT, ASTPath.ARGUMENT}, | |
| new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT}); | |
| } else if (matchKeyword("NewArray")) { | |
| entry = newASTEntry(Kind.NEW_ARRAY, new String[] {ASTPath.TYPE, ASTPath.DIMENSION, ASTPath.INITIALIZER}, | |
| new String[] {ASTPath.TYPE, ASTPath.DIMENSION, ASTPath.INITIALIZER}); | |
| } else if (matchKeyword("NewClass")) { | |
| entry = newASTEntry(Kind.NEW_CLASS, new String[] {ASTPath.ENCLOSING_EXPRESSION, ASTPath.TYPE_ARGUMENT, ASTPath.IDENTIFIER, ASTPath.ARGUMENT, ASTPath.CLASS_BODY}, | |
| new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT}); | |
| } else if (matchKeyword("ParameterizedType")) { | |
| entry = newASTEntry(Kind.PARAMETERIZED_TYPE, new String[] {ASTPath.TYPE, ASTPath.TYPE_ARGUMENT}, | |
| new String[] {ASTPath.TYPE_ARGUMENT}); | |
| } else if (matchKeyword("Parenthesized")) { | |
| entry = newASTEntry(Kind.PARENTHESIZED, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("Return")) { | |
| entry = newASTEntry(Kind.RETURN, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("Switch")) { | |
| entry = newASTEntry(Kind.SWITCH, new String[] {ASTPath.EXPRESSION, ASTPath.CASE}, | |
| new String[] {ASTPath.CASE}); | |
| } else if (matchKeyword("Synchronized")) { | |
| entry = newASTEntry(Kind.SYNCHRONIZED, new String[] {ASTPath.EXPRESSION, ASTPath.BLOCK}); | |
| } else if (matchKeyword("Throw")) { | |
| entry = newASTEntry(Kind.THROW, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("Try")) { | |
| entry = newASTEntry(Kind.TRY, new String[] {ASTPath.BLOCK, ASTPath.CATCH, ASTPath.FINALLY_BLOCK}, | |
| new String[] {ASTPath.CATCH}); | |
| } else if (matchKeyword("TypeCast")) { | |
| entry = newASTEntry(Kind.TYPE_CAST, new String[] {ASTPath.TYPE, ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("TypeParameter")) { | |
| entry = newASTEntry(Kind.TYPE_PARAMETER, new String[] {ASTPath.BOUND}, | |
| new String[] {ASTPath.BOUND}); | |
| } else if (matchKeyword("Unary")) { | |
| // Always use Kind.UNARY_PLUS for Unary | |
| entry = newASTEntry(Kind.UNARY_PLUS, new String[] {ASTPath.EXPRESSION}); | |
| } else if (matchKeyword("UnionType")) { | |
| entry = newASTEntry(Kind.UNION_TYPE, new String[] {ASTPath.TYPE_ALTERNATIVE}, | |
| new String[] {ASTPath.TYPE_ALTERNATIVE}); | |
| } else if (matchKeyword("Variable")) { | |
| entry = newASTEntry(Kind.VARIABLE, new String[] {ASTPath.TYPE, ASTPath.INITIALIZER}); | |
| } else if (matchKeyword("WhileLoop")) { | |
| entry = newASTEntry(Kind.WHILE_LOOP, new String[] {ASTPath.CONDITION, ASTPath.STATEMENT}); | |
| } else if (matchKeyword("Wildcard")) { | |
| // Always use Kind.UNBOUNDED_WILDCARD for Wildcard | |
| entry = newASTEntry(Kind.UNBOUNDED_WILDCARD, new String[] {ASTPath.BOUND}); | |
| } else { | |
| throw new ParseException("Invalid AST path type: " + st.sval); | |
| } | |
| return entry; | |
| } | |
| /** | |
| * Parses and constructs a new AST entry, where none of the child selections require | |
| * arguments. For example, the call: | |
| * | |
| * <pre> | |
| * {@code newASTEntry(Kind.WHILE_LOOP, new String[] {"condition", "statement"});</pre> | |
| * | |
| * constructs a while loop AST entry, where the valid child selectors are "condition" or | |
| * "statement". | |
| * | |
| * @param kind the kind of this AST entry | |
| * @param legalChildSelectors a list of the legal child selectors for this AST entry | |
| * @return a new {@link ASTPath.ASTEntry} | |
| * @throws ParseException if an illegal argument is found | |
| */ | |
| private ASTPath.ASTEntry newASTEntry(Kind kind, String[] legalChildSelectors) throws IOException, ParseException { | |
| return newASTEntry(kind, legalChildSelectors, null); | |
| } | |
| /** | |
| * Parses and constructs a new AST entry. For example, the call: | |
| * | |
| * <pre> | |
| * {@code newASTEntry(Kind.CASE, new String[] {"expression", "statement"}, new String[] {"statement"}); | |
| * </pre> | |
| * | |
| * constructs a case AST entry, where the valid child selectors are | |
| * "expression" or "statement" and the "statement" child selector requires | |
| * an argument. | |
| * | |
| * @param kind the kind of this AST entry | |
| * @param legalChildSelectors a list of the legal child selectors for this AST entry | |
| * @param argumentChildSelectors a list of the child selectors that also require an argument. | |
| * Entries here should also be in the legalChildSelectors list. | |
| * @return a new {@link ASTPath.ASTEntry} | |
| * @throws ParseException if an illegal argument is found | |
| */ | |
| private ASTPath.ASTEntry newASTEntry(Kind kind, String[] legalChildSelectors, String[] argumentChildSelectors) throws IOException, ParseException { | |
| expectChar('.'); | |
| for (String arg : legalChildSelectors) { | |
| if (matchKeyword(arg)) { | |
| if (argumentChildSelectors != null && ArraysMDE.indexOf(argumentChildSelectors, arg) >= 0) { | |
| int index = matchNNInteger(); | |
| return new ASTPath.ASTEntry(kind, arg, index); | |
| } else { | |
| return new ASTPath.ASTEntry(kind, arg); | |
| } | |
| } | |
| } | |
| throw new ParseException("Invalid argument for " + kind + " (legal arguments - " + Arrays.toString(legalChildSelectors) + "): " + st.sval); | |
| } | |
| /** | |
| * Parses the next tokens as a Java type. | |
| */ | |
| private Type parseType() throws IOException, ParseException { | |
| DeclaredType declaredType; | |
| if (matchChar('?')) { | |
| declaredType = new DeclaredType(DeclaredType.WILDCARD); | |
| } else { | |
| declaredType = parseDeclaredType(); | |
| } | |
| if (checkKeyword("extends") || checkKeyword("super")) { | |
| return parseBoundedType(declaredType); | |
| } else { | |
| Type type = declaredType; | |
| while (matchChar('[')) { | |
| expectChar(']'); | |
| type = new ArrayType(type); | |
| } | |
| return type; | |
| } | |
| } | |
| /** | |
| * Parses the next tokens as a declared type. | |
| */ | |
| private DeclaredType parseDeclaredType() throws IOException, ParseException { | |
| String type = matchIdentifier(); | |
| if (type == null) { | |
| type = matchPrimitiveType(); | |
| if (type == null) { | |
| throw new ParseException("Expected identifier or primitive type"); | |
| } | |
| } | |
| return parseDeclaredType(type); | |
| } | |
| /** | |
| * Parses the next tokens as a declared type. | |
| * @param name the name of the initial identifier | |
| */ | |
| private DeclaredType parseDeclaredType(String name) throws IOException, ParseException { | |
| DeclaredType type = new DeclaredType(name); | |
| if (matchChar('<')) { | |
| type.addTypeParameter(parseType()); | |
| while (matchChar(',')) { | |
| type.addTypeParameter(parseType()); | |
| } | |
| expectChar('>'); | |
| } | |
| if (matchChar('.')) { | |
| type.setInnerType(parseDeclaredType()); | |
| } | |
| return type; | |
| } | |
| /** | |
| * Parses the next tokens as a bounded type. | |
| * @param type the name, which precedes "extends" or "super" | |
| */ | |
| private BoundedType parseBoundedType(DeclaredType type) throws IOException, ParseException { | |
| BoundKind kind; | |
| if (matchKeyword("extends")) { | |
| kind = BoundKind.EXTENDS; | |
| } else if (matchKeyword("super")) { | |
| kind = BoundKind.SUPER; | |
| } else { | |
| throw new ParseException("Illegal bound kind: " + st.sval); | |
| } | |
| return new BoundedType(type, kind, parseDeclaredType()); | |
| } | |
| private void parseClass() throws IOException, ParseException { | |
| expectKeyword("class"); | |
| String basename = expectIdentifier(); | |
| String fullName = curPkgPrefix + basename; | |
| AClass c = scene.classes.vivify(fullName); | |
| expectChar(':'); | |
| parseAnnotations(c); | |
| parseBounds(c.bounds); | |
| while (checkKeyword("extends")) { | |
| parseExtends(c); | |
| } | |
| while (checkKeyword("implements")) { | |
| parseImplements(c); | |
| } | |
| parseASTInsertions(c); | |
| while (checkKeyword("field")) { | |
| parseField(c); | |
| } | |
| while (checkKeyword("staticinit")) { | |
| parseStaticInit(c); | |
| } | |
| while (checkKeyword("instanceinit")) { | |
| parseInstanceInit(c); | |
| } | |
| while (checkKeyword("method")) { | |
| parseMethod(c); | |
| } | |
| c.methods.prune(); | |
| } | |
| // Reads the index file in this.st and puts the information in this.scene. | |
| private void parse() throws ParseException, IOException { | |
| st.nextToken(); | |
| while (st.ttype != TT_EOF) { | |
| expectKeyword("package"); | |
| String pkg; | |
| if (checkIdentifier() == null) { | |
| pkg = null; | |
| // the default package cannot be annotated | |
| matchChar(':'); | |
| } else { | |
| pkg = expectQualifiedName(); | |
| // AElement p = scene.packages.vivify(pkg); | |
| AClass p = scene.classes.vivify(pkg + ".package-info"); | |
| expectChar(':'); | |
| p = scene.classes.vivify(pkg + ".package-info"); | |
| parseAnnotations(p); | |
| } | |
| if (pkg != null) { | |
| curPkgPrefix = pkg + "."; | |
| } else { | |
| curPkgPrefix = ""; | |
| } | |
| for (;;) { | |
| if (checkKeyword("annotation")) { | |
| parseAnnotationDef(); | |
| } else if (checkKeyword("class")) { | |
| parseClass(); | |
| } else if (checkKeyword("package") || st.ttype == TT_EOF) { | |
| break; | |
| } else { | |
| throw new ParseException("Expected: `annotation', `class', or `package'. Found: `" | |
| + st.sval + "', ttype:" + st.ttype); | |
| } | |
| } | |
| } | |
| /* | |
| for (Map.Entry<String, AnnotationDef> entry : defs.entrySet()) { | |
| final String annotationType = entry.getKey(); | |
| AnnotationDef def = entry.getValue(); | |
| for (AnnotationFieldType aft : def.fieldTypes.values()) { | |
| aft.accept(new AFTVisitor<Void, Void>() { | |
| @Override | |
| public Void visitAnnotationAFT(AnnotationAFT aft, | |
| Void arg) { | |
| for (AnnotationFieldType t : aft.annotationDef.fieldTypes.values()) { | |
| t.accept(this, arg); | |
| } | |
| return null; | |
| } | |
| @Override | |
| public Void visitArrayAFT(ArrayAFT aft, Void arg) { | |
| return aft.elementType == null ? null | |
| : aft.elementType.accept(this, arg); | |
| } | |
| @Override | |
| public Void visitBasicAFT(BasicAFT aft, Void arg) { | |
| return null; | |
| } | |
| @Override | |
| public Void visitClassTokenAFT(ClassTokenAFT aft, Void arg) { | |
| return null; | |
| } | |
| @Override | |
| public Void visitEnumAFT(EnumAFT aft, Void arg) { | |
| importSet(annotationType, aft).add(aft.typeName); | |
| return null; | |
| } | |
| private Set<String> importSet(final String annotationType, | |
| AnnotationFieldType aft) { | |
| Set<String> imps = scene.imports.get(annotationType); | |
| if (imps == null) { | |
| imps = new TreeSet<String>(); | |
| scene.imports.put(annotationType, imps); | |
| } | |
| return imps; | |
| } | |
| }, null); | |
| } | |
| } | |
| */ | |
| } | |
| private IndexFileParser(Reader in, AScene scene) { | |
| defs = new LinkedHashMap<String, AnnotationDef>(); | |
| for (AnnotationDef ad : Annotations.standardDefs) { | |
| try { | |
| addDef(ad); | |
| } catch (ParseException e) { | |
| throw new Error(e); | |
| } | |
| } | |
| st = new StreamTokenizer(in); | |
| st.slashSlashComments(true); | |
| // restrict numbers -- don't really need, could interfere with | |
| // annotation values | |
| // st.ordinaryChar('-'); | |
| // HMMM this fixes fully-qualified-name strangeness but breaks | |
| // floating-point numbers | |
| st.ordinaryChar('.'); | |
| // argggh!!! stupid default needs to be overridden! see java bug 4217680 | |
| st.ordinaryChar('/'); | |
| // for "type-argument" | |
| st.wordChars('-', '-'); | |
| // java identifiers can contain numbers, _, and $ | |
| st.wordChars('0', '9'); | |
| st.wordChars('_', '_'); | |
| st.wordChars('$', '$'); | |
| this.scene = scene; | |
| // See if the nonnull analysis picks up on this: | |
| // curPkgPrefix == ""; // will get changed later anyway | |
| } | |
| /** | |
| * Reads annotations from <code>in</code> in index file format and merges | |
| * them into <code>scene</code>. Annotations | |
| * from the input are merged into the scene; it is an error if both the | |
| * scene and the input contain annotations of the same type on the same | |
| * element. | |
| * | |
| * <p> | |
| * Since each annotation in a scene carries its own definition and the | |
| * scene as a whole no longer has a set of definitions, annotation | |
| * definitions that are given in the input but never used are not saved | |
| * anywhere and will not be included if the scene is written back to an | |
| * index file. Similarly, retention policies on definitions of annotations | |
| * that are never used at the top level are dropped. | |
| * | |
| * <p> | |
| * Caveat: Parsing of floating point numbers currently does not work. | |
| */ | |
| public static Map<String, AnnotationDef> parse(LineNumberReader in, | |
| AScene scene) throws IOException, ParseException { | |
| IndexFileParser parser = new IndexFileParser(in, scene); | |
| // no filename is available in the exception messages | |
| return parseAndReturnAnnotationDefs(null, in, parser); | |
| } | |
| /** | |
| * Reads annotations from the index file <code>filename</code> and merges | |
| * them into <code>scene</code>; see {@link #parse(LineNumberReader, AScene)}. | |
| */ | |
| public static Map<String, AnnotationDef> parseFile(String filename, | |
| AScene scene) throws IOException { | |
| LineNumberReader in = new LineNumberReader(new FileReader(filename)); | |
| IndexFileParser parser = new IndexFileParser(in, scene); | |
| return parseAndReturnAnnotationDefs(filename, in, parser); | |
| } | |
| /** | |
| * Reads annotations from the string (in index file format) and merges | |
| * them into <code>scene</code>; see {@link #parse(LineNumberReader, AScene)}. | |
| * Primarily for testing. | |
| */ | |
| public static Map<String, AnnotationDef> parseString(String fileContents, | |
| AScene scene) throws IOException { | |
| String filename = | |
| "While parsing string: \n----------------BEGIN----------------\n" | |
| + fileContents + "----------------END----------------\n"; | |
| LineNumberReader in = new LineNumberReader( | |
| new StringReader(fileContents)); | |
| IndexFileParser parser = new IndexFileParser(in, scene); | |
| return parseAndReturnAnnotationDefs(filename, in, parser); | |
| } | |
| private static Map<String, AnnotationDef> parseAndReturnAnnotationDefs( | |
| String filename, LineNumberReader in, IndexFileParser parser) | |
| throws IOException { | |
| try { | |
| parser.parse(); | |
| return Collections.unmodifiableMap(parser.defs); | |
| } catch (IOException e) { | |
| throw filename == null ? new FileIOException(in, e) | |
| : new FileIOException(in, filename, e); | |
| } catch (ParseException e) { | |
| throw filename == null ? new FileIOException(in, e) | |
| : new FileIOException(in, filename, e); | |
| } | |
| } | |
| /** | |
| * Parse the given text into a {@link Type}. | |
| * @param text the text to parse | |
| * @return the type | |
| */ | |
| public static Type parseType(String text) { | |
| StringReader in = new StringReader(text); | |
| IndexFileParser parser = new IndexFileParser(in, null); | |
| try { | |
| parser.st.nextToken(); | |
| return parser.parseType(); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Error parsing type from: '" + text + "'", e); | |
| } | |
| } | |
| } |