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}. *

* * 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 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 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(); 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> primitiveTypes; static { Map> pt = new LinkedHashMap>(); 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 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 set1 = scene.imports.get(name); Set set2 = scene.imports.get(baseName); if (set1 == null) { set1 = new TreeSet(); scene.imports.put(name, set1); } if (set2 == null) { set2 = new TreeSet(); 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 fields = new LinkedHashMap(); // 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 locNumbers = new ArrayList(); 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 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 or String key; if (matchChar('<')) { String basename = expectIdentifier(); if (!(basename.equals("init") || basename.equals("clinit"))) { throw new ParseException( "The only special methods allowed are and "); } 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 = ""; } } 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.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 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 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: * *

     * {@code newASTEntry(Kind.WHILE_LOOP, new String[] {"condition", "statement"});
* * 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: * *
     * {@code newASTEntry(Kind.CASE, new String[] {"expression", "statement"}, new String[] {"statement"});
     * 
* * 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 entry : defs.entrySet()) { final String annotationType = entry.getKey(); AnnotationDef def = entry.getValue(); for (AnnotationFieldType aft : def.fieldTypes.values()) { aft.accept(new AFTVisitor() { @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 importSet(final String annotationType, AnnotationFieldType aft) { Set imps = scene.imports.get(annotationType); if (imps == null) { imps = new TreeSet(); scene.imports.put(annotationType, imps); } return imps; } }, null); } } */ } private IndexFileParser(Reader in, AScene scene) { defs = new LinkedHashMap(); 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 in in index file format and merges * them into scene. 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. * *

* 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. * *

* Caveat: Parsing of floating point numbers currently does not work. */ public static Map 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 filename and merges * them into scene; see {@link #parse(LineNumberReader, AScene)}. */ public static Map 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 scene; see {@link #parse(LineNumberReader, AScene)}. * Primarily for testing. */ public static Map 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 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); } } }