1package annotations.io;
2
3/*>>>
4import org.checkerframework.checker.nullness.qual.*;
5*/
6
7import static java.io.StreamTokenizer.TT_EOF;
8import static java.io.StreamTokenizer.TT_NUMBER;
9import static java.io.StreamTokenizer.TT_WORD;
10
11import java.io.FileReader;
12import java.io.IOException;
13import java.io.LineNumberReader;
14import java.io.Reader;
15import java.io.StreamTokenizer;
16import java.io.StringReader;
17import java.text.Collator;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.HashMap;
22import java.util.LinkedHashMap;
23import java.util.LinkedHashSet;
24import java.util.Map;
25import java.util.Set;
26import java.util.TreeSet;
27import java.util.regex.Pattern;
28
29import com.sun.source.tree.Tree.Kind;
30import com.sun.tools.javac.code.TypeAnnotationPosition;
31
32import annotations.Annotation;
33import annotations.AnnotationBuilder;
34import annotations.AnnotationFactory;
35import annotations.Annotations;
36import annotations.ArrayBuilder;
37import annotations.el.ABlock;
38import annotations.el.AClass;
39import annotations.el.ADeclaration;
40import annotations.el.AElement;
41import annotations.el.AExpression;
42import annotations.el.AField;
43import annotations.el.AMethod;
44import annotations.el.AScene;
45import annotations.el.ATypeElement;
46import annotations.el.ATypeElementWithType;
47import annotations.el.AnnotationDef;
48import annotations.el.BoundLocation;
49import annotations.el.InnerTypeLocation;
50import annotations.el.LocalLocation;
51import annotations.el.RelativeLocation;
52import annotations.el.TypeIndexLocation;
53import annotations.field.AnnotationAFT;
54import annotations.field.AnnotationFieldType;
55import annotations.field.ArrayAFT;
56import annotations.field.BasicAFT;
57import annotations.field.ClassTokenAFT;
58import annotations.field.EnumAFT;
59import annotations.field.ScalarAFT;
60import annotations.util.coll.VivifyingMap;
61
62import plume.ArraysMDE;
63import plume.FileIOException;
64import plume.Pair;
65
66import type.ArrayType;
67import type.BoundedType;
68import type.BoundedType.BoundKind;
69import type.DeclaredType;
70import type.Type;
71
72/**
73 * IndexFileParser provides static methods
74 * {@link #parse(LineNumberReader, AScene)},
75 * {@link #parseFile(String, AScene)}, and
76 * {@link #parseString(String, AScene)}.
77 * Each of these parses an index file into a {@link AScene}.
78 * <p>
79 *
80 * If there are any problems, it throws a ParseException internally, or a
81 * FileIOException externally.
82 */
83public final class IndexFileParser {
84
85    private static final String[] typeSelectors = { "bound", "identifier",
86        "type", "typeAlternative", "typeArgument", "typeParameter",
87        "underlyingType" };
88
89    private static boolean abbreviate = true;
90
91    // The input
92    private final StreamTokenizer st;
93
94    // The output
95    private final AScene scene;
96
97    private String curPkgPrefix;
98
99    /**
100     * Holds definitions we've seen so far.  Maps from annotation name to
101     * the definition itself.  Maps from both the qualified name and the
102     * unqualified name.  If the unqualified name is not unique, it maps
103     * to null and the qualified name should be used instead. */
104    private final HashMap<String, AnnotationDef> defs;
105
106    public static void setAbbreviate(boolean b) {
107      abbreviate = b;
108    }
109
110    private int expectNonNegative(int i) throws ParseException {
111        if (i >= 0) {
112            return i;
113        } else {
114            throw new ParseException("Expected a nonnegative integer, got " + st);
115        }
116    }
117
118    /** True if the next thing from st is the given character. */
119    private boolean checkChar(char c) {
120        return st.ttype == c;
121    }
122
123    /** True if the next thing from st is the given string token. */
124    private boolean checkKeyword(String s) {
125        return st.ttype == TT_WORD && st.sval.equals(s);
126    }
127
128    /**
129     * Return true if the next thing to be read from st is the given string.
130     * In that case, also read past the given string.
131     * If the result is false, reads nothing from st.
132     */
133    private boolean matchChar(char c) throws IOException {
134        if (checkChar(c)) {
135            st.nextToken();
136            return true;
137        } else {
138            return false;
139        }
140    }
141
142    /**
143     * Return true if the next thing to be read from st is the given string.
144     * In that case, also read past the given string.
145     * If the result is false, reads nothing from st.
146     */
147    private boolean matchKeyword(String s) throws IOException {
148        if (checkKeyword(s)) {
149            st.nextToken();
150            return true;
151        } else {
152            return false;
153        }
154    }
155
156    /** Reads from st.  If the result is not c, throws an exception. */
157    private void expectChar(char c) throws IOException, ParseException {
158        if (! matchChar(c)) {
159            // Alternately, could use st.toString().
160            String found;
161            switch (st.ttype) {
162            case StreamTokenizer.TT_WORD: found = st.sval; break;
163            case StreamTokenizer.TT_NUMBER: found = "" + st.nval; break;
164            case StreamTokenizer.TT_EOL: found = "end of line"; break;
165            case StreamTokenizer.TT_EOF: found = "end of file"; break;
166            default: found = "'" + ((char) st.ttype) + "'"; break;
167            }
168            throw new ParseException("Expected '" + c + "', found " + found);
169        }
170    }
171
172    /** Reads from st.  If the result is not s, throws an exception. */
173    private void expectKeyword(String s) throws IOException,
174            ParseException {
175        if (! matchKeyword(s)) {
176            throw new ParseException("Expected `" + s + "'");
177        }
178    }
179
180    private static final Set<String> knownKeywords;
181    static {
182        String[] knownKeywords_array =
183                { "abstract", "assert", "boolean", "break", "byte", "case",
184                        "catch", "char", "class", "const", "continue",
185                        "default", "do", "double", "else", "enum", "extends",
186                        "false", "final", "finally", "float", "for", "if",
187                        "goto", "implements", "import", "instanceof", "int",
188                        "interface", "long", "native", "new", "null",
189                        "package", "private", "protected", "public", "return",
190                        "short", "static", "strictfp", "super", "switch",
191                        "synchronized", "this", "throw", "throws", "transient",
192                        "true", "try", "void", "volatile", "while", };
193        knownKeywords = new LinkedHashSet<String>();
194        Collections.addAll(knownKeywords, knownKeywords_array);
195    }
196
197    private boolean isValidIdentifier(String x) {
198        if (x.length() == 0 || !Character.isJavaIdentifierStart(x.charAt(0))
199                || knownKeywords.contains(x))
200            return false;
201        for (int i = 1; i < x.length(); i++) {
202            if (!Character.isJavaIdentifierPart(x.charAt(i))) {
203                return false;
204            }
205        }
206        return true;
207    }
208
209    private String checkIdentifier() {
210        if (st.sval == null) {
211            return null;
212        } else {
213            String val = st.sval;
214            if (st.ttype == TT_WORD && isValidIdentifier(val)) {
215                return st.sval;
216            } else {
217                return null;
218            }
219        }
220    }
221
222    private String matchIdentifier() throws IOException {
223        String x = checkIdentifier();
224        if (x != null) {
225            st.nextToken();
226            return x;
227        } else {
228            return null;
229        }
230    }
231
232    private String expectIdentifier() throws IOException, ParseException {
233        String id = matchIdentifier();
234        if (id == null) { throw new ParseException("Expected an identifier"); }
235        return id;
236    }
237
238    private String checkPrimitiveType() {
239        if (st.sval == null) {
240            return null;
241        } else {
242            String val = st.sval;
243            if (st.ttype == TT_WORD && primitiveTypes.containsKey(val)) {
244                return st.sval;
245            } else {
246                return null;
247            }
248        }
249    }
250
251    private String matchPrimitiveType() throws IOException {
252        String x = checkPrimitiveType();
253        if (x != null) {
254            st.nextToken();
255            return x;
256        } else {
257            return null;
258        }
259    }
260
261    // an identifier, or a sequence of dot-separated identifiers
262    private String expectQualifiedName() throws IOException, ParseException {
263        String name = expectIdentifier();
264        while (matchChar('.')) {
265            name += '.' + expectIdentifier();
266        }
267        return name;
268    }
269
270    private int checkNNInteger() {
271        if (st.ttype == TT_NUMBER) {
272            int x = (int) st.nval;
273            if (x == st.nval && x >= -1) // shouldn't give us a huge number
274                return x;
275        }
276        return -1;
277    }
278
279    private int matchNNInteger() throws IOException {
280        int x = checkNNInteger();
281        if (x >= -1) {
282            st.nextToken();
283            return x;
284        } else {
285            return -1;
286        }
287    }
288
289    // Mapping from primitive types and void to their corresponding
290    // class objects. Class.forName doesn't directly support these.
291    // Using this map we can go from "void.class" to the correct
292    // Class object.
293    private static final Map<String, Class<?>> primitiveTypes;
294    static {
295        Map<String, Class<?>> pt = new LinkedHashMap<String, Class<?>>();
296        pt.put("byte", byte.class);
297        pt.put("short", short.class);
298        pt.put("int", int.class);
299        pt.put("long", long.class);
300        pt.put("float", float.class);
301        pt.put("double", double.class);
302        pt.put("char", char.class);
303        pt.put("boolean", boolean.class);
304        pt.put("void", void.class);
305        primitiveTypes = pt;
306    }
307
308    /** Parse scalar annotation value. */
309    // HMMM can a (readonly) Integer be casted to a writable Object?
310    private Object parseScalarAFV(ScalarAFT aft) throws IOException, ParseException {
311        if (aft instanceof BasicAFT) {
312            Object val;
313            BasicAFT baft = (BasicAFT) aft;
314            Class<?> type = baft.type;
315            if (type == boolean.class) {
316                if (matchKeyword("true")) {
317                    val = true;
318                } else if (matchKeyword("false")) {
319                    val = false;
320                } else {
321                    throw new ParseException("Expected `true' or `false'");
322                }
323            } else if (type == char.class) {
324                if (st.ttype == '\'' && st.sval.length() == 1) {
325                    val = st.sval.charAt(0);
326                } else {
327                    throw new ParseException("Expected a character literal");
328                }
329                st.nextToken();
330            } else if (type == String.class) {
331                if (st.ttype == '"') {
332                    val = st.sval;
333                } else {
334                    throw new ParseException("Expected a string literal");
335                }
336                st.nextToken();
337            } else {
338                if (st.ttype == TT_NUMBER) {
339                    double n = st.nval;
340                    // TODO validate the literal better
341                    // HMMM StreamTokenizer can't handle all floating point
342                    // numbers; in particular, scientific notation is a problem
343                    if (type == byte.class) {
344                        val = (byte) n;
345                    } else if (type == short.class) {
346                        val = (short) n;
347                    } else if (type == int.class) {
348                        val = (int) n;
349                    } else if (type == long.class) {
350                        val = (long) n;
351                    } else if (type == float.class) {
352                        val = (float) n;
353                    } else if (type == double.class) {
354                        val = n;
355                    } else {
356                        throw new AssertionError();
357                    }
358                    st.nextToken();
359                } else {
360                    throw new ParseException(
361                            "Expected a number literal");
362                }
363            }
364            assert aft.isValidValue(val);
365            return val;
366        } else if (aft instanceof ClassTokenAFT) {
367            // Expect the class name in the format that Class.forName accepts,
368            // which is some very strange format.
369            // Example inputs followed by their Java source ".class" equivalent:
370            //   [[I.class      for int[][].class
371            //   [java.util.Map for Map[].class
372            //   java.util.Map  for Map.class
373            // Have to use fully-qualified names, i.e. "Object" alone won't work.
374            // Also note use of primitiveTypes map for primitives and void.
375            int arrays = 0;
376            StringBuilder type = new StringBuilder();
377            while (matchChar('[')) {
378                // Array dimensions as prefix
379                ++arrays;
380            }
381            while (!matchKeyword("class")) {
382                if (st.ttype >= 0) {
383                    type.append((char) st.ttype);
384                } else if (st.ttype == TT_WORD) {
385                    type.append(st.sval);
386                } else {
387                    throw new ParseException("Found something that doesn't belong in a signature");
388                }
389                st.nextToken();
390            }
391
392            // Drop the '.' before the "class"
393            type.deleteCharAt(type.length()-1);
394            // expectKeyword("class");
395
396            // Add arrays as prefix in the type.
397            while (arrays-->0) {
398                type.insert(0, '[');
399            }
400
401            try {
402                String sttype = type.toString();
403                Class<?> tktype;
404                if (primitiveTypes.containsKey(sttype)) {
405                    tktype = primitiveTypes.get(sttype);
406                } else {
407                    tktype = Class.forName(sttype);
408                }
409                assert aft.isValidValue(tktype);
410                return tktype;
411            } catch (ClassNotFoundException e) {
412                throw new ParseException("Could not load class: " + type, e);
413            }
414        } else if (aft instanceof EnumAFT) {
415            String name = expectQualifiedName();
416            assert aft.isValidValue(name);
417            return name;
418        } else if (aft instanceof AnnotationAFT) {
419            AnnotationAFT aaft = (AnnotationAFT) aft;
420            AnnotationDef d = parseAnnotationHead();
421            if (! d.name.equals(aaft.annotationDef.name)) {
422                throw new ParseException("Got an " + d.name
423                    + " subannotation where an " + aaft.annotationDef.name
424                    + " was expected");
425            }
426            AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(d);
427            // interested in this annotation,
428            // so should be interested in subannotations
429            assert ab != null;
430            AnnotationBuilder ab2 = (AnnotationBuilder) ab;
431            Annotation suba = parseAnnotationBody(d, ab2);
432            assert aft.isValidValue(suba);
433            return suba;
434        } else {
435            throw new AssertionError("IndexFileParser.parseScalarAFV: unreachable code.");
436        }
437    }
438
439    private void parseAndAddArrayAFV(ArrayAFT aaft, ArrayBuilder arrb) throws IOException, ParseException {
440        ScalarAFT comp;
441        if (aaft.elementType != null) {
442            comp = aaft.elementType;
443        } else {
444            throw new IllegalArgumentException("array AFT has null elementType");
445        }
446        if (matchChar('{')) {
447            // read an array
448            while (!matchChar('}')) {
449                arrb.appendElement(parseScalarAFV(comp));
450                if (!checkChar('}')) {
451                    expectChar(',');
452                }
453            }
454        } else {
455            // not an array, so try reading just one value as an array
456            arrb.appendElement(parseScalarAFV(comp));
457        }
458        arrb.finish();
459    }
460
461    // parses a field such as "f1=5" in "@A(f1=5, f2=10)".
462    private void parseAnnotationField(AnnotationDef d, AnnotationBuilder ab) throws IOException, ParseException {
463        String fieldName;
464        if (d.fieldTypes.size() == 1
465            && d.fieldTypes.containsKey("value")) {
466            fieldName = "value";
467            if (matchKeyword("value")) {
468                expectChar('=');
469            }
470        } else {
471            fieldName = expectIdentifier();
472            expectChar('=');
473        }
474        // HMMM let's hope the builder checks for duplicate fields
475        // because we can't do it any more
476        AnnotationFieldType aft1 = d.fieldTypes.get(fieldName);
477        if (aft1 == null) {
478            throw new ParseException("The annotation type " + d.name
479                + " has no field called " + fieldName);
480        }
481        AnnotationFieldType aft = (AnnotationFieldType) aft1;
482        if (aft instanceof ArrayAFT) {
483            ArrayAFT aaft = (ArrayAFT) aft;
484            if (aaft.elementType == null) {
485                // Array of unknown element type--must be zero-length
486                expectChar('{');
487                expectChar('}');
488                ab.addEmptyArrayField(fieldName);
489            } else {
490                parseAndAddArrayAFV(aaft, ab.beginArrayField(fieldName, aaft));
491            }
492        } else if (aft instanceof ScalarAFT) {
493            ScalarAFT saft = (ScalarAFT) aft;
494            Object value = parseScalarAFV(saft);
495            ab.addScalarField(fieldName, saft, value);
496        } else {
497            throw new AssertionError();
498        }
499    }
500
501    // reads the "@A" part of an annotation such as "@A(f1=5, f2=10)".
502    private AnnotationDef parseAnnotationHead() throws IOException,
503            ParseException {
504        expectChar('@');
505        String name = expectQualifiedName();
506        AnnotationDef d = defs.get(name);
507        if (d == null) {
508            // System.err.println("No definition for annotation type " + name);
509            // System.err.printf("  defs contains %d entries%n", defs.size());
510            // for (Map.Entry<String,AnnotationDef> entry : defs.entrySet()) {
511            //    System.err.printf("    defs entry: %s => %s%n", entry.getKey(), entry.getValue());
512            // }
513            throw new ParseException("No definition for annotation type " + name);
514        }
515        return d;
516    }
517
518    private Annotation parseAnnotationBody(AnnotationDef d, AnnotationBuilder ab) throws IOException, ParseException {
519        if (matchChar('(')) {
520            parseAnnotationField(d, ab);
521            while (matchChar(',')) {
522                parseAnnotationField(d, ab);
523            }
524            expectChar(')');
525        }
526        Annotation ann = ab.finish();
527        if (! ann.def.equals(d)) {
528            throw new ParseException(
529                "parseAnnotationBody: Annotation def isn't as it should be.\n" + d + "\n" + ann.def);
530        }
531        if (ann.def().fieldTypes.size() != d.fieldTypes.size()) {
532            throw new ParseException(
533                "At least one annotation field is missing");
534        }
535        return ann;
536    }
537
538    private void parseAnnotations(AElement e)
539            throws IOException, ParseException {
540        while (checkChar('@')) {
541            AnnotationDef d = parseAnnotationHead();
542            AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(d);
543            if (ab == null) {
544                // don't care about the result
545                // but need to skip over it anyway
546                @SuppressWarnings("unused")
547                Object trash = parseAnnotationBody(d, AnnotationFactory.saf
548                        .beginAnnotation(d));
549            } else {
550                Annotation a = parseAnnotationBody(d, ab);
551                for (Annotation other : e.tlAnnotationsHere) {
552                    if (a.def.name.equals(other.def.name)) {
553                        System.err.println(
554                                "WARNING: duplicate annotation of type "
555                                        + a.def().name);
556                        continue;
557                    }
558                }
559                Annotation tla = a;
560                if (! tla.def.equals(d)) {
561                    throw new ParseException("Bad def");
562                }
563                e.tlAnnotationsHere.add(tla);
564            }
565        }
566    }
567
568    private ScalarAFT parseScalarAFT() throws IOException, ParseException {
569        for (BasicAFT baft : BasicAFT.bafts.values()) {
570            if (matchKeyword(baft.toString())) {
571                return baft;
572            }
573        }
574        // wasn't a BasicAFT
575        if (matchKeyword("Class")) {
576            return ClassTokenAFT.ctaft/* dumpParameterization() */;
577        } else if (matchKeyword("enum")) {
578            String name = expectQualifiedName();
579            if (abbreviate) {
580              int i = name.lastIndexOf('.');
581              if (i >= 0) {
582                String baseName = name.substring(i+1);
583                Set<String> set1 = scene.imports.get(name);
584                Set<String> set2 = scene.imports.get(baseName);
585                if (set1 == null) {
586                  set1 = new TreeSet<String>();
587                  scene.imports.put(name, set1);
588                }
589                if (set2 == null) {
590                  set2 = new TreeSet<String>();
591                  scene.imports.put(name, set2);
592                }
593                set1.add(name);
594                set2.add(name);
595                name = baseName;
596              }
597            }
598            return new EnumAFT(name);
599        } else if (matchKeyword("annotation-field")) {
600            String name = expectQualifiedName();
601            AnnotationDef ad = defs.get(name);
602            if (ad == null) {
603                throw new ParseException("Annotation type " + name + " used as a field before it is defined");
604            }
605            return new AnnotationAFT((AnnotationDef) ad);
606        } else {
607            throw new ParseException(
608                    "Expected the beginning of an annotation field type: "
609                    + "a primitive type, `String', `Class', `enum', or `annotation-field'. Got '"
610                    + st.sval + "'.");
611        }
612    }
613
614    private AnnotationFieldType parseAFT() throws IOException,
615            ParseException {
616        if (matchKeyword("unknown")) {
617            // Handle unknown[]; see AnnotationBuilder#addEmptyArrayField
618            expectChar('[');
619            expectChar(']');
620            return new ArrayAFT(null);
621        }
622        ScalarAFT baseAFT = parseScalarAFT();
623        // only one level of array is permitted
624        if (matchChar('[')) {
625            expectChar(']');
626            return new ArrayAFT(baseAFT);
627        } else {
628            return baseAFT;
629        }
630    }
631
632    private void parseAnnotationDef() throws IOException, ParseException {
633        expectKeyword("annotation");
634
635        expectChar('@');
636        String basename = expectIdentifier();
637        String fullName = curPkgPrefix + basename;
638
639        AnnotationDef ad = new AnnotationDef(fullName);
640        expectChar(':');
641        parseAnnotations(ad);
642
643        Map<String, AnnotationFieldType> fields =
644                new LinkedHashMap<String, AnnotationFieldType>();
645
646        // yuck; it would be nicer to do a positive match
647        while (st.ttype != TT_EOF && !checkKeyword("annotation")
648               && !checkKeyword("class") && !checkKeyword("package")) {
649            AnnotationFieldType type = parseAFT();
650            String name = expectIdentifier();
651            if (fields.containsKey(name)) {
652                throw new ParseException("Duplicate definition of field "
653                                         + name);
654            }
655            fields.put(name, type);
656        }
657
658        ad.setFieldTypes(fields);
659
660        // Now add the definition to the map of all definitions.
661        addDef(ad, basename);
662
663    }
664
665    // Add the definition to the map of all definitions.
666    // also see addDef(AnnotationDef, String).
667    public void addDef(AnnotationDef ad) throws ParseException {
668        String basename = ad.name;
669        int dotPos = basename.lastIndexOf('.');
670        if (dotPos != -1) {
671            basename = basename.substring(dotPos + 1);
672        }
673        addDef(ad, basename);
674    }
675
676    // Add the definition to the map of all definitions.
677    public void addDef(AnnotationDef ad, String basename) throws ParseException {
678        // System.out.println("addDef:" + ad);
679
680        if (defs.containsKey(ad.name)) {
681            // TODO:  permit identical re-definition
682            System.err.println("Duplicate definition of annotation type " + ad.name);
683        }
684        defs.put(ad.name, ad);
685        // Add short name; but if it's already there, remove it to avoid ambiguity.
686        if (! basename.equals(ad.name)) {
687            if (defs.containsKey(basename)) {
688                // not "defs.remove(basename)" because then a subsequent
689                // one could get added, which would be wrong.
690                defs.put(basename, null);
691            } else {
692                defs.put(basename, ad);
693            }
694        }
695    }
696
697
698    private void parseInnerTypes(ATypeElement e)
699            throws IOException, ParseException {
700        parseInnerTypes(e, 0);
701    }
702
703    private void parseInnerTypes(ATypeElement e, int offset)
704            throws IOException, ParseException {
705        while (matchKeyword("inner-type")) {
706            ArrayList<Integer> locNumbers =
707                    new ArrayList<Integer>();
708            locNumbers.add(offset + expectNonNegative(matchNNInteger()));
709            // TODO: currently, we simply read the binary representation.
710            // Should we read a higher-level format?
711            while (matchChar(',')) {
712                locNumbers.add(expectNonNegative(matchNNInteger()));
713            }
714            InnerTypeLocation loc;
715            try {
716                loc = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(locNumbers));
717            } catch (AssertionError ex) {
718                throw new ParseException(ex.getMessage(), ex);
719            }
720            AElement it = e.innerTypes.vivify(loc);
721            expectChar(':');
722            parseAnnotations(it);
723        }
724    }
725
726    private void parseBounds(VivifyingMap<BoundLocation, ATypeElement> bounds)
727        throws IOException, ParseException {
728        while (checkKeyword("typeparam") || checkKeyword("bound")) {
729            if (matchKeyword("typeparam")) {
730                int paramIndex = expectNonNegative(matchNNInteger());
731                BoundLocation bl = new BoundLocation(paramIndex, -1);
732                ATypeElement b = bounds.vivify(bl);
733                expectChar(':');
734                parseAnnotations(b);
735                // does this make sense?
736                parseInnerTypes(b);
737            } else if (matchKeyword("bound")) {
738                // expectChar(',');
739                int paramIndex = expectNonNegative(matchNNInteger());
740                expectChar('&');
741                int boundIndex = expectNonNegative(matchNNInteger());
742                BoundLocation bl = new BoundLocation(paramIndex, boundIndex);
743                ATypeElement b = bounds.vivify(bl);
744                expectChar(':');
745                parseAnnotations(b);
746                // does this make sense?
747                parseInnerTypes(b);
748            } else {
749                throw new Error("impossible");
750            }
751        }
752    }
753
754    private void parseExtends(AClass cls) throws IOException, ParseException {
755        expectKeyword("extends");
756        TypeIndexLocation idx = new TypeIndexLocation(-1);
757        ATypeElement ext = cls.extendsImplements.vivify(idx);
758        expectChar(':');
759        parseAnnotations(ext);
760        parseInnerTypes(ext);
761    }
762
763    private void parseImplements(AClass cls) throws IOException, ParseException {
764        expectKeyword("implements");
765        int implIndex = expectNonNegative(matchNNInteger());
766        TypeIndexLocation idx = new TypeIndexLocation(implIndex);
767        ATypeElement impl = cls.extendsImplements.vivify(idx);
768        expectChar(':');
769        parseAnnotations(impl);
770        parseInnerTypes(impl);
771    }
772
773    private void parseField(AClass c) throws IOException,
774            ParseException {
775        expectKeyword("field");
776        String name = expectIdentifier();
777        AField f = c.fields.vivify(name);
778
779        expectChar(':');
780        parseAnnotations(f);
781        if (checkKeyword("type") && matchKeyword("type")) {
782            expectChar(':');
783            parseAnnotations(f.type);
784            parseInnerTypes(f.type);
785        }
786
787        f.init = c.fieldInits.vivify(name);
788        parseExpression(f.init);
789        parseASTInsertions(f);
790    }
791
792    private void parseStaticInit(AClass c) throws IOException,
793            ParseException {
794        expectKeyword("staticinit");
795        expectChar('*');
796        int blockIndex = expectNonNegative(matchNNInteger());
797        expectChar(':');
798
799        ABlock staticinit = c.staticInits.vivify(blockIndex);
800        parseBlock(staticinit);
801    }
802
803    private void parseInstanceInit(AClass c) throws IOException,
804            ParseException {
805        expectKeyword("instanceinit");
806        expectChar('*');
807        int blockIndex = expectNonNegative(matchNNInteger());
808        expectChar(':');
809
810        ABlock instanceinit = c.instanceInits.vivify(blockIndex);
811        parseBlock(instanceinit);
812    }
813
814    private void parseMethod(AClass c) throws IOException,
815            ParseException {
816        expectKeyword("method");
817        // special case: method could be <init> or <clinit>
818        String key;
819        if (matchChar('<')) {
820            String basename = expectIdentifier();
821            if (!(basename.equals("init") || basename.equals("clinit"))) {
822                throw new ParseException(
823                    "The only special methods allowed are <init> and <clinit>");
824            }
825            expectChar('>');
826            key = "<" + basename + ">";
827        } else {
828            key = expectIdentifier();
829            // too bad className is private in AClass and thus must be
830            // extracted from what toString() returns
831            if (Pattern.matches("AClass: (?:[^. ]+\\.)*" + key,
832                    c.toString())) {  // ugh
833                key = "<init>";
834            }
835        }
836
837        expectChar('(');
838        key += '(';
839        while (!matchChar(':')) {
840            if (st.ttype >= 0) {
841                key += st.ttype == 46 ? '/' :(char) st.ttype;
842            } else if (st.ttype == TT_WORD) {
843                key += st.sval;
844            } else {
845                throw new ParseException("Found something that doesn't belong in a signature");
846            }
847            st.nextToken();
848        }
849
850        AMethod m = c.methods.vivify(key);
851        parseAnnotations(m);
852        parseMethod(m);
853    }
854
855    private void parseMethod(AMethod m) throws IOException, ParseException {
856        parseBounds(m.bounds);
857
858        // Permit return value, receiver, and parameters in any order.
859        while (checkKeyword("return") || checkKeyword("receiver") || checkKeyword("parameter")) {
860            if (matchKeyword("return")) {
861                expectChar(':');
862                parseAnnotations(m.returnType);
863                parseInnerTypes(m.returnType);
864            } else if (matchKeyword("parameter")) {
865                // make "#" optional
866                if (checkChar('#')) {
867                    matchChar('#');
868                }
869                int idx = expectNonNegative(matchNNInteger());
870                AField p = m.parameters.vivify(idx);
871                expectChar(':');
872                parseAnnotations(p);
873                if (checkKeyword("type") && matchKeyword("type")) {
874                    expectChar(':');
875                    parseAnnotations(p.type);
876                    parseInnerTypes(p.type);
877                }
878            } else if (matchKeyword("receiver")) {
879                expectChar(':');
880                parseAnnotations(m.receiver.type);
881                parseInnerTypes(m.receiver.type);
882            } else {
883                throw new Error("This can't happen");
884            }
885        }
886
887        parseBlock(m.body);
888        parseASTInsertions(m);
889    }
890
891    private void parseLambda(AMethod m) throws IOException, ParseException {
892        while (checkKeyword("parameter")) {
893            matchKeyword("parameter");
894            // make "#" optional
895            if (checkChar('#')) {
896                matchChar('#');
897            }
898            int idx = expectNonNegative(matchNNInteger());
899            AField p = m.parameters.vivify(idx);
900            expectChar(':');
901            parseAnnotations(p);
902            if (checkKeyword("type") && matchKeyword("type")) {
903                expectChar(':');
904                parseAnnotations(p.type);
905                parseInnerTypes(p.type);
906            }
907        }
908
909        // parseBlock(m.body, true);
910        parseASTInsertions(m);
911    }
912
913    private void parseBlock(ABlock bl) throws IOException,
914            ParseException {
915        boolean matched = true;
916
917        while (matched) {
918            matched = false;
919
920            while (checkKeyword("local")) {
921                matchKeyword("local");
922                matched = true;
923                LocalLocation loc;
924                if (checkNNInteger() != -1) {
925                    // the local variable is specified by bytecode index/range
926                    int index = expectNonNegative(matchNNInteger());
927                    expectChar('#');
928                    int scopeStart = expectNonNegative(matchNNInteger());
929                    expectChar('+');
930                    int scopeLength = expectNonNegative(matchNNInteger());
931                    loc = new LocalLocation(index, scopeStart, scopeLength);
932                } else {
933                    // look for a valid identifier for the local variable
934                    String lvar = expectIdentifier();
935                    int varIndex;
936                    if (checkChar('*')) {
937                        expectChar('*');
938                        varIndex = expectNonNegative(matchNNInteger());
939                    } else {
940                        // default the variable index to 0, the most common case
941                        varIndex = 0;
942                    }
943                    loc = new LocalLocation(lvar, varIndex);
944                }
945                AField l = bl.locals.vivify(loc);
946                expectChar(':');
947                parseAnnotations(l);
948                if (checkKeyword("type") && matchKeyword("type")) {
949                    expectChar(':');
950                    parseAnnotations(l.type);
951                    parseInnerTypes(l.type);
952                }
953            }
954            matched = parseExpression(bl) || matched;
955        }
956    }
957
958    private boolean parseExpression(AExpression exp) throws IOException,
959            ParseException {
960        boolean matched = true;
961        boolean evermatched = false;
962
963        while (matched) {
964            matched = false;
965
966            while (checkKeyword("typecast")) {
967                matchKeyword("typecast");
968                matched = true;
969                evermatched = true;
970                RelativeLocation loc;
971                if (checkChar('#')) {
972                    expectChar('#');
973                    int offset = expectNonNegative(matchNNInteger());
974                    int type_index = 0;
975                    if (checkChar(',')) {
976                        expectChar(',');
977                        type_index = expectNonNegative(matchNNInteger());
978                    }
979                    loc = RelativeLocation.createOffset(offset, type_index);
980                } else {
981                    expectChar('*');
982                    int index = expectNonNegative(matchNNInteger());
983                    int type_index = 0;
984                    if (checkChar(',')) {
985                        expectChar(',');
986                        type_index = expectNonNegative(matchNNInteger());
987                    }
988                    loc = RelativeLocation.createIndex(index, type_index);
989                }
990                ATypeElement t = exp.typecasts.vivify(loc);
991                expectChar(':');
992                parseAnnotations(t);
993                parseInnerTypes(t);
994            }
995            while (checkKeyword("instanceof")) {
996                matchKeyword("instanceof");
997                matched = true;
998                evermatched = true;
999                RelativeLocation loc;
1000                if (checkChar('#')) {
1001                    expectChar('#');
1002                    int offset = expectNonNegative(matchNNInteger());
1003                    loc = RelativeLocation.createOffset(offset, 0);
1004                } else {
1005                    expectChar('*');
1006                    int index = expectNonNegative(matchNNInteger());
1007                    loc = RelativeLocation.createIndex(index, 0);
1008                }
1009                ATypeElement i = exp.instanceofs.vivify(loc);
1010                expectChar(':');
1011                parseAnnotations(i);
1012                parseInnerTypes(i);
1013            }
1014            while (checkKeyword("new")) {
1015                matchKeyword("new");
1016                matched = true;
1017                evermatched = true;
1018                RelativeLocation loc;
1019                if (checkChar('#')) {
1020                    expectChar('#');
1021                    int offset = expectNonNegative(matchNNInteger());
1022                    loc = RelativeLocation.createOffset(offset, 0);
1023                } else {
1024                    expectChar('*');
1025                    int index = expectNonNegative(matchNNInteger());
1026                    loc = RelativeLocation.createIndex(index, 0);
1027                }
1028                ATypeElement n = exp.news.vivify(loc);
1029                expectChar(':');
1030                parseAnnotations(n);
1031                parseInnerTypes(n);
1032            }
1033            while (checkKeyword("call")) {
1034                matchKeyword("call");
1035                matched = true;
1036                evermatched = true;
1037                int i;
1038                boolean isOffset = checkChar('#');
1039                expectChar(isOffset ? '#' : '*');
1040                i = expectNonNegative(matchNNInteger());
1041                expectChar(':');
1042                while (checkKeyword("typearg")) {
1043                    matchKeyword("typearg");
1044                    if (checkChar('#')) { matchChar('#'); }
1045                    int type_index = expectNonNegative(matchNNInteger());
1046                    RelativeLocation loc = isOffset
1047                            ? RelativeLocation.createOffset(i, type_index)
1048                            : RelativeLocation.createIndex(i, type_index);
1049                    ATypeElement t = exp.calls.vivify(loc);
1050                    expectChar(':');
1051                    parseAnnotations(t);
1052                    parseInnerTypes(t);
1053                }
1054            }
1055            while (checkKeyword("reference")) {
1056                matchKeyword("reference");
1057                matched = true;
1058                evermatched = true;
1059                ATypeElement t;
1060                RelativeLocation loc;
1061                int i;
1062                boolean isOffset = checkChar('#');
1063                if (isOffset) {
1064                    expectChar('#');
1065                    i = expectNonNegative(matchNNInteger());
1066                    loc = RelativeLocation.createOffset(i, 0);
1067                } else {
1068                    expectChar('*');
1069                    i = expectNonNegative(matchNNInteger());
1070                    loc = RelativeLocation.createIndex(i, 0);
1071                }
1072                expectChar(':');
1073                t = exp.refs.vivify(loc);
1074                parseAnnotations(t);
1075                parseInnerTypes(t);
1076                while (checkKeyword("typearg")) {
1077                    matchKeyword("typearg");
1078                    if (checkChar('#')) { matchChar('#'); }
1079                    int type_index = expectNonNegative(matchNNInteger());
1080                    loc = isOffset
1081                        ? RelativeLocation.createOffset(i, type_index)
1082                        : RelativeLocation.createIndex(i, type_index);
1083                    t = exp.refs.vivify(loc);
1084                    expectChar(':');
1085                    parseAnnotations(t);
1086                    parseInnerTypes(t);
1087                }
1088            }
1089            while (checkKeyword("lambda")) {
1090                matchKeyword("lambda");
1091                matched = true;
1092                evermatched = true;
1093                RelativeLocation loc;
1094                if (checkChar('#')) {
1095                    expectChar('#');
1096                    int offset = expectNonNegative(matchNNInteger());
1097                    int type_index = 0;
1098                    if (checkChar(',')) {
1099                        expectChar(',');
1100                        type_index = expectNonNegative(matchNNInteger());
1101                    }
1102                    loc = RelativeLocation.createOffset(offset, type_index);
1103                } else {
1104                    expectChar('*');
1105                    int index = expectNonNegative(matchNNInteger());
1106                    int type_index = 0;
1107                    if (checkChar(',')) {
1108                        expectChar(',');
1109                        type_index = expectNonNegative(matchNNInteger());
1110                    }
1111                    loc = RelativeLocation.createIndex(index, type_index);
1112                }
1113                AMethod m = exp.funs.vivify(loc);
1114                expectChar(':');
1115                // parseAnnotations(m);
1116                parseLambda(m);
1117                // parseMethod(m);
1118            }
1119        }
1120        return evermatched;
1121    }
1122
1123    private static boolean isTypeSelector(String selector) {
1124      return Arrays.<String>binarySearch(typeSelectors, selector, Collator.getInstance()) >= 0;
1125    }
1126
1127    private static boolean selectsExpression(ASTPath astPath) {
1128        int n = astPath.size();
1129        if (--n >= 0) {
1130            ASTPath.ASTEntry entry = astPath.get(n);
1131            while (--n >= 0 && entry.getTreeKind() == Kind.MEMBER_SELECT
1132                    && entry.childSelectorIs(ASTPath.EXPRESSION)) {
1133              entry = astPath.get(n);
1134            }
1135            return !isTypeSelector(entry.getChildSelector());
1136        }
1137        return false;
1138    }
1139
1140    private boolean parseASTInsertions(ADeclaration decl)
1141            throws IOException, ParseException {
1142        boolean matched = false;
1143        while (checkKeyword("insert-annotation")) {
1144            matched = true;
1145            matchKeyword("insert-annotation");
1146            ASTPath astPath = parseASTPath();
1147            expectChar(':');
1148            // if path doesn't indicate a type, a cast must be generated
1149            if (selectsExpression(astPath)) {
1150                ATypeElementWithType i = decl.insertTypecasts.vivify(astPath);
1151                parseAnnotations(i);
1152                i.setType(new DeclaredType());
1153                parseInnerTypes(i);
1154            } else {
1155                // astPath = fixNewArrayType(astPath);  // handle special case
1156                // ATypeElement i = decl.insertAnnotations.vivify(astPath);
1157                // parseAnnotations(i);
1158                // parseInnerTypes(i);
1159                int offset = 0;
1160                Pair<ASTPath, InnerTypeLocation> pair =
1161                        splitNewArrayType(astPath);  // handle special case
1162                ATypeElement i;
1163                if (pair == null) {
1164                    i = decl.insertAnnotations.vivify(astPath);
1165                } else {
1166                    i = decl.insertAnnotations.vivify(pair.a);
1167                    if (pair.b != null) {
1168                        i = i.innerTypes.vivify(pair.b);
1169                        offset = pair.b.location.size();
1170                    }
1171                }
1172                parseAnnotations(i);
1173                parseInnerTypes(i, offset);
1174            }
1175        }
1176        while (checkKeyword("insert-typecast")) {
1177            matched = true;
1178            matchKeyword("insert-typecast");
1179            ASTPath astPath = parseASTPath();
1180            expectChar(':');
1181            ATypeElementWithType i = decl.insertTypecasts.vivify(astPath);
1182            parseAnnotations(i);
1183            Type type = parseType();
1184            i.setType(type);
1185            parseInnerTypes(i);
1186        }
1187        return matched;
1188    }
1189
1190    // Due to the unfortunate representation of new array expressions,
1191    // ASTPaths to their inner array types break the usual rule that
1192    // an ASTPath corresponds to an AST node.  This method restores the
1193    // invariant by separating out the inner type information.
1194    private Pair<ASTPath, InnerTypeLocation> splitNewArrayType(ASTPath astPath) {
1195        ASTPath outerPath = astPath;
1196        InnerTypeLocation loc = null;
1197        int last = astPath.size() - 1;
1198
1199        if (last > 0) {
1200            ASTPath.ASTEntry entry = astPath.get(last);
1201            if (entry.getTreeKind() == Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE)) {
1202                int a = entry.getArgument();
1203                if (a > 0) {
1204                    outerPath = astPath.getParentPath().extend(new ASTPath.ASTEntry(Kind.NEW_ARRAY, ASTPath.TYPE, 0));
1205            loc = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(Collections.nCopies(2 * a, 0)));
1206                }
1207            }
1208        }
1209
1210        return Pair.of(outerPath, loc);
1211    }
1212
1213    private ASTPath fixNewArrayType(ASTPath astPath) {
1214        ASTPath outerPath = astPath;
1215        int last = astPath.size() - 1;
1216
1217        if (last > 0) {
1218            ASTPath.ASTEntry entry = astPath.get(last);
1219            if (entry.getTreeKind() == Kind.NEW_ARRAY && entry.childSelectorIs(ASTPath.TYPE)) {
1220                int a = entry.getArgument();
1221                outerPath = astPath.getParentPath().extend(new ASTPath.ASTEntry(Kind.NEW_ARRAY, ASTPath.TYPE, 0));
1222                while (--a >= 0) {
1223                    outerPath = outerPath.extend(new ASTPath.ASTEntry(Kind.ARRAY_TYPE, ASTPath.TYPE));
1224                }
1225            }
1226        }
1227
1228        return outerPath;
1229    }
1230
1231    /**
1232     * Parses an AST path.
1233     * @return the AST path
1234     */
1235    private ASTPath parseASTPath() throws IOException, ParseException {
1236        ASTPath astPath = ASTPath.empty().extend(parseASTEntry());
1237        while (matchChar(',')) {
1238            astPath = astPath.extend(parseASTEntry());
1239        }
1240        return astPath;
1241    }
1242
1243    /**
1244     * Parses and returns the next AST entry.
1245     * @return a new AST entry
1246     * @throws ParseException if the next entry type is invalid
1247     */
1248    private ASTPath.ASTEntry parseASTEntry() throws IOException, ParseException {
1249        ASTPath.ASTEntry entry;
1250        if (matchKeyword("AnnotatedType")) {
1251            entry = newASTEntry(Kind.ANNOTATED_TYPE, new String[] {ASTPath.ANNOTATION, ASTPath.UNDERLYING_TYPE},
1252                    new String[] {ASTPath.ANNOTATION});
1253        } else if (matchKeyword("ArrayAccess")) {
1254            entry = newASTEntry(Kind.ARRAY_ACCESS, new String[] {ASTPath.EXPRESSION, ASTPath.INDEX});
1255        } else if (matchKeyword("ArrayType")) {
1256            entry = newASTEntry(Kind.ARRAY_TYPE, new String[] {ASTPath.TYPE});
1257        } else if (matchKeyword("Assert")) {
1258            entry = newASTEntry(Kind.ASSERT, new String[] {ASTPath.CONDITION, ASTPath.DETAIL});
1259        } else if (matchKeyword("Assignment")) {
1260            entry = newASTEntry(Kind.ASSIGNMENT, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION});
1261        } else if (matchKeyword("Binary")) {
1262            // Always use Kind.PLUS for Binary
1263            entry = newASTEntry(Kind.PLUS, new String[] {ASTPath.LEFT_OPERAND, ASTPath.RIGHT_OPERAND});
1264        } else if (matchKeyword("Block")) {
1265            entry = newASTEntry(Kind.BLOCK, new String[] {ASTPath.STATEMENT}, new String[] {ASTPath.STATEMENT});
1266        } else if (matchKeyword("Case")) {
1267            entry = newASTEntry(Kind.CASE, new String[] {ASTPath.EXPRESSION, ASTPath.STATEMENT},
1268                    new String[] {ASTPath.STATEMENT});
1269        } else if (matchKeyword("Catch")) {
1270            entry = newASTEntry(Kind.CATCH, new String[] {ASTPath.PARAMETER, ASTPath.BLOCK});
1271        } else if (matchKeyword("Class")) {
1272            entry = newASTEntry(Kind.CLASS,
1273                    new String[] {ASTPath.BOUND, ASTPath.INITIALIZER, ASTPath.TYPE_PARAMETER},
1274                    new String[] {ASTPath.BOUND, ASTPath.INITIALIZER, ASTPath.TYPE_PARAMETER});
1275        } else if (matchKeyword("CompoundAssignment")) {
1276            // Always use Kind.PLUS_ASSIGNMENT for CompoundAssignment
1277            entry = newASTEntry(Kind.PLUS_ASSIGNMENT, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION});
1278        } else if (matchKeyword("ConditionalExpression")) {
1279            entry = newASTEntry(Kind.CONDITIONAL_EXPRESSION,
1280                    new String[] {ASTPath.CONDITION, ASTPath.TRUE_EXPRESSION, ASTPath.FALSE_EXPRESSION});
1281        } else if (matchKeyword("DoWhileLoop")) {
1282            entry = newASTEntry(Kind.DO_WHILE_LOOP, new String[] {ASTPath.CONDITION, ASTPath.STATEMENT});
1283        } else if (matchKeyword("EnhancedForLoop")) {
1284            entry = newASTEntry(Kind.ENHANCED_FOR_LOOP, new String[] {ASTPath.VARIABLE, ASTPath.EXPRESSION, ASTPath.STATEMENT});
1285        } else if (matchKeyword("ExpressionStatement")) {
1286            entry = newASTEntry(Kind.EXPRESSION_STATEMENT, new String[] {ASTPath.EXPRESSION});
1287        } else if (matchKeyword("ForLoop")) {
1288            entry = newASTEntry(Kind.FOR_LOOP, new String[] {ASTPath.INITIALIZER, ASTPath.CONDITION, ASTPath.UPDATE, ASTPath.STATEMENT},
1289                    new String[] {ASTPath.INITIALIZER, ASTPath.UPDATE});
1290        } else if (matchKeyword("If")) {
1291            entry = newASTEntry(Kind.IF, new String[] {ASTPath.CONDITION, ASTPath.THEN_STATEMENT, ASTPath.ELSE_STATEMENT});
1292        } else if (matchKeyword("InstanceOf")) {
1293            entry = newASTEntry(Kind.INSTANCE_OF, new String[] {ASTPath.EXPRESSION, ASTPath.TYPE});
1294        } else if (matchKeyword("LabeledStatement")) {
1295            entry = newASTEntry(Kind.LABELED_STATEMENT, new String[] {ASTPath.STATEMENT});
1296        } else if (matchKeyword("LambdaExpression")) {
1297            entry = newASTEntry(Kind.LAMBDA_EXPRESSION, new String[] {ASTPath.PARAMETER, ASTPath.BODY},
1298                    new String[] {ASTPath.PARAMETER});
1299        } else if (matchKeyword("MemberReference")) {
1300            entry = newASTEntry(Kind.MEMBER_REFERENCE, new String[] {ASTPath.QUALIFIER_EXPRESSION, ASTPath.TYPE_ARGUMENT},
1301                    new String[] {ASTPath.TYPE_ARGUMENT});
1302        } else if (matchKeyword("MemberSelect")) {
1303            entry = newASTEntry(Kind.MEMBER_SELECT, new String[] {ASTPath.EXPRESSION});
1304        } else if (matchKeyword("Method")) {
1305            entry = newASTEntry(Kind.METHOD, new String[] {ASTPath.BODY, ASTPath.TYPE, ASTPath.PARAMETER, ASTPath.TYPE_PARAMETER},
1306                    new String[] {ASTPath.PARAMETER, ASTPath.TYPE_PARAMETER});
1307        } else if (matchKeyword("MethodInvocation")) {
1308            entry = newASTEntry(Kind.METHOD_INVOCATION, new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.METHOD_SELECT, ASTPath.ARGUMENT},
1309                    new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT});
1310        } else if (matchKeyword("NewArray")) {
1311            entry = newASTEntry(Kind.NEW_ARRAY, new String[] {ASTPath.TYPE, ASTPath.DIMENSION, ASTPath.INITIALIZER},
1312                    new String[] {ASTPath.TYPE, ASTPath.DIMENSION, ASTPath.INITIALIZER});
1313        } else if (matchKeyword("NewClass")) {
1314            entry = newASTEntry(Kind.NEW_CLASS, new String[] {ASTPath.ENCLOSING_EXPRESSION, ASTPath.TYPE_ARGUMENT, ASTPath.IDENTIFIER, ASTPath.ARGUMENT, ASTPath.CLASS_BODY},
1315                    new String[] {ASTPath.TYPE_ARGUMENT, ASTPath.ARGUMENT});
1316        } else if (matchKeyword("ParameterizedType")) {
1317            entry = newASTEntry(Kind.PARAMETERIZED_TYPE, new String[] {ASTPath.TYPE, ASTPath.TYPE_ARGUMENT},
1318                    new String[] {ASTPath.TYPE_ARGUMENT});
1319        } else if (matchKeyword("Parenthesized")) {
1320            entry = newASTEntry(Kind.PARENTHESIZED, new String[] {ASTPath.EXPRESSION});
1321        } else if (matchKeyword("Return")) {
1322            entry = newASTEntry(Kind.RETURN, new String[] {ASTPath.EXPRESSION});
1323        } else if (matchKeyword("Switch")) {
1324            entry = newASTEntry(Kind.SWITCH, new String[] {ASTPath.EXPRESSION, ASTPath.CASE},
1325                    new String[] {ASTPath.CASE});
1326        } else if (matchKeyword("Synchronized")) {
1327            entry = newASTEntry(Kind.SYNCHRONIZED, new String[] {ASTPath.EXPRESSION, ASTPath.BLOCK});
1328        } else if (matchKeyword("Throw")) {
1329            entry = newASTEntry(Kind.THROW, new String[] {ASTPath.EXPRESSION});
1330        } else if (matchKeyword("Try")) {
1331            entry = newASTEntry(Kind.TRY, new String[] {ASTPath.BLOCK, ASTPath.CATCH, ASTPath.FINALLY_BLOCK},
1332                    new String[] {ASTPath.CATCH});
1333        } else if (matchKeyword("TypeCast")) {
1334            entry = newASTEntry(Kind.TYPE_CAST, new String[] {ASTPath.TYPE, ASTPath.EXPRESSION});
1335        } else if (matchKeyword("TypeParameter")) {
1336            entry = newASTEntry(Kind.TYPE_PARAMETER, new String[] {ASTPath.BOUND},
1337                    new String[] {ASTPath.BOUND});
1338        } else if (matchKeyword("Unary")) {
1339            // Always use Kind.UNARY_PLUS for Unary
1340            entry = newASTEntry(Kind.UNARY_PLUS, new String[] {ASTPath.EXPRESSION});
1341        } else if (matchKeyword("UnionType")) {
1342            entry = newASTEntry(Kind.UNION_TYPE, new String[] {ASTPath.TYPE_ALTERNATIVE},
1343                    new String[] {ASTPath.TYPE_ALTERNATIVE});
1344        } else if (matchKeyword("Variable")) {
1345            entry = newASTEntry(Kind.VARIABLE, new String[] {ASTPath.TYPE, ASTPath.INITIALIZER});
1346        } else if (matchKeyword("WhileLoop")) {
1347            entry = newASTEntry(Kind.WHILE_LOOP, new String[] {ASTPath.CONDITION, ASTPath.STATEMENT});
1348        } else if (matchKeyword("Wildcard")) {
1349            // Always use Kind.UNBOUNDED_WILDCARD for Wildcard
1350            entry = newASTEntry(Kind.UNBOUNDED_WILDCARD, new String[] {ASTPath.BOUND});
1351        } else {
1352            throw new ParseException("Invalid AST path type: " + st.sval);
1353        }
1354        return entry;
1355    }
1356
1357    /**
1358     * Parses and constructs a new AST entry, where none of the child selections require
1359     * arguments. For example, the call:
1360     *
1361     * <pre>
1362     * {@code newASTEntry(Kind.WHILE_LOOP, new String[] {"condition", "statement"});</pre>
1363     *
1364     * constructs a while loop AST entry, where the valid child selectors are "condition" or
1365     * "statement".
1366     *
1367     * @param kind the kind of this AST entry
1368     * @param legalChildSelectors a list of the legal child selectors for this AST entry
1369     * @return a new {@link ASTPath.ASTEntry}
1370     * @throws ParseException if an illegal argument is found
1371     */
1372    private ASTPath.ASTEntry newASTEntry(Kind kind, String[] legalChildSelectors) throws IOException, ParseException {
1373        return newASTEntry(kind, legalChildSelectors, null);
1374    }
1375
1376    /**
1377     * Parses and constructs a new AST entry. For example, the call:
1378     *
1379     * <pre>
1380     * {@code newASTEntry(Kind.CASE, new String[] {"expression", "statement"}, new String[] {"statement"});
1381     * </pre>
1382     *
1383     * constructs a case AST entry, where the valid child selectors are
1384     * "expression" or "statement" and the "statement" child selector requires
1385     * an argument.
1386     *
1387     * @param kind the kind of this AST entry
1388     * @param legalChildSelectors a list of the legal child selectors for this AST entry
1389     * @param argumentChildSelectors a list of the child selectors that also require an argument.
1390     *                               Entries here should also be in the legalChildSelectors list.
1391     * @return a new {@link ASTPath.ASTEntry}
1392     * @throws ParseException if an illegal argument is found
1393     */
1394    private ASTPath.ASTEntry newASTEntry(Kind kind, String[] legalChildSelectors, String[] argumentChildSelectors) throws IOException, ParseException {
1395        expectChar('.');
1396        for (String arg : legalChildSelectors) {
1397            if (matchKeyword(arg)) {
1398                if (argumentChildSelectors != null && ArraysMDE.indexOf(argumentChildSelectors, arg) >= 0) {
1399                    int index = matchNNInteger();
1400                    return new ASTPath.ASTEntry(kind, arg, index);
1401                } else {
1402                    return new ASTPath.ASTEntry(kind, arg);
1403                }
1404            }
1405        }
1406        throw new ParseException("Invalid argument for " + kind + " (legal arguments - " + Arrays.toString(legalChildSelectors) + "): " + st.sval);
1407    }
1408
1409    /**
1410     * Parses the next tokens as a Java type.
1411     */
1412    private Type parseType() throws IOException, ParseException {
1413        DeclaredType declaredType;
1414        if (matchChar('?')) {
1415            declaredType = new DeclaredType(DeclaredType.WILDCARD);
1416        } else {
1417            declaredType = parseDeclaredType();
1418        }
1419        if (checkKeyword("extends") || checkKeyword("super")) {
1420            return parseBoundedType(declaredType);
1421        } else {
1422            Type type = declaredType;
1423            while (matchChar('[')) {
1424                expectChar(']');
1425                type = new ArrayType(type);
1426            }
1427            return type;
1428        }
1429    }
1430
1431    /**
1432     * Parses the next tokens as a declared type.
1433     */
1434    private DeclaredType parseDeclaredType() throws IOException, ParseException {
1435        String type = matchIdentifier();
1436        if (type == null) {
1437            type = matchPrimitiveType();
1438            if (type == null) {
1439                throw new ParseException("Expected identifier or primitive type");
1440            }
1441        }
1442        return parseDeclaredType(type);
1443    }
1444
1445    /**
1446     * Parses the next tokens as a declared type.
1447     * @param name the name of the initial identifier
1448     */
1449    private DeclaredType parseDeclaredType(String name) throws IOException, ParseException {
1450        DeclaredType type = new DeclaredType(name);
1451        if (matchChar('<')) {
1452            type.addTypeParameter(parseType());
1453            while (matchChar(',')) {
1454                type.addTypeParameter(parseType());
1455            }
1456            expectChar('>');
1457        }
1458        if (matchChar('.')) {
1459            type.setInnerType(parseDeclaredType());
1460        }
1461        return type;
1462    }
1463
1464    /**
1465     * Parses the next tokens as a bounded type.
1466     * @param type the name, which precedes "extends" or "super"
1467     */
1468    private BoundedType parseBoundedType(DeclaredType type) throws IOException, ParseException {
1469        BoundKind kind;
1470        if (matchKeyword("extends")) {
1471            kind = BoundKind.EXTENDS;
1472        } else if (matchKeyword("super")) {
1473            kind = BoundKind.SUPER;
1474        } else {
1475            throw new ParseException("Illegal bound kind: " + st.sval);
1476        }
1477        return new BoundedType(type, kind, parseDeclaredType());
1478    }
1479
1480    private void parseClass() throws IOException, ParseException {
1481        expectKeyword("class");
1482        String basename = expectIdentifier();
1483        String fullName = curPkgPrefix + basename;
1484
1485        AClass c = scene.classes.vivify(fullName);
1486        expectChar(':');
1487
1488        parseAnnotations(c);
1489        parseBounds(c.bounds);
1490
1491        while (checkKeyword("extends")) {
1492            parseExtends(c);
1493        }
1494        while (checkKeyword("implements")) {
1495            parseImplements(c);
1496        }
1497        parseASTInsertions(c);
1498
1499        while (checkKeyword("field")) {
1500            parseField(c);
1501        }
1502        while (checkKeyword("staticinit")) {
1503            parseStaticInit(c);
1504        }
1505        while (checkKeyword("instanceinit")) {
1506            parseInstanceInit(c);
1507        }
1508        while (checkKeyword("method")) {
1509            parseMethod(c);
1510        }
1511        c.methods.prune();
1512    }
1513
1514    // Reads the index file in this.st and puts the information in this.scene.
1515    private void parse() throws ParseException, IOException {
1516        st.nextToken();
1517
1518        while (st.ttype != TT_EOF) {
1519            expectKeyword("package");
1520
1521            String pkg;
1522            if (checkIdentifier() == null) {
1523                pkg = null;
1524                // the default package cannot be annotated
1525                matchChar(':');
1526            } else {
1527                pkg = expectQualifiedName();
1528                // AElement p = scene.packages.vivify(pkg);
1529                AClass p = scene.classes.vivify(pkg + ".package-info");
1530                expectChar(':');
1531                p = scene.classes.vivify(pkg + ".package-info");
1532                parseAnnotations(p);
1533            }
1534
1535            if (pkg != null) {
1536                curPkgPrefix = pkg + ".";
1537            } else {
1538                curPkgPrefix = "";
1539            }
1540
1541            for (;;) {
1542                if (checkKeyword("annotation")) {
1543                    parseAnnotationDef();
1544                } else if (checkKeyword("class")) {
1545                    parseClass();
1546                } else if (checkKeyword("package") || st.ttype == TT_EOF) {
1547                    break;
1548                } else {
1549                    throw new ParseException("Expected: `annotation', `class', or `package'. Found: `"
1550                            + st.sval + "', ttype:" + st.ttype);
1551                }
1552            }
1553        }
1554
1555/*
1556        for (Map.Entry<String, AnnotationDef> entry : defs.entrySet()) {
1557            final String annotationType = entry.getKey();
1558            AnnotationDef def = entry.getValue();
1559            for (AnnotationFieldType aft : def.fieldTypes.values()) {
1560                aft.accept(new AFTVisitor<Void, Void>() {
1561                    @Override
1562                    public Void visitAnnotationAFT(AnnotationAFT aft,
1563                            Void arg) {
1564                        for (AnnotationFieldType t : aft.annotationDef.fieldTypes.values()) {
1565                            t.accept(this, arg);
1566                        }
1567                        return null;
1568                    }
1569
1570                    @Override
1571                    public Void visitArrayAFT(ArrayAFT aft, Void arg) {
1572                      return aft.elementType == null ? null
1573                          : aft.elementType.accept(this, arg);
1574                    }
1575
1576                    @Override
1577                    public Void visitBasicAFT(BasicAFT aft, Void arg) {
1578                      return null;
1579                    }
1580
1581                    @Override
1582                    public Void visitClassTokenAFT(ClassTokenAFT aft, Void arg) {
1583                      return null;
1584                    }
1585
1586                    @Override
1587                    public Void visitEnumAFT(EnumAFT aft, Void arg) {
1588                        importSet(annotationType, aft).add(aft.typeName);
1589                        return null;
1590                    }
1591
1592                    private Set<String> importSet(final String annotationType,
1593                        AnnotationFieldType aft) {
1594                      Set<String> imps = scene.imports.get(annotationType);
1595                      if (imps == null) {
1596                          imps = new TreeSet<String>();
1597                          scene.imports.put(annotationType, imps);
1598                      }
1599                      return imps;
1600                    }
1601                }, null);
1602            }
1603        }
1604*/
1605    }
1606
1607    private IndexFileParser(Reader in, AScene scene) {
1608        defs = new LinkedHashMap<String, AnnotationDef>();
1609        for (AnnotationDef ad : Annotations.standardDefs) {
1610            try {
1611                addDef(ad);
1612            } catch (ParseException e) {
1613                throw new Error(e);
1614            }
1615        }
1616
1617        st = new StreamTokenizer(in);
1618        st.slashSlashComments(true);
1619
1620        // restrict numbers -- don't really need, could interfere with
1621        // annotation values
1622        // st.ordinaryChar('-');
1623        // HMMM this fixes fully-qualified-name strangeness but breaks
1624        // floating-point numbers
1625        st.ordinaryChar('.');
1626
1627        // argggh!!! stupid default needs to be overridden! see java bug 4217680
1628        st.ordinaryChar('/');
1629
1630        // for "type-argument"
1631        st.wordChars('-', '-');
1632
1633        // java identifiers can contain numbers, _, and $
1634        st.wordChars('0', '9');
1635        st.wordChars('_', '_');
1636        st.wordChars('$', '$');
1637
1638        this.scene = scene;
1639
1640        // See if the nonnull analysis picks up on this:
1641        // curPkgPrefix == ""; // will get changed later anyway
1642    }
1643
1644    /**
1645     * Reads annotations from <code>in</code> in index file format and merges
1646     * them into <code>scene</code>.  Annotations
1647     * from the input are merged into the scene; it is an error if both the
1648     * scene and the input contain annotations of the same type on the same
1649     * element.
1650     *
1651     * <p>
1652     * Since each annotation in a scene carries its own definition and the
1653     * scene as a whole no longer has a set of definitions, annotation
1654     * definitions that are given in the input but never used are not saved
1655     * anywhere and will not be included if the scene is written back to an
1656     * index file.  Similarly, retention policies on definitions of annotations
1657     * that are never used at the top level are dropped.
1658     *
1659     * <p>
1660     * Caveat: Parsing of floating point numbers currently does not work.
1661     */
1662    public static Map<String, AnnotationDef> parse(LineNumberReader in,
1663            AScene scene) throws IOException, ParseException {
1664        IndexFileParser parser = new IndexFileParser(in, scene);
1665        // no filename is available in the exception messages
1666        return parseAndReturnAnnotationDefs(null, in, parser);
1667    }
1668
1669    /**
1670     * Reads annotations from the index file <code>filename</code> and merges
1671     * them into <code>scene</code>; see {@link #parse(LineNumberReader, AScene)}.
1672     */
1673    public static Map<String, AnnotationDef> parseFile(String filename,
1674            AScene scene) throws IOException {
1675        LineNumberReader in = new LineNumberReader(new FileReader(filename));
1676        IndexFileParser parser = new IndexFileParser(in, scene);
1677        return parseAndReturnAnnotationDefs(filename, in, parser);
1678    }
1679
1680    /**
1681     * Reads annotations from the string (in index file format) and merges
1682     * them into <code>scene</code>; see {@link #parse(LineNumberReader, AScene)}.
1683     * Primarily for testing.
1684     */
1685    public static Map<String, AnnotationDef> parseString(String fileContents,
1686            AScene scene) throws IOException {
1687        String filename =
1688            "While parsing string: \n----------------BEGIN----------------\n"
1689                    + fileContents + "----------------END----------------\n";
1690        LineNumberReader in = new LineNumberReader(
1691            new StringReader(fileContents));
1692        IndexFileParser parser = new IndexFileParser(in, scene);
1693        return parseAndReturnAnnotationDefs(filename, in, parser);
1694    }
1695
1696    private static Map<String, AnnotationDef> parseAndReturnAnnotationDefs(
1697          String filename, LineNumberReader in, IndexFileParser parser)
1698              throws IOException {
1699      try {
1700          parser.parse();
1701          return Collections.unmodifiableMap(parser.defs);
1702      } catch (IOException e) {
1703          throw filename == null ? new FileIOException(in, e)
1704                  : new FileIOException(in, filename, e);
1705      } catch (ParseException e) {
1706          throw filename == null ? new FileIOException(in, e)
1707                  : new FileIOException(in, filename, e);
1708      }
1709    }
1710
1711    /**
1712     * Parse the given text into a {@link Type}.
1713     * @param text the text to parse
1714     * @return the type
1715     */
1716    public static Type parseType(String text) {
1717        StringReader in = new StringReader(text);
1718        IndexFileParser parser = new IndexFileParser(in, null);
1719        try {
1720            parser.st.nextToken();
1721            return parser.parseType();
1722        } catch (Exception e) {
1723            throw new RuntimeException("Error parsing type from: '" + text + "'", e);
1724        }
1725    }
1726}
1727