/* * 03/21/2010 * * Copyright (C) 2010 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is distributed under a modified BSD license. See the included * RSTALanguageSupport.License.txt file for details. */ package org.fife.rsta.ac.java.rjc.parser; import java.io.EOFException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.fife.rsta.ac.java.rjc.ast.AbstractTypeDeclarationNode; import org.fife.rsta.ac.java.rjc.ast.CodeBlock; import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; import org.fife.rsta.ac.java.rjc.ast.EnumBody; import org.fife.rsta.ac.java.rjc.ast.EnumDeclaration; import org.fife.rsta.ac.java.rjc.ast.Field; import org.fife.rsta.ac.java.rjc.ast.FormalParameter; import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; import org.fife.rsta.ac.java.rjc.ast.LocalVariable; import org.fife.rsta.ac.java.rjc.ast.Method; import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration; import org.fife.rsta.ac.java.rjc.ast.NormalInterfaceDeclaration; import org.fife.rsta.ac.java.rjc.ast.Package; import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; import org.fife.rsta.ac.java.rjc.ast.TypeDeclarationContainer; import org.fife.rsta.ac.java.rjc.lang.Annotation; import org.fife.rsta.ac.java.rjc.lang.Modifiers; import org.fife.rsta.ac.java.rjc.lang.Type; import org.fife.rsta.ac.java.rjc.lang.TypeArgument; import org.fife.rsta.ac.java.rjc.lang.TypeParameter; import org.fife.rsta.ac.java.rjc.lexer.*; import org.fife.rsta.ac.java.rjc.notices.ParserNotice; /** * Generates an abstract syntax tree for a Java source file. * * @author Robert Futrell * @version 1.0 */ public class ASTFactory implements TokenTypes { private static final boolean DEBUG = false; /** * Whether the next member (or class, interface or enum) is deprecated. */ private boolean nextMemberDeprecated; public ASTFactory() { } private boolean checkDeprecated() { boolean deprecated = nextMemberDeprecated; nextMemberDeprecated = false; return deprecated; } /** * Checks whether a local variable's name collides with a local variable * defined earlier. Note that this method assumes that it is called * immediately whenever a variable is parsed, thus any other variables * declared in a code block were declared before the one being checked. * * @param cu The compilation unit. * @param lVar The just-scanned local variable. * @param block The code block the variable is in. * @param m The method the (possibly nested) code block <code>block</code> * is in, or <code>null</code> for none. */ private void checkForDuplicateLocalVarNames(CompilationUnit cu, Token lVar, CodeBlock block, Method m) { String name = lVar.getLexeme(); boolean found = false; // See if a local variable defined previously in this block has the // same name. for (int i=0; i<block.getLocalVarCount(); i++) { LocalVariable otherLocal = block.getLocalVar(i); if (name.equals(otherLocal.getName())) { cu.addParserNotice(lVar, "Duplicate local variable: " + name); found = true; break; } } // If not... if (!found) { // If this was a nested code block, check previously-defined // variables in the parent block. if (block.getParent()!=null) { checkForDuplicateLocalVarNames(cu, lVar, block.getParent(), m); } // If this was the highest-level code block, if we're in the body // of a method, check the method's parameters. else if (m!=null) { for (int i=0; i<m.getParameterCount(); i++) { FormalParameter param = m.getParameter(i); if (name.equals(param.getName())) { cu.addParserNotice(lVar, "Duplicate local variable: " + name); break; } } } } } /** * Assumes <tt>t</tt> is the actual '<tt>@foobar</tt>' annotation token. * * @param cu * @param s * @return * @throws IOException */ private Annotation _getAnnotation(CompilationUnit cu, Scanner s) throws IOException { s.yylexNonNull(ANNOTATION_START, "Annotation expected"); Type type = _getType(cu, s); if ("Deprecated".equals(type.toString())) { nextMemberDeprecated = true; } if (s.yyPeekCheckType()==SEPARATOR_LPAREN) { s.yylex(); // TODO: Read rest of Annotation stuff s.eatThroughNextSkippingBlocks(SEPARATOR_RPAREN); } Annotation a = new Annotation(type); return a; } private CodeBlock _getBlock(CompilationUnit cu, CodeBlock parent, Method m, Scanner s, boolean isStatic) throws IOException { return _getBlock(cu, parent, m, s, isStatic, 1); } /** * Parses a block of code. This should not be called. * * @param parent The parent code block, or <code>null</code> if none (i.e. * this is the body of a method, a static initializer block, etc.). * @param m The method containing this block, or <code>null</code> if this * block is not part of a method. * @param s The scanner. * @param isStatic Whether this is a static code block. * @param depth The nested depth of this code block. */ private CodeBlock _getBlock(CompilationUnit cu, CodeBlock parent, Method m, Scanner s, boolean isStatic, int depth) throws IOException { log("Entering _getBlock() (" + depth + ")"); // TODO: Implement me to get variable declarations. Token t = s.yylexNonNull(SEPARATOR_LBRACE, "'{' expected"); CodeBlock block = new CodeBlock(isStatic, s.createOffset(t.getOffset())); block.setParent(parent); boolean atStatementStart =true; OUTER: while (true) { // Don't bail if they have unmatched parens (for example), just // return the current status of the block. //t = s.yylexNonNull("Unexpected end of input"); if ((t = s.yylex())==null) { log("Exiting _getBlock() - eos (" + depth + ")"); block.setDeclarationEndOffset(s.createOffset(s.getOffset())); return block; } int type = t.getType(); boolean isFinal = false; switch (type) { case SEPARATOR_LBRACE: s.yyPushback(t); CodeBlock child = _getBlock(cu, block, m, s, isStatic, depth+1); block.add(child); atStatementStart = true; break; case SEPARATOR_RBRACE: block.setDeclarationEndOffset(s.createOffset(t.getOffset())); break OUTER; case KEYWORD_TRY: t = s.yyPeekNonNull(SEPARATOR_LBRACE, SEPARATOR_LPAREN, "'{' or '(' expected"); if (t.getType()==SEPARATOR_LPAREN) { // Auto-closeable stuff // TODO: Get block-scoped var(s) s.eatParenPairs(); } s.yyPeekNonNull(SEPARATOR_LBRACE, "'{' expected"); CodeBlock tryBlock = _getBlock(cu, block, m, s, isStatic, depth+1); block.add(tryBlock); while (s.yyPeekCheckType()==KEYWORD_CATCH && s.yyPeekCheckType(2)==SEPARATOR_LPAREN) { s.yylex(); // catch s.yylex(); // lparen Type exType = null; Token var = null; boolean multiCatch = false; do { isFinal = false; Token temp = s.yyPeekNonNull(IDENTIFIER, KEYWORD_FINAL, "Throwable type expected"); if (temp.isType(KEYWORD_FINAL)) { isFinal = true; s.yylex(); } s.yyPeekNonNull(IDENTIFIER, "Variable declarator expected"); exType = _getType(cu, s); // Not good for multi-catch! var = s.yylexNonNull(IDENTIFIER, OPERATOR_BITWISE_OR, "Variable declarator expected"); multiCatch |= var.isType(OPERATOR_BITWISE_OR); } while (var.isType(OPERATOR_BITWISE_OR)); s.yylexNonNull(SEPARATOR_RPAREN, "')' expected"); s.yyPeekNonNull(SEPARATOR_LBRACE, "'{' expected"); CodeBlock catchBlock = _getBlock(cu, block, m, s, false, depth); int offs = var.getOffset(); // Not actually in block! if (multiCatch) { // TODO: With Java 7's multi-catch, calculate // least upper bound for exception type: // http://cr.openjdk.java.net/~darcy/ProjectCoin/ProjectCoin-Documentation-v0.83.html#multi_catch exType = new Type("java"); exType.addIdentifier("lang", null); exType.addIdentifier("Throwable", null); } LocalVariable localVar = new LocalVariable(s, isFinal, exType, offs, var.getLexeme()); checkForDuplicateLocalVarNames(cu, var, block, m); catchBlock.addLocalVariable(localVar); block.add(catchBlock); } break; case KEYWORD_FOR: // TODO: Get local var (e.g. "int i", "Iterator i", etc.) // Fall through case KEYWORD_WHILE: int nextType = s.yyPeekCheckType(); while (nextType!=-1 && nextType!=SEPARATOR_LPAREN) { t = s.yylex(); // Grab the (unexpected) token if (t!=null) { // Should always be true ParserNotice pn = new ParserNotice(t, "Unexpected token"); cu.addParserNotice(pn); } nextType = s.yyPeekCheckType(); } if (nextType==SEPARATOR_LPAREN) { s.eatParenPairs(); } nextType = s.yyPeekCheckType(); if (nextType==SEPARATOR_LBRACE) { child = _getBlock(cu, block, m, s, isStatic, depth+1); block.add(child); atStatementStart = true; } break; // NOTE: The code below is supposed to try to parse code blocks and identify // variable declarations. This does work somewhat, but the problem is that // our parsing of type parameters isn't good enough, and lines like: // for (int i=0; i<foo; i++) { // get incorrectly parsed as a type "i<foo>" (even though the closing '>' isn't // there, but that's not the big problem really!). We should be able to check // whether our type parameters are well-formed, and if they aren't, then assume // push all tokens back, and assume that the '<' was a less-than operator. // It's the "push all tokens back" that's tough for us at the moment, as we've // lost most of the tokens (there may be an arbitrary number that have been // parsed and discarded). case KEYWORD_FINAL: isFinal = true; t = s.yylexNonNull("Unexpected end of file"); // Fall through default: if (t.isType(SEPARATOR_SEMICOLON)) { atStatementStart = true; break; } else if (atStatementStart && (t.isBasicType() || (t.isIdentifier()))) { s.yyPushback(t); // TODO: This is very inefficient Type varType = null; try { varType = _getType(cu, s, true); } catch (IOException ioe) { // Not a var declaration s.eatUntilNext(SEPARATOR_SEMICOLON, SEPARATOR_LBRACE, SEPARATOR_RBRACE); // Only needed if ended on ';' or '}', but... atStatementStart = true; break; } if (s.yyPeekCheckType()==IDENTIFIER) { while ((t=s.yylexNonNull(IDENTIFIER, "Variable name expected (type==" + varType.toString() + ")"))!=null) { int arrayDepth = s.skipBracketPairs(); varType.incrementBracketPairCount(arrayDepth); String varDec = varType.toString() + " " + t.getLexeme(); log(">>> Variable -- " + varDec + " (line " + t.getLine() + ")"); int offs = t.getOffset(); String name = t.getLexeme(); LocalVariable lVar = new LocalVariable(s, isFinal, varType, offs, name); checkForDuplicateLocalVarNames(cu, t, block, m); block.addLocalVariable(lVar); nextType = s.yyPeekCheckType(); // A "valid" nextType would be '=', ',' or ';'. // If it's an '=', skip past the assignment. if (nextType==OPERATOR_EQUALS) { Token temp = s.eatThroughNextSkippingBlocksAndStuffInParens(SEPARATOR_COMMA, SEPARATOR_SEMICOLON); if (temp!=null) { s.yyPushback(temp); } nextType = s.yyPeekCheckType(); } // If next is a comma, loop to read the next local // var. Otherwise, whether or not it's valid, // eat until the end of the statement. if (nextType!=SEPARATOR_COMMA) { s.eatThroughNextSkippingBlocks(SEPARATOR_SEMICOLON); break; } s.yylex(); // Eat the comma (does nothing if EOS) } } } else { atStatementStart = false; } break; } } log("Exiting _getBlock() (" + depth + ")"); return block; } private void _getClassBody(CompilationUnit cu, Scanner s, NormalClassDeclaration classDec) throws IOException { log("Entering _getClassBody"); Token t = s.yylexNonNull(SEPARATOR_LBRACE, "'{' expected"); classDec.setBodyStartOffset(s.createOffset(t.getOffset())); t = s.yylexNonNull("ClassBody expected"); while (t.getType() != SEPARATOR_RBRACE) { switch (t.getType()) { case SEPARATOR_SEMICOLON: break; // Do nothing case KEYWORD_STATIC: Token t2 = s.yyPeekNonNull("'{' or modifier expected"); if (t2.isType(SEPARATOR_LBRACE)) { CodeBlock block = _getBlock(cu, null, null, s, true); classDec.addMember(block); break; } else { // Not "static {" => must be a member. s.yyPushback(t); // Put back "static" Modifiers modList = _getModifierList(cu, s); _getMemberDecl(cu, s, classDec, modList); } break; case SEPARATOR_LBRACE: s.yyPushback(t); CodeBlock block = _getBlock(cu, null, null, s, false); classDec.addMember(block); break; default: s.yyPushback(t); Modifiers modList = _getModifierList(cu, s); _getMemberDecl(cu, s, classDec, modList); break; } try { t = s.yylexNonNull("'}' expected (one)"); classDec.setBodyEndOffset(s.createOffset(t.getOffset())); } catch (IOException ioe) { classDec.setBodyEndOffset(s.createOffset(s.getOffset())); int line = s.getLine(); int col = s.getColumn(); ParserNotice pn = new ParserNotice(line, col, 1, "'}' expected (two)"); cu.addParserNotice(pn); break; // No more content in file } } log("Exiting _getClassBody"); } private TypeDeclaration _getClassOrInterfaceDeclaration(CompilationUnit cu, Scanner s, TypeDeclarationContainer addTo, Modifiers modList) throws IOException { log("Entering _getClassOrInterfaceDeclaration"); Token t = s.yyPeekNonNull( "class, enum, interface or @interface expected"); if (modList==null) { // Not yet read in modList = _getModifierList(cu, s); } t = s.yylexNonNull("class, enum, interface or @interface expected"); TypeDeclaration td = null; switch (t.getType()) { case KEYWORD_CLASS: td = _getNormalClassDeclaration(cu, s, addTo); break; case KEYWORD_ENUM: td = _getEnumDeclaration(cu, s, addTo); break; case KEYWORD_INTERFACE: td = _getNormalInterfaceDeclaration(cu, s, addTo); break; case ANNOTATION_START: // TODO: AnnotationTypeDeclaration, implement me. throw new IOException( "AnnotationTypeDeclaration not implemented"); default: ParserNotice notice = new ParserNotice(t, "class, interface or enum expected"); cu.addParserNotice(notice); //return td; // Assume we're a class to get more problems. td = _getNormalClassDeclaration(cu, s, addTo); break; } ((AbstractTypeDeclarationNode) td).setModifiers(modList); ((AbstractTypeDeclarationNode)td).setDeprecated(checkDeprecated()); log("Exiting _getClassOrInterfaceDeclaration"); return td; } /** * Reads tokens for a Java source file from the specified lexer and returns * the structure of the source as an AST. * * @param scanner The scanner to read from. * @return The root node of the AST. */ public CompilationUnit getCompilationUnit(String name, Scanner scanner) throws IOException { CompilationUnit cu = new CompilationUnit(name); try { // Get annotations. List initialAnnotations = null; // Usually none while (scanner.yyPeekCheckType()==ANNOTATION_START) { if (initialAnnotations==null) { initialAnnotations = new ArrayList(1); } initialAnnotations.add(_getAnnotation(cu, scanner)); } // Get possible "package" line. Token t = scanner.yylex(); if (t==null) { return cu; } if (t.isType(KEYWORD_PACKAGE)) { t = scanner.yyPeekNonNull("Identifier expected"); int offs = t.getOffset(); String qualifiedID = getQualifiedIdentifier(scanner); Package pkg = new Package(scanner, offs, qualifiedID); if (initialAnnotations!=null) { // pkg.setAnnotations(initialAnnotations); initialAnnotations = null; } cu.setPackage(pkg); scanner.yylexNonNull(SEPARATOR_SEMICOLON, "Semicolon expected"); t = scanner.yylex(); } // Go through any import statements. OUTER: while (t!=null && t.isType(KEYWORD_IMPORT)) { boolean isStatic = false; StringBuffer buf = new StringBuffer(); t = scanner.yylexNonNull("Incomplete import statement"); Token temp = null; int offs = 0; if (t.isType(KEYWORD_STATIC)) { isStatic = true; t = scanner.yylexNonNull("Incomplete import statement"); } if (!t.isIdentifier()) { cu.addParserNotice(t, "Expected identifier, found: \"" + t.getLexeme() + "\""); scanner.eatThroughNextSkippingBlocks(SEPARATOR_SEMICOLON); // We expect "t" to be the semicolon below t = scanner.getMostRecentToken(); } else { offs = t.getOffset(); buf.append(t.getLexeme()); temp = scanner.yylexNonNull(SEPARATOR_DOT, SEPARATOR_SEMICOLON, "'.' or ';' expected"); while (temp.isType(SEPARATOR_DOT)) { temp = scanner.yylexNonNull(IDENTIFIER, OPERATOR_TIMES, "Identifier or '*' expected"); if (temp.isIdentifier()) { buf.append('.').append(temp.getLexeme()); } else {//if (temp.getLexeme().equals("*")) { buf.append(".*"); temp = scanner.yylex(); // We're bailing, so scan here break; } temp = scanner.yylexNonNull(KEYWORD_IMPORT, SEPARATOR_DOT, SEPARATOR_SEMICOLON, "'.' or ';' expected"); if (temp.isType(KEYWORD_IMPORT)) { cu.addParserNotice(temp, "';' expected"); t = temp; continue OUTER; } } t = temp; } if (temp==null || !t.isType(SEPARATOR_SEMICOLON)) { throw new IOException("Semicolon expected, found " + t); } ImportDeclaration id = new ImportDeclaration(scanner, offs, buf.toString(), isStatic); cu.addImportDeclaration(id); t = scanner.yylex(); } // class files aren't required to have TypeDeclarations. if (t==null) { return cu; } scanner.yyPushback(t); //TypeDeclaration td = null; while ((/*td = */_getTypeDeclaration(cu, scanner)) != null) { if (initialAnnotations!=null) { // td.addAnnotations(initialAnnotations); initialAnnotations = null; } // cu.addTypeDeclaration(td); // Done when the type declarations are created. } } catch (IOException ioe) { if (!(ioe instanceof EOFException)) { // Not just "end of file" ioe.printStackTrace(); } ParserNotice notice = null; Token lastTokenLexed = scanner.getMostRecentToken(); if (lastTokenLexed==null) { notice = new ParserNotice(0,0,5, ioe.getMessage()); } else { notice = new ParserNotice(lastTokenLexed, ioe.getMessage()); } cu.addParserNotice(notice); //throw ioe; // Un-comment me to get the AnnotationTypeDeclaration error count in "Main" test } return cu; } private EnumBody _getEnumBody(CompilationUnit cu, Scanner s, EnumDeclaration enumDec) throws IOException { // TODO: Implement me CodeBlock block = _getBlock(cu, null, null, s, false); enumDec.setBodyEndOffset(s.createOffset(block.getNameEndOffset())); return null; } private EnumDeclaration _getEnumDeclaration(CompilationUnit cu, Scanner s, TypeDeclarationContainer addTo) throws IOException { Token t = s.yylexNonNull(IDENTIFIER, "Identifier expected"); String enumName = t.getLexeme(); EnumDeclaration enumDec = new EnumDeclaration(s,t.getOffset(),enumName); enumDec.setPackage(cu.getPackage()); addTo.addTypeDeclaration(enumDec); t = s.yylexNonNull("implements or '{' expected"); if (t.isType(KEYWORD_IMPLEMENTS)) { List implemented = new ArrayList(1); // Usually small do { implemented.add(_getType(cu, s)); t = s.yylex(); } while (t != null && t.isType(SEPARATOR_COMMA)); // enumDesc.setImplementedInterfaces(implemented); if (t != null) { s.yyPushback(t); } } else if (t.isType(SEPARATOR_LBRACE)) { s.yyPushback(t); } _getEnumBody(cu, s, enumDec); //EnumBody enumBody = _getEnumBody(cu, s); //enumDec.setEnumBody(enumBody); return enumDec; } private List _getFormalParameters(CompilationUnit cu, List tokenList) throws IOException { List list = new ArrayList(0); Scanner s = new Scanner(tokenList); Token t = s.yylex(); if (t==null) { // No parameters return list; } while (true) { boolean isFinal = false; if (t.isType(KEYWORD_FINAL)) { isFinal = true; t = s.yylexNonNull("Type expected"); } List annotations = null; // TODO: Annotations s.yyPushback(t); Type type = _getType(cu, s); Token temp = s.yylexNonNull("Argument name expected"); boolean elipsis = false; if (temp.isType(ELIPSIS)) { elipsis = true; temp = s.yylexNonNull(IDENTIFIER, "Argument name expected"); } type.incrementBracketPairCount(s.skipBracketPairs()); int offs = temp.getOffset(); String name = temp.getLexeme(); FormalParameter param = new FormalParameter(s, isFinal, type, offs, name, annotations); list.add(param); if (elipsis) { break; // Must be last parameter } t = s.yylex(); if (t==null) { break; } else if (t.getType()!=SEPARATOR_COMMA) { throw new IOException("Comma expected"); } t = s.yylexNonNull("Parameter or ')' expected"); } return list; } private void _getInterfaceBody(CompilationUnit cu, Scanner s, NormalInterfaceDeclaration iDec) throws IOException { log("Entering _getInterfaceBody"); Token t = s.yylexNonNull(SEPARATOR_LBRACE, "'{' expected"); iDec.setBodyStartOffset(s.createOffset(t.getOffset())); t = s.yylexNonNull("InterfaceBody expected"); while (t.getType() != SEPARATOR_RBRACE) { switch (t.getType()) { case SEPARATOR_SEMICOLON: break; // Do nothing case SEPARATOR_LBRACE: s.yyPushback(t); // TODO: What is this? _getBlock(cu, null, null, s, false); break; default: s.yyPushback(t); Modifiers modList = _getModifierList(cu, s); _getInterfaceMemberDecl(cu, s, iDec, modList); break; } try { t = s.yylexNonNull("'}' expected (one)"); iDec.setBodyEndOffset(s.createOffset(t.getOffset())); } catch (IOException ioe) { iDec.setBodyEndOffset(s.createOffset(s.getOffset())); int line = s.getLine(); int col = s.getColumn(); ParserNotice pn = new ParserNotice(line, col, 1, "'}' expected (two)"); cu.addParserNotice(pn); } } log("Exiting _getInterfaceBody"); } /* * InterfaceMemberDecl: * InterfaceMethodOrFieldDecl * InterfaceGenericMethodDecl * void Identifier VoidInterfaceMethodDeclaratorRest * InterfaceDeclaration * ClassDeclaration */ private void _getInterfaceMemberDecl(CompilationUnit cu, Scanner s, NormalInterfaceDeclaration iDec, Modifiers modList) throws IOException { log("Entering _getInterfaceMemberDecl"); List tokenList = new ArrayList(1); List methodNameAndTypeTokenList = null; List methodParamsList = null; int bracketPairCount; boolean methodDecl = false; boolean blockDecl = false; boolean varDecl = false; Token t; OUTER: while (true) { t = s.yylexNonNull("Unexpected end of input"); switch (t.getType()) { case SEPARATOR_LPAREN: methodNameAndTypeTokenList = tokenList; methodParamsList = new ArrayList(1); methodDecl = true; break OUTER; case SEPARATOR_LBRACE: blockDecl = true; break OUTER; case OPERATOR_EQUALS: varDecl = true; // can be "= 4;", "= new foo();" or even "= new foo() { ... };". s.eatThroughNextSkippingBlocks(SEPARATOR_SEMICOLON); break OUTER; case SEPARATOR_SEMICOLON: varDecl = true; break OUTER; default: tokenList.add(t); break; } } if (varDecl) { log("*** Variable declaration:"); Scanner tempScanner = new Scanner(tokenList); Type type = _getType(cu, tempScanner); Token fieldNameToken = tempScanner.yylexNonNull(IDENTIFIER, "Identifier (field name) expected"); bracketPairCount = tempScanner.skipBracketPairs(); type.incrementBracketPairCount(bracketPairCount); Field field = new Field(s, modList, type, fieldNameToken); field.setDeprecated(checkDeprecated()); field.setDocComment(s.getLastDocComment()); log(field.toString()); iDec.addMember(field); // TODO: Parse and grab the "value" after the '=' sign. } else if (methodDecl) { log("*** Method declaration:"); Scanner tempScanner = new Scanner(methodNameAndTypeTokenList); Type type = null; if (methodNameAndTypeTokenList.size()>1) { // InterfaceMethodOrFieldDecl or InterfaceGenericMethodDecl if (tempScanner.yyPeekCheckType()==OPERATOR_LT) { // InterfaceGenericMethodDecl _getTypeParameters(cu, tempScanner); type = _getType(cu, tempScanner); } else { // InterfaceMethodOrFieldDecl (really just an InterfaceMethod) type = _getType(cu, tempScanner); } } Token methodNameToken = tempScanner.yylexNonNull(IDENTIFIER, "Identifier (method name) expected"); while (true) { t = s.yylexNonNull("Unexpected end of input"); if (t.isType(SEPARATOR_RPAREN)) { break; } methodParamsList.add(t); } List formalParams = _getFormalParameters(cu, methodParamsList); if (s.yyPeekCheckType()==SEPARATOR_LBRACKET) { if (type==null) { throw new IOException("Constructors cannot return array types"); } type.incrementBracketPairCount(s.skipBracketPairs()); } List thrownTypeNames = getThrownTypeNames(cu, s); t = s.yylexNonNull("'{' or ';' expected"); if (t.getType() != SEPARATOR_SEMICOLON) { throw new IOException("';' expected"); } Method m = new Method(s, modList, type, methodNameToken, formalParams, thrownTypeNames); m.setDeprecated(checkDeprecated()); m.setDocComment(s.getLastDocComment()); iDec.addMember(m); } else if (blockDecl) { // s.yyPushback(t); // _getBlock(cu, s, false); // // TODO: Add the member for a block... // Could be a code block (static or not), or an inner class, enum, // or interface... if (tokenList.size()<2) { for (int i=tokenList.size()-1; i>=0; i--) { s.yyPushback((Token)tokenList.get(i)); } CodeBlock block = _getBlock(cu, null, null, s, false); iDec.addMember(block); } else { // inner class, enum, or interface (?) s.yyPushback(t); // The '{' token for (int i=tokenList.size()-1; i>=0; i--) { s.yyPushback((Token)tokenList.get(i)); } /*TypeDeclaration type = */_getClassOrInterfaceDeclaration(cu, s, iDec, modList); } } log("Exiting _getInterfaceMemberDecl"); } /* * MemberDecl: * GenericMethodOrConstructorDecl * MethodOrFieldDecl * void Identifier VoidMethodDeclaratorRest * Identifier ConstructorDeclaratorRest * InterfaceDeclaration * ClassDeclaration */ private void _getMemberDecl(CompilationUnit cu, Scanner s, NormalClassDeclaration classDec, Modifiers modList) throws IOException { log("Entering _getMemberDecl"); List tokenList = new ArrayList(1); List methodNameAndTypeTokenList = null; List methodParamsList = null; int bracketPairCount; boolean methodDecl = false; boolean blockDecl = false; boolean varDecl = false; Token t; OUTER: while (true) { t = s.yylexNonNull("Unexpected end of input"); switch (t.getType()) { case SEPARATOR_LPAREN: methodNameAndTypeTokenList = tokenList; methodParamsList = new ArrayList(1); methodDecl = true; break OUTER; case SEPARATOR_LBRACE: blockDecl = true; break OUTER; case OPERATOR_EQUALS: varDecl = true; // can be "= 4;", "= new foo();" or even "= new foo() { ... };". s.eatThroughNextSkippingBlocks(SEPARATOR_SEMICOLON); break OUTER; case SEPARATOR_SEMICOLON: varDecl = true; break OUTER; default: tokenList.add(t); break; } } if (varDecl) { log("*** Variable declaration:"); Scanner tempScanner = new Scanner(tokenList); Type type = _getType(cu, tempScanner); Token fieldNameToken = tempScanner.yylexNonNull(IDENTIFIER, "Identifier (field name) expected"); bracketPairCount = tempScanner.skipBracketPairs(); type.incrementBracketPairCount(bracketPairCount); Field field = new Field(s, modList, type, fieldNameToken); field.setDeprecated(checkDeprecated()); field.setDocComment(s.getLastDocComment()); log(field.toString()); classDec.addMember(field); } else if (methodDecl) { log("*** Method declaration:"); CodeBlock block = null; // Method body Scanner tempScanner = new Scanner(methodNameAndTypeTokenList); Type type = null; if (methodNameAndTypeTokenList.size()>1) { // GenericMethodOrConstructorDecl, or method (not a regular constructor). if (tempScanner.yyPeekCheckType()==OPERATOR_LT) { // GenericMethodOrConstructorDecl _getTypeParameters(cu, tempScanner); if (tempScanner.yyPeekCheckType(2)==-1) { // GenericConstructor // Do nothing; we're good to keep going } else { // A generic method declaration. type = _getType(cu, tempScanner); } } else { type = _getType(cu, tempScanner); // Method (not a constructor) } } Token methodNameToken = tempScanner.yylexNonNull(IDENTIFIER, "Identifier (method name) expected"); while (true) { t = s.yylexNonNull("Unexpected end of input"); if (t.isType(SEPARATOR_RPAREN)) { break; } methodParamsList.add(t); } List formalParams = _getFormalParameters(cu, methodParamsList); if (s.yyPeekCheckType()==SEPARATOR_LBRACKET) { if (type==null) { throw new IOException("Constructors cannot return array types"); } type.incrementBracketPairCount(s.skipBracketPairs()); } List thrownTypeNames = getThrownTypeNames(cu, s); Method m = new Method(s, modList, type, methodNameToken, formalParams, thrownTypeNames); m.setDeprecated(checkDeprecated()); m.setDocComment(s.getLastDocComment()); classDec.addMember(m); t = s.yylexNonNull("'{' or ';' expected"); if (t.isType(SEPARATOR_SEMICOLON)) { // Just a method declaration (such as in an interface) } else if (t.isType(SEPARATOR_LBRACE)) { s.yyPushback(t); block = _getBlock(cu, null, m, s, false); } else { throw new IOException("'{' or ';' expected"); } m.setBody(block); } else if (blockDecl) { // Could be a code block (static or not), or an inner class, enum, // or interface... nextMemberDeprecated = false; if (tokenList.size()<2) { for (int i=tokenList.size()-1; i>=0; i--) { s.yyPushback((Token)tokenList.get(i)); } CodeBlock block = _getBlock(cu, null, null, s, false); classDec.addMember(block); } else { // inner class, enum, or interface (?) s.yyPushback(t); // The '{' token for (int i=tokenList.size()-1; i>=0; i--) { s.yyPushback((Token)tokenList.get(i)); } /*TypeDeclaration type = */_getClassOrInterfaceDeclaration(cu, s, classDec, modList); } } log("Exiting _getMemberDecl (next== " + s.yyPeek() + ")"); } private Modifiers _getModifierList(CompilationUnit cu, Scanner s) throws IOException { Modifiers modList = null; Token t = s.yylexNonNull("Unexpected end of input"); while (true) { int modifier = isModifier(t); if (modifier != -1) { if (modList==null) { modList = new Modifiers(); } if (!modList.addModifier(modifier)) { cu.addParserNotice(t, "Duplicate modifier"); } } else if (t.isType(ANNOTATION_START)) { Token next = s.yyPeekNonNull("Annotation expected"); s.yyPushback(t); // Put '@' back // TODO: Handle at a higher level (even at Scanner?) if (next.isType(KEYWORD_INTERFACE)) { return modList; } if (modList==null) { modList = new Modifiers(); } modList.addAnnotation(_getAnnotation(cu, s)); } else { s.yyPushback(t); return modList; } t = s.yylexNonNull("Unexpected end of input"); } } private NormalClassDeclaration _getNormalClassDeclaration( CompilationUnit cu, Scanner s, TypeDeclarationContainer addTo) throws IOException { log("Entering _getNormalClassDeclaration"); String className = null; Token t = s.yylexNonNull("Identifier expected"); if (t.isType(IDENTIFIER)) { className = t.getLexeme(); } else { className = "Unknown"; cu.addParserNotice(new ParserNotice(t, "Class name expected")); s.eatUntilNext(KEYWORD_EXTENDS, KEYWORD_IMPLEMENTS, SEPARATOR_LBRACE); } NormalClassDeclaration classDec = new NormalClassDeclaration(s, t.getOffset(), className); classDec.setPackage(cu.getPackage()); addTo.addTypeDeclaration(classDec); t = s.yylexNonNull("TypeParameters, extends, implements or '{' expected"); if (t.isType(OPERATOR_LT)) { s.yyPushback(t); List typeParams = _getTypeParameters(cu, s); classDec.setTypeParameters(typeParams); t = s.yylexNonNull("extends, implements or '{' expected"); } if (t.isType(KEYWORD_EXTENDS)) { classDec.setExtendedType(_getType(cu, s)); t = s.yylexNonNull("implements or '{' expected"); } if (t.isType(KEYWORD_IMPLEMENTS)) { do { classDec.addImplemented(_getType(cu, s)); t = s.yylex(); } while (t != null && t.isType(SEPARATOR_COMMA)); if (t != null) { s.yyPushback(t); } } else if (t.isType(SEPARATOR_LBRACE)) { s.yyPushback(t); } _getClassBody(cu, s, classDec); log("Exiting _getNormalClassDeclaration"); return classDec; } private NormalInterfaceDeclaration _getNormalInterfaceDeclaration( CompilationUnit cu, Scanner s, TypeDeclarationContainer addTo) throws IOException { String iName = null; Token t = s.yylexNonNull("Identifier expected"); if (t.isType(IDENTIFIER)) { iName = t.getLexeme(); } else { iName = "Unknown"; cu.addParserNotice(new ParserNotice(t, "Interface name expected")); s.eatUntilNext(KEYWORD_EXTENDS, SEPARATOR_LBRACE); } NormalInterfaceDeclaration iDec = new NormalInterfaceDeclaration( s, t.getOffset(), iName); iDec.setPackage(cu.getPackage()); addTo.addTypeDeclaration(iDec); t = s.yylexNonNull("TypeParameters, extends or '{' expected"); if (t.isType(OPERATOR_LT)) { s.yyPushback(t); _getTypeParameters(cu, s); t = s.yylexNonNull("Interface body expected"); } if (t.isType(KEYWORD_EXTENDS)) { do { iDec.addExtended(_getType(cu, s)); t = s.yylex(); } while (t != null && t.isType(SEPARATOR_COMMA)); if (t != null) { s.yyPushback(t); } } else if (t.isType(SEPARATOR_LBRACE)) { s.yyPushback(t); } _getInterfaceBody(cu, s, iDec); return iDec; } private String getQualifiedIdentifier(Scanner scanner) throws IOException { Token t = null; StringBuffer sb = new StringBuffer(); while ((t = scanner.yylex()).isIdentifier()) { sb.append(t.getLexeme()); t = scanner.yylex(); if (t.isType(SEPARATOR_DOT)) { sb.append('.'); } else { break; } } // QualifiedIdentifier has ended. scanner.yyPushback(t); return sb.toString(); } private List getThrownTypeNames(CompilationUnit cu, Scanner s) throws IOException { if (s.yyPeekCheckType()!=KEYWORD_THROWS) { return null; } s.yylex(); List list = new ArrayList(1); // Usually small list.add(getQualifiedIdentifier(s)); while (s.yyPeekCheckType()==SEPARATOR_COMMA) { s.yylex(); list.add(getQualifiedIdentifier(s)); } return list; } // For "backwards compatibility," don't know if "false" is usually // correct or not private Type _getType(CompilationUnit cu, Scanner s) throws IOException { return _getType(cu, s, false); } private Type _getType(CompilationUnit cu, Scanner s, boolean pushbackOnUnexpected) throws IOException { log("Entering _getType()"); Type type = new Type(); Token t = s.yylexNonNull("Type expected"); // TODO: "void" checking is NOT in the JLS for type! Remove me if (t.isType(KEYWORD_VOID)) { type.addIdentifier(t.getLexeme(), null); log("Exiting _getType(): " + type.toString()); return type; } else if (t.isBasicType()) { int arrayDepth = s.skipBracketPairs(); type.addIdentifier(t.getLexeme(), null); type.setBracketPairCount(arrayDepth); log("Exiting _getType(): " + type.toString()); return type; } OUTER: while (true) { switch (t.getType()) { case IDENTIFIER: List typeArgs = null; if (s.yyPeekCheckType()==OPERATOR_LT) { typeArgs = _getTypeArguments(cu, s); } type.addIdentifier(t.getLexeme(), typeArgs); t = s.yylexNonNull("Unexpected end of input"); if (t.isType(SEPARATOR_DOT)) { t = s.yylexNonNull("Unexpected end of input"); continue; } else if (t.isType(SEPARATOR_LBRACKET)) { s.yyPushback(t); type.setBracketPairCount(s.skipBracketPairs()); break OUTER; } else { s.yyPushback(t); break OUTER; } default: if (pushbackOnUnexpected) { s.yyPushback(t); } throw new IOException("Expected identifier, found: " + t); } } log("Exiting _getType(): " + type.toString()); return type; } private TypeArgument _getTypeArgument(CompilationUnit cu, Scanner s) throws IOException { log("Entering _getTypeArgument()"); TypeArgument typeArg = null; Token t = s.yyPeekNonNull("Type or '?' expected"); if (t.isType(OPERATOR_QUESTION)) { s.yylex(); // Pop the '?' off the stream. t = s.yyPeek(); if (t.getType()!=OPERATOR_GT) { t = s.yylexNonNull(SEPARATOR_COMMA, KEYWORD_EXTENDS, KEYWORD_SUPER, "',', super or extends expected"); switch (t.getType()) { case SEPARATOR_COMMA: typeArg = new TypeArgument(null, 0, null); s.yyPushback(t); break; case KEYWORD_EXTENDS: Type otherType = _getType(cu, s); typeArg = new TypeArgument(null, TypeArgument.EXTENDS, otherType); break; default: // KEYWORD_SUPER: otherType = _getType(cu, s); typeArg = new TypeArgument(null, TypeArgument.SUPER, otherType); break; } } else { typeArg = new TypeArgument(null, 0, null); } } else { Type type = _getType(cu, s); typeArg = new TypeArgument(type); } log("Exiting _getTypeArgument() : " + typeArg); return typeArg; } private List _getTypeArguments(CompilationUnit cu, Scanner s) throws IOException { s.increaseTypeArgumentsLevel(); log("Entering _getTypeArguments() (" + s.getTypeArgumentsLevel() + ")"); s.markResetPosition(); s.yylexNonNull(OPERATOR_LT, "'<' expected"); List typeArgs = new ArrayList(1); Token t = null; do { typeArgs.add(_getTypeArgument(cu, s)); t = s.yylexNonNull("',' or '>' expected"); if (t.getType()!=SEPARATOR_COMMA && t.getType()!=OPERATOR_GT) { // Assume we're in a code block, and are simply at the (much // more common) case of e.g. "if (i < 7) ...". s.resetToLastMarkedPosition(); log("Exiting _getTypeArguments() (" + s.getTypeArgumentsLevel() + ") - NOT TYPE ARGUMENTS (" + t.getLexeme() + ")"); s.decreaseTypeArgumentsLevel(); return null; } } while (t.isType(SEPARATOR_COMMA)); log("Exiting _getTypeArguments() (" + s.getTypeArgumentsLevel() + ")"); s.decreaseTypeArgumentsLevel(); s.clearResetPosition(); return typeArgs; } private TypeDeclaration _getTypeDeclaration(CompilationUnit cu, Scanner s) throws IOException { /* * TypeDeclaration: * ClassOrInterfaceDeclaration * ';' */ Token t = s.yylex(); if (t == null) { return null; // End of source file. } // Skip any semicolons. while (t.isType(SEPARATOR_SEMICOLON)) { t = s.yylex(); if (t == null) { return null; // End of source file } } s.yyPushback(t); // Probably some modifier, e.g. "public" String docComment = s.getLastDocComment(); TypeDeclaration td = _getClassOrInterfaceDeclaration(cu, s, cu, null); td.setDocComment(docComment); // May be null return td; } private TypeParameter _getTypeParameter(CompilationUnit cu, Scanner s) throws IOException { log("Entering _getTypeParameter()"); Token identifier = s.yylexNonNull(IDENTIFIER, "Identifier expected"); TypeParameter typeParam = new TypeParameter(identifier); if (s.yyPeekCheckType()==KEYWORD_EXTENDS) { do { s.yylex(); // Pop off "extends" or "&". typeParam.addBound(_getType(cu, s)); } while (s.yyPeekCheckType()==OPERATOR_BITWISE_AND); } log("Exiting _getTypeParameter(): " + typeParam.getName()); return typeParam; } private List _getTypeParameters(CompilationUnit cu, Scanner s) throws IOException { s.increaseTypeArgumentsLevel(); log("Entering _getTypeParameters() (" + s.getTypeArgumentsLevel() + ")"); s.markResetPosition(); Token t = s.yylexNonNull(OPERATOR_LT, "TypeParameters expected"); List typeParams = new ArrayList(1); do { TypeParameter typeParam = _getTypeParameter(cu, s); typeParams.add(typeParam); t = s.yylexNonNull(SEPARATOR_COMMA, OPERATOR_GT, "',' or '>' expected"); } while (t.isType(SEPARATOR_COMMA)); log("Exiting _getTypeParameters() (" + s.getTypeArgumentsLevel() + ")"); s.decreaseTypeArgumentsLevel(); return typeParams; } private int isModifier(Token t) { switch (t.getType()) { case KEYWORD_PUBLIC: case KEYWORD_PROTECTED: case KEYWORD_PRIVATE: case KEYWORD_STATIC: case KEYWORD_ABSTRACT: case KEYWORD_FINAL: case KEYWORD_NATIVE: case KEYWORD_SYNCHRONIZED: case KEYWORD_TRANSIENT: case KEYWORD_VOLATILE: case KEYWORD_STRICTFP: return t.getType(); default: return -1; } } private static final void log(String msg) { if (DEBUG) { System.out.println(msg); } } }