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