/* * Janino - An embedded Java[TM] compiler * * Copyright (c) 2001-2010, Arno Unkrig * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the * following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.codehaus.janino; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.Location; import org.codehaus.commons.compiler.WarningHandler; import org.codehaus.janino.Java.AlternateConstructorInvocation; import org.codehaus.janino.Java.AmbiguousName; import org.codehaus.janino.Java.Annotation; import org.codehaus.janino.Java.AnonymousClassDeclaration; import org.codehaus.janino.Java.ArrayAccessExpression; import org.codehaus.janino.Java.ArrayInitializer; import org.codehaus.janino.Java.ArrayInitializerOrRvalue; import org.codehaus.janino.Java.ArrayType; import org.codehaus.janino.Java.AssertStatement; import org.codehaus.janino.Java.Assignment; import org.codehaus.janino.Java.Atom; import org.codehaus.janino.Java.BasicType; import org.codehaus.janino.Java.BinaryOperation; import org.codehaus.janino.Java.Block; import org.codehaus.janino.Java.BlockStatement; import org.codehaus.janino.Java.BooleanLiteral; import org.codehaus.janino.Java.BreakStatement; import org.codehaus.janino.Java.Cast; import org.codehaus.janino.Java.CatchClause; import org.codehaus.janino.Java.CharacterLiteral; import org.codehaus.janino.Java.ClassDeclaration; import org.codehaus.janino.Java.ClassLiteral; import org.codehaus.janino.Java.CompilationUnit; import org.codehaus.janino.Java.CompilationUnit.ImportDeclaration; import org.codehaus.janino.Java.ConditionalExpression; import org.codehaus.janino.Java.ConstructorDeclarator; import org.codehaus.janino.Java.ConstructorInvocation; import org.codehaus.janino.Java.ContinueStatement; import org.codehaus.janino.Java.Crement; import org.codehaus.janino.Java.DoStatement; import org.codehaus.janino.Java.ElementValue; import org.codehaus.janino.Java.ElementValuePair; import org.codehaus.janino.Java.EmptyStatement; import org.codehaus.janino.Java.ExpressionStatement; import org.codehaus.janino.Java.FieldAccessExpression; import org.codehaus.janino.Java.FieldDeclaration; import org.codehaus.janino.Java.FloatingPointLiteral; import org.codehaus.janino.Java.ForEachStatement; import org.codehaus.janino.Java.ForStatement; import org.codehaus.janino.Java.FunctionDeclarator.FormalParameter; import org.codehaus.janino.Java.FunctionDeclarator.FormalParameters; import org.codehaus.janino.Java.IfStatement; import org.codehaus.janino.Java.Initializer; import org.codehaus.janino.Java.Instanceof; import org.codehaus.janino.Java.IntegerLiteral; import org.codehaus.janino.Java.InterfaceDeclaration; import org.codehaus.janino.Java.LabeledStatement; import org.codehaus.janino.Java.LocalClassDeclaration; import org.codehaus.janino.Java.LocalClassDeclarationStatement; import org.codehaus.janino.Java.LocalVariableDeclarationStatement; import org.codehaus.janino.Java.Lvalue; import org.codehaus.janino.Java.MemberClassDeclaration; import org.codehaus.janino.Java.MemberInterfaceDeclaration; import org.codehaus.janino.Java.MemberTypeDeclaration; import org.codehaus.janino.Java.MethodDeclarator; import org.codehaus.janino.Java.MethodInvocation; import org.codehaus.janino.Java.Modifiers; import org.codehaus.janino.Java.NamedClassDeclaration; import org.codehaus.janino.Java.NewAnonymousClassInstance; import org.codehaus.janino.Java.NewArray; import org.codehaus.janino.Java.NewClassInstance; import org.codehaus.janino.Java.NewInitializedArray; import org.codehaus.janino.Java.NullLiteral; import org.codehaus.janino.Java.PackageDeclaration; import org.codehaus.janino.Java.PackageMemberClassDeclaration; import org.codehaus.janino.Java.PackageMemberInterfaceDeclaration; import org.codehaus.janino.Java.PackageMemberTypeDeclaration; import org.codehaus.janino.Java.ParenthesizedExpression; import org.codehaus.janino.Java.QualifiedThisReference; import org.codehaus.janino.Java.ReferenceType; import org.codehaus.janino.Java.ReturnStatement; import org.codehaus.janino.Java.Rvalue; import org.codehaus.janino.Java.RvalueMemberType; import org.codehaus.janino.Java.Statement; import org.codehaus.janino.Java.StringLiteral; import org.codehaus.janino.Java.SuperConstructorInvocation; import org.codehaus.janino.Java.SuperclassFieldAccessExpression; import org.codehaus.janino.Java.SuperclassMethodInvocation; import org.codehaus.janino.Java.SwitchStatement; import org.codehaus.janino.Java.SynchronizedStatement; import org.codehaus.janino.Java.ThisReference; import org.codehaus.janino.Java.ThrowStatement; import org.codehaus.janino.Java.TryStatement; import org.codehaus.janino.Java.Type; import org.codehaus.janino.Java.TypeArgument; import org.codehaus.janino.Java.TypeParameter; import org.codehaus.janino.Java.UnaryOperation; import org.codehaus.janino.Java.VariableDeclarator; import org.codehaus.janino.Java.WhileStatement; import org.codehaus.janino.Java.Wildcard; import org.codehaus.janino.Scanner.Token; import org.codehaus.janino.util.enumerator.Enumerator; /** * A parser for the Java™ programming language. *
* 'JLS7' refers to the Java Language Specification, Java SE 7 * Edition. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class Parser { private final Scanner scanner; public Parser(Scanner scanner) { this.scanner = scanner; } /** @return The scanner that produces the tokens for this parser. */ public Scanner getScanner() { return this.scanner; } /** *
* CompilationUnit := [ PackageDeclaration ] * { ImportDeclaration } * { TypeDeclaration } **/ public CompilationUnit parseCompilationUnit() throws CompileException, IOException { CompilationUnit compilationUnit = new CompilationUnit(this.location().getFileName()); if (this.peek("package")) { compilationUnit.setPackageDeclaration(this.parsePackageDeclaration()); } while (this.peek("import")) { compilationUnit.addImportDeclaration(this.parseImportDeclaration()); } while (!this.peekEof()) { if (this.peekRead(";")) continue; compilationUnit.addPackageMemberTypeDeclaration(this.parsePackageMemberTypeDeclaration()); } return compilationUnit; } /** *
* PackageDeclaration := 'package' QualifiedIdentifier ';' **/ public PackageDeclaration parsePackageDeclaration() throws CompileException, IOException { this.read("package"); Location loc = this.location(); String packageName = Parser.join(this.parseQualifiedIdentifier(), "."); this.read(";"); this.verifyStringIsConventionalPackageName(packageName, loc); return new PackageDeclaration(loc, packageName); } /** *
* ImportDeclaration := 'import' ImportDeclarationBody ';' **/ public CompilationUnit.ImportDeclaration parseImportDeclaration() throws CompileException, IOException { this.read("import"); CompilationUnit.ImportDeclaration importDeclaration = this.parseImportDeclarationBody(); this.read(";"); return importDeclaration; } /** *
* ImportDeclarationBody := [ 'static' ] Identifier { '.' Identifier } [ '.' '*' ] **/ public CompilationUnit.ImportDeclaration parseImportDeclarationBody() throws CompileException, IOException { final Location loc = this.location(); boolean isStatic = this.peekRead("static"); List
* QualifiedIdentifier := Identifier { '.' Identifier } **/ public String[] parseQualifiedIdentifier() throws CompileException, IOException { List
* PackageMemberTypeDeclaration := * ModifiersOpt 'class' ClassDeclarationRest | * ModifiersOpt 'interface' InterfaceDeclarationRest **/ public PackageMemberTypeDeclaration parsePackageMemberTypeDeclaration() throws CompileException, IOException { String optionalDocComment = this.scanner.doc(); Modifiers modifiers = this.parseModifiers(); switch (this.read(new String[] { "class", "interface" })) { case 0: if (optionalDocComment == null) this.warning("CDCM", "Class doc comment missing", this.location()); return (PackageMemberClassDeclaration) this.parseClassDeclarationRest( optionalDocComment, // optionalDocComment modifiers, // modifiers ClassDeclarationContext.COMPILATION_UNIT // context ); case 1: if (optionalDocComment == null) this.warning("IDCM", "Interface doc comment missing", this.location()); return (PackageMemberInterfaceDeclaration) this.parseInterfaceDeclarationRest( optionalDocComment, // optionalDocComment modifiers, // modifiers InterfaceDeclarationContext.COMPILATION_UNIT // context ); default: throw new IllegalStateException(); } } /** *
* ModifiersAndAnnotations := { 'public' | 'protected' | 'private' | 'static' | 'abstract' | 'final' | 'native' * | 'synchronized' | 'transient' | 'volatile' | 'strictfp' | Annotation } **/ public Java.Modifiers parseModifiers() throws CompileException, IOException { short mod = 0; List
* Annotation := * MarkerAnnotation // JLS7 9.7.2 * | SingleElementAnnotation // JLS7 9.7.3 * | NormalAnnotation // JLS7 9.7.1 * * MarkerAnnotation := '@' Identifier * * SingleElementAnnotation := '@' Identifier '(' ElementValue ')' * * NormalAnnotation := '@' TypeName '(' ElementValuePairsOpt ')' * * ElementValuePairsOpt := [ ElementValuePair { ',' ElementValuePair } ] **/ private Java.Annotation parseAnnotation() throws CompileException, IOException { this.read("@"); ReferenceType type = this.parseReferenceType(); if (!this.peekRead("(")) return new Java.MarkerAnnotation(type); if (this.peekIdentifier() == null || !this.peekNextButOne("=")) { Java.ElementValue elementValue = this.parseElementValue(); this.read(")"); return new Java.SingleElementAnnotation(type, elementValue); } List
* ElementValuePair := Identifier '=' ElementValue **/ private Java.ElementValuePair parseElementValuePair() throws CompileException, IOException { String identifier = this.readIdentifier(); this.read("="); return new Java.ElementValuePair(identifier, this.parseElementValue()); } /** *
* ElementValue := * ConditionalExpression * | Annotation * | ElementValueArrayInitializer **/ private Java.ElementValue parseElementValue() throws CompileException, IOException { if (this.peek("@")) return this.parseAnnotation(); if (this.peek("{")) return this.parseElementValueArrayInitializer(); return this.parseConditionalAndExpression().toRvalueOrCompileException(); } /** *
* ElementValueArrayInitializer := '{' { ElementValue | ',' } '}' **/ private ElementValue parseElementValueArrayInitializer() throws CompileException, IOException { this.read("{"); List
* ClassDeclarationRest := * Identifier [ typeParameters ] * [ 'extends' ReferenceType ] * [ 'implements' ReferenceTypeList ] * ClassBody **/ public NamedClassDeclaration parseClassDeclarationRest( String optionalDocComment, Modifiers modifiers, ClassDeclarationContext context ) throws CompileException, IOException { Location location = this.location(); String className = this.readIdentifier(); this.verifyIdentifierIsConventionalClassOrInterfaceName(className, location); TypeParameter[] optionalTypeParameters = this.parseTypeParametersOpt(); ReferenceType optionalExtendedType = null; if (this.peekRead("extends")) { optionalExtendedType = this.parseReferenceType(); } ReferenceType[] implementedTypes = new ReferenceType[0]; if (this.peekRead("implements")) { implementedTypes = this.parseReferenceTypeList(); } NamedClassDeclaration namedClassDeclaration; if (context == ClassDeclarationContext.COMPILATION_UNIT) { namedClassDeclaration = new PackageMemberClassDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers className, // name optionalTypeParameters, // optionalTypeParameters optionalExtendedType, // optinalExtendedType implementedTypes // implementedTypes ); } else if (context == ClassDeclarationContext.TYPE_DECLARATION) { namedClassDeclaration = new MemberClassDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers className, // name optionalTypeParameters, // optionalTypeParameters optionalExtendedType, // optionalExtendedType implementedTypes // implementedTypes ); } else if (context == ClassDeclarationContext.BLOCK) { namedClassDeclaration = new LocalClassDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers className, // name optionalTypeParameters, // optionalTypeParameters optionalExtendedType, // optionalExtendedType implementedTypes // implementedTypes ); } else { throw new JaninoRuntimeException("SNO: Class declaration in unexpected context " + context); } this.parseClassBody(namedClassDeclaration); return namedClassDeclaration; } /** Enumerator for the kinds of context where a class declaration can occur. */ public static final class ClassDeclarationContext extends Enumerator { /** Enumerator for the kinds of context where a class declaration can occur. */ /** The class declaration appears inside a 'block'. */ /** The class declaration appears inside a 'block'. */ public static final ClassDeclarationContext BLOCK = new ClassDeclarationContext("block"); /** The class declaration appears (directly) inside a type declaration. */ public static final ClassDeclarationContext TYPE_DECLARATION = new ClassDeclarationContext("type_declaration"); /** The class declaration appears on the top level. */ public static final ClassDeclarationContext COMPILATION_UNIT = new ClassDeclarationContext("compilation_unit"); private ClassDeclarationContext(String name) { super(name); } } /** *
* ClassBody := '{' { ClassBodyDeclaration } '}' **/ public void parseClassBody(ClassDeclaration classDeclaration) throws CompileException, IOException { this.read("{"); for (;;) { if (this.peekRead("}")) return; this.parseClassBodyDeclaration(classDeclaration); } } /** *
* ClassBodyDeclaration := * ';' | * ModifiersOpt ( * Block | // Instance (JLS7 8.6) or static initializer (JLS7 8.7) * 'void' Identifier MethodDeclarationRest | * 'class' ClassDeclarationRest | * 'interface' InterfaceDeclarationRest | * ConstructorDeclarator | * Type Identifier ( * MethodDeclarationRest | * FieldDeclarationRest ';' * ) * ) * **/ public void parseClassBodyDeclaration(ClassDeclaration classDeclaration) throws CompileException, IOException { if (this.peekRead(";")) return; String optionalDocComment = this.scanner.doc(); Modifiers modifiers = this.parseModifiers(); // Initializer? if (this.peek("{")) { if ((modifiers.flags & ~Mod.STATIC) != 0) { throw this.compileException("Only modifier \"static\" allowed on initializer"); } Initializer initializer = new Initializer( this.location(), // location Mod.isStatic(modifiers.flags), // statiC this.parseBlock() // block ); classDeclaration.addInitializer(initializer); return; } // "void" method declaration. if (this.peekRead("void")) { Location location = this.location(); if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", location); String name = this.readIdentifier(); classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest( optionalDocComment, // optionalDocComment modifiers, // modifiers new BasicType(location, BasicType.VOID), // type name // name )); return; } // Member class. if (this.peekRead("class")) { if (optionalDocComment == null) this.warning("MCDCM", "Member class doc comment missing", this.location()); classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseClassDeclarationRest( optionalDocComment, // optionalDocComment modifiers, // modifiers ClassDeclarationContext.TYPE_DECLARATION // context )); return; } // Member interface. if (this.peekRead("interface")) { if (optionalDocComment == null) { this.warning("MIDCM", "Member interface doc comment missing", this.location()); } classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseInterfaceDeclarationRest( optionalDocComment, // optionalDocComment modifiers.add(Mod.STATIC), // modifiers InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context )); return; } // Constructor. if ( classDeclaration instanceof NamedClassDeclaration && this.peek().value.equals(((NamedClassDeclaration) classDeclaration).getName()) && this.peekNextButOne("(") ) { if (optionalDocComment == null) this.warning("CDCM", "Constructor doc comment missing", this.location()); classDeclaration.addConstructor(this.parseConstructorDeclarator( optionalDocComment, // declaringClass modifiers // modifiers )); return; } // Member method or field. Type memberType = this.parseType(); Location location = this.location(); String memberName = this.readIdentifier(); // Method declarator. if (this.peek("(")) { if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location()); classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest( optionalDocComment, // optionalDocComment modifiers, // modifiers memberType, // type memberName // name )); return; } // Field declarator. if (optionalDocComment == null) this.warning("FDCM", "Field doc comment missing", this.location()); FieldDeclaration fd = new FieldDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers memberType, // type this.parseFieldDeclarationRest(memberName) // variableDeclarators ); this.read(";"); classDeclaration.addFieldDeclaration(fd); } /** *
* InterfaceDeclarationRest := * Identifier [ typeParameters ] * [ 'extends' ReferenceTypeList ] * InterfaceBody **/ public InterfaceDeclaration parseInterfaceDeclarationRest( String optionalDocComment, Modifiers modifiers, InterfaceDeclarationContext context ) throws CompileException, IOException { Location location = this.location(); String interfaceName = this.readIdentifier(); this.verifyIdentifierIsConventionalClassOrInterfaceName(interfaceName, location); TypeParameter[] optionalTypeParameters = this.parseTypeParametersOpt(); ReferenceType[] extendedTypes = new ReferenceType[0]; if (this.peekRead("extends")) { extendedTypes = this.parseReferenceTypeList(); } InterfaceDeclaration interfaceDeclaration; if (context == InterfaceDeclarationContext.COMPILATION_UNIT) { interfaceDeclaration = new PackageMemberInterfaceDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers interfaceName, // name optionalTypeParameters, // optionalTypeParameters extendedTypes // extendedTypes ); } else if (context == InterfaceDeclarationContext.NAMED_TYPE_DECLARATION) { interfaceDeclaration = new MemberInterfaceDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers, // modifiers interfaceName, // name optionalTypeParameters, // optionalTypeParameters extendedTypes // extendedTypes ); } else { throw new JaninoRuntimeException("SNO: Interface declaration in unexpected context " + context); } this.parseInterfaceBody(interfaceDeclaration); return interfaceDeclaration; } /** Enumerator for the kinds of context where an interface declaration can occur. */ public static final class InterfaceDeclarationContext extends Enumerator { /** The interface declaration appears (directly) inside a 'named type declaration'. */ public static final InterfaceDeclarationContext NAMED_TYPE_DECLARATION = new InterfaceDeclarationContext("named_type_declaration"); /** The interface declaration appears at the top level. */ public static final InterfaceDeclarationContext COMPILATION_UNIT = new InterfaceDeclarationContext("compilation_unit"); private InterfaceDeclarationContext(String name) { super(name); } } /** *
* InterfaceBody := '{' { * ';' | * ModifiersOpt ( * 'void' Identifier MethodDeclarationRest | * 'class' ClassDeclarationRest | * 'interface' InterfaceDeclarationRest | * Type Identifier ( * MethodDeclarationRest | * FieldDeclarationRest * ) * ) * } '}' **/ public void parseInterfaceBody(InterfaceDeclaration interfaceDeclaration) throws CompileException, IOException { this.read("{"); while (!this.peekRead("}")) { if (this.peekRead(";")) continue; String optionalDocComment = this.scanner.doc(); Modifiers modifiers = this.parseModifiers(); // "void" method declaration. if (this.peekRead("void")) { if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location()); Location location = this.location(); String name = this.readIdentifier(); interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest( optionalDocComment, // optionalDocComment modifiers.add(Mod.ABSTRACT | Mod.PUBLIC), // modifiers new BasicType(location, BasicType.VOID), // type name // name )); } else // Member class. if (this.peekRead("class")) { if (optionalDocComment == null) { this.warning("MCDCM", "Member class doc comment missing", this.location()); } interfaceDeclaration.addMemberTypeDeclaration( (MemberTypeDeclaration) this.parseClassDeclarationRest( optionalDocComment, // optionalDocComment modifiers.add(Mod.STATIC | Mod.PUBLIC), // ModifiersAndAnnotations ClassDeclarationContext.TYPE_DECLARATION // context ) ); } else // Member interface. if (this.peekRead("interface")) { if (optionalDocComment == null) { this.warning("MIDCM", "Member interface doc comment missing", this.location()); } interfaceDeclaration.addMemberTypeDeclaration( (MemberTypeDeclaration) this.parseInterfaceDeclarationRest( optionalDocComment, // optionalDocComment modifiers.add(Mod.STATIC | Mod.PUBLIC), // ModifiersAndAnnotations InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context ) ); } else // Member method or field. { this.parseTypeArgumentsOpt(); Type memberType = this.parseType(); String memberName = this.readIdentifier(); Location location = this.location(); // Method declarator. if (this.peek("(")) { if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location()); interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest( optionalDocComment, // optionalDocComment modifiers.add(Mod.ABSTRACT | Mod.PUBLIC), // modifiers memberType, // type memberName // name )); } else // Field declarator. { if (optionalDocComment == null) this.warning("FDCM", "Field doc comment missing", this.location()); FieldDeclaration fd = new FieldDeclaration( location, // location optionalDocComment, // optionalDocComment modifiers.add(Mod.PUBLIC | Mod.STATIC | Mod.FINAL), // modifiers memberType, // type this.parseFieldDeclarationRest(memberName) // variableDeclarators ); interfaceDeclaration.addConstantDeclaration(fd); } } } } /** *
* ConstructorDeclarator := * Identifier * FormalParameters * [ 'throws' ReferenceTypeList ] * '{' * [ 'this' Arguments ';' | 'super' Arguments ';' | Primary '.' 'super' Arguments ';' ] * BlockStatements * '}' **/ public ConstructorDeclarator parseConstructorDeclarator(String optionalDocComment, Modifiers modifiers) throws CompileException, IOException { this.readIdentifier(); // Class name // Parse formal parameters. final FormalParameters formalParameters = this.parseFormalParameters(); // Parse "throws" clause. ReferenceType[] thrownExceptions; if (this.peekRead("throws")) { thrownExceptions = this.parseReferenceTypeList(); } else { thrownExceptions = new ReferenceType[0]; } // Parse constructor body. final Location location = this.location(); this.read("{"); // Special treatment for the first statement of the constructor body: If this is surely an // expression statement, and if it could be a "ConstructorInvocation", then parse the // expression and check if it IS a ConstructorInvocation. ConstructorInvocation optionalConstructorInvocation = null; List
* MethodDeclarationRest := * FormalParameters * { '[' ']' } * [ 'throws' ReferenceTypeList ] * ( ';' | MethodBody ) **/ public MethodDeclarator parseMethodDeclarationRest( String optionalDocComment, Modifiers modifiers, Type type, String name ) throws CompileException, IOException { Location location = this.location(); this.verifyIdentifierIsConventionalMethodName(name, location); final FormalParameters formalParameters = this.parseFormalParameters(); for (int i = this.parseBracketsOpt(); i > 0; --i) type = new ArrayType(type); ReferenceType[] thrownExceptions; if (this.peekRead("throws")) { thrownExceptions = this.parseReferenceTypeList(); } else { thrownExceptions = new ReferenceType[0]; } List
* VariableInitializer := * ArrayInitializer | * Expression **/ public ArrayInitializerOrRvalue parseVariableInitializer() throws CompileException, IOException { if (this.peek("{")) { return this.parseArrayInitializer(); } else { return this.parseExpression().toRvalueOrCompileException(); } } /** *
* ArrayInitializer := * '{' [ VariableInitializer { ',' VariableInitializer } [ ',' ] '}' **/ public ArrayInitializer parseArrayInitializer() throws CompileException, IOException { final Location location = this.location(); this.read("{"); List
* FormalParameters := '(' [ FormalParameter { ',' FormalParameter } ] ')' **/ public FormalParameters parseFormalParameters() throws CompileException, IOException { this.read("("); if (this.peekRead(")")) return new FormalParameters(); List
* FormalParameter := [ 'final' ] Type [ '.' '.' '.' ] Identifier BracketsOpt **/ public FormalParameter parseFormalParameter(boolean[] hasEllipsis) throws CompileException, IOException { final boolean finaL = this.peekRead("final"); Type type = this.parseType(); // SUPPRESS CHECKSTYLE UsageDistance if (this.peekRead(".")) { this.read("."); this.read("."); hasEllipsis[0] = true; } Location location = this.location(); String name = this.readIdentifier(); this.verifyIdentifierIsConventionalLocalVariableOrParameterName(name, location); for (int i = this.parseBracketsOpt(); i > 0; --i) type = new ArrayType(type); return new FormalParameter(location, finaL, type, name); } /** *
* BracketsOpt := { '[' ']' } **/ int parseBracketsOpt() throws CompileException, IOException { int res = 0; while (this.peek("[") && this.peekNextButOne("]")) { this.read(); this.read(); ++res; } return res; } /** *
* MethodBody := Block **/ public Block parseMethodBody() throws CompileException, IOException { return this.parseBlock(); } /** *
* '{' BlockStatements '}' **/ public Block parseBlock() throws CompileException, IOException { Block block = new Block(this.location()); this.read("{"); block.addStatements(this.parseBlockStatements()); this.read("}"); return block; } /** *
* BlockStatements := { BlockStatement } **/ public List
* BlockStatement := { Identifier ':' } ( * ( Modifiers Type | ModifiersOpt BasicType ) VariableDeclarators ';' | * 'class' ... | * Statement | * 'final' Type VariableDeclarators ';' | * Expression ';' | * Expression VariableDeclarators ';' (1) * ) ** * (1) "Expression" must pose a type, and has optional trailing brackets. */ public BlockStatement parseBlockStatement() throws CompileException, IOException { // Statement? if ( ( this.peekIdentifier() != null && this.peekNextButOne(":") ) || this.peek(new String[] { "if", "for", "while", "do", "try", "switch", "synchronized", "return", "throw", "break", "continue", "assert" }) != -1 || this.peek(new String[] { "{", ";" }) != -1 ) return this.parseStatement(); // Local class declaration? if (this.peekRead("class")) { // JAVADOC[TM] ignores doc comments for local classes, but we // don't... String optionalDocComment = this.scanner.doc(); if (optionalDocComment == null) this.warning("LCDCM", "Local class doc comment missing", this.location()); final LocalClassDeclaration lcd = (LocalClassDeclaration) this.parseClassDeclarationRest( optionalDocComment, // optionalDocComment new Modifiers(), // modifiers ClassDeclarationContext.BLOCK // context ); return new LocalClassDeclarationStatement(lcd); } // Modifiers Type VariableDeclarators ';' if (this.peek(new String[] { "final", "@" }) != -1) { LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement( this.location(), // location this.parseModifiers(), // modifiers this.parseType(), // type this.parseVariableDeclarators() // variableDeclarators ); this.read(";"); return lvds; } // It's either a non-final local variable declaration or an expression statement. We can // only tell after parsing an expression. Atom a = this.parseExpression(); // Expression ';' if (this.peekRead(";")) { return new ExpressionStatement(a.toRvalueOrCompileException()); } // Expression VariableDeclarators ';' Type variableType = a.toTypeOrCompileException(); LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement( a.getLocation(), // location new Java.Modifiers(Mod.NONE), // modifiers variableType, // type this.parseVariableDeclarators() // variableDeclarators ); this.read(";"); return lvds; } /** *
* VariableDeclarators := VariableDeclarator { ',' VariableDeclarator } **/ public VariableDeclarator[] parseVariableDeclarators() throws CompileException, IOException { List
* FieldDeclarationRest := * VariableDeclaratorRest * { ',' VariableDeclarator } **/ public VariableDeclarator[] parseFieldDeclarationRest(String name) throws CompileException, IOException { List
* VariableDeclarator := Identifier VariableDeclaratorRest **/ public VariableDeclarator parseVariableDeclarator() throws CompileException, IOException { return this.parseVariableDeclaratorRest(this.readIdentifier()); } /** *
* VariableDeclaratorRest := { '[' ']' } [ '=' VariableInitializer ] ** Used by field declarations and local variable declarations. */ public VariableDeclarator parseVariableDeclaratorRest(String name) throws CompileException, IOException { Location loc = this.location(); int brackets = this.parseBracketsOpt(); ArrayInitializerOrRvalue initializer = null; if (this.peekRead("=")) initializer = this.parseVariableInitializer(); return new VariableDeclarator(loc, name, brackets, initializer); } /** *
* Statement := * LabeledStatement | * Block | * IfStatement | * ForStatement | * WhileStatement | * DoStatement | * TryStatement | * 'switch' ... | * 'synchronized' ... | * ReturnStatement | * ThrowStatement | * BreakStatement | * ContinueStatement | * EmptyStatement | * ExpressionStatement **/ public Statement parseStatement() throws CompileException, IOException { if (this.peekIdentifier() != null && this.peekNextButOne(":")) { return this.parseLabeledStatement(); } Statement stmt = ( this.peek("{") ? this.parseBlock() : this.peek("if") ? this.parseIfStatement() : this.peek("for") ? this.parseForStatement() : this.peek("while") ? this.parseWhileStatement() : this.peek("do") ? this.parseDoStatement() : this.peek("try") ? this.parseTryStatement() : this.peek("switch") ? this.parseSwitchStatement() : this.peek("synchronized") ? this.parseSynchronizedStatement() : this.peek("return") ? this.parseReturnStatement() : this.peek("throw") ? this.parseThrowStatement() : this.peek("break") ? this.parseBreakStatement() : this.peek("continue") ? this.parseContinueStatement() : this.peek("assert") ? this.parseAssertStatement() : this.peek(";") ? this.parseEmptyStatement() : this.parseExpressionStatement() ); if (stmt == null) throw this.compileException("'" + this.peek().value + "' NYI"); return stmt; } /** *
* LabeledStatement := Identifier ':' Statement **/ public Statement parseLabeledStatement() throws CompileException, IOException { String label = this.readIdentifier(); this.read(":"); return new LabeledStatement( this.location(), // location label, // label this.parseStatement() // body ); } /** *
* IfStatement := 'if' '(' Expression ')' Statement [ 'else' Statement ] **/ public Statement parseIfStatement() throws CompileException, IOException { final Location location = this.location(); this.read("if"); this.read("("); final Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); Statement thenStatement = this.parseStatement(); Statement optionalElseStatement = null; if (this.peekRead("else")) { optionalElseStatement = this.parseStatement(); } return new IfStatement( location, // location condition, // condition thenStatement, // thenStatement optionalElseStatement // optionalElseStatement ); } /** *
* ForStatement := * 'for' '(' [ ForInit ] ';' [ Expression ] ';' [ ExpressionList ] ')' Statement * | 'for' '(' FormalParameter ':' Expression ')' Statement * * ForInit := * Modifiers Type VariableDeclarators * | ModifiersOpt BasicType VariableDeclarators * | Expression VariableDeclarators (1) * | Expression { ',' Expression } ** (1) "Expression" must pose a type. */ public Statement parseForStatement() throws CompileException, IOException { this.read("for"); Location forLocation = this.location(); this.read("("); BlockStatement optionalInit = null; INIT: if (!this.peek(";")) { // 'for' '(' Modifiers Type VariableDeclarators // 'for' '(' [ Modifiers ] BasicType VariableDeclarators if (this.peek(new String[] { "final", "@", "byte", "short", "char", "int", "long", "float", "double", "boolean" }) != -1) { Modifiers modifiers = this.parseModifiers(); Type type = this.parseType(); if (this.peekIdentifier() != null && this.peekNextButOne(":")) { // 'for' '(' [ Modifiers ] Type identifier ':' Expression ')' Statement final String name = this.readIdentifier(); final Location nameLocation = this.location(); this.read(":"); Rvalue expression = this.parseExpression().toRvalue(); this.read(")"); return new ForEachStatement( forLocation, // location new FormalParameter( // currentElement nameLocation, Mod.isFinal(modifiers.flags), type, name ), expression, // expression this.parseStatement() // body ); } // 'for' '(' [ Modifiers ] Type VariableDeclarators optionalInit = new LocalVariableDeclarationStatement( this.location(), // location modifiers, // modifiers type, // type this.parseVariableDeclarators() // variableDeclarators ); break INIT; } Atom a = this.parseExpression(); if (this.peekIdentifier() != null) { if (this.peekNextButOne(":")) { // 'for' '(' Expression identifier ':' Expression ')' Statement final String name = this.readIdentifier(); final Location nameLocation = this.location(); this.read(":"); Rvalue expression = this.parseExpression().toRvalue(); this.read(")"); return new ForEachStatement( forLocation, // location new FormalParameter( // currentElement nameLocation, false, a.toTypeOrCompileException(), name ), expression, // expression this.parseStatement() // body ); } // 'for' '(' Expression VariableDeclarators optionalInit = new LocalVariableDeclarationStatement( this.location(), // location new Java.Modifiers(Mod.NONE), // modifiers a.toTypeOrCompileException(), // type this.parseVariableDeclarators() // variableDeclarators ); break INIT; } if (!this.peekRead(",")) { // 'for' '(' Expression optionalInit = new ExpressionStatement(a.toRvalueOrCompileException()); break INIT; } // 'for' '(' Expression { ',' Expression } { List
* WhileStatement := 'while' '(' Expression ')' Statement **/ public Statement parseWhileStatement() throws CompileException, IOException { final Location location = this.location(); this.read("while"); this.read("("); Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); return new WhileStatement( location, // location condition, // condition this.parseStatement() // body ); } /** *
* DoStatement := 'do' Statement 'while' '(' Expression ')' ';' **/ public Statement parseDoStatement() throws CompileException, IOException { final Location location = this.location(); this.read("do"); final Statement body = this.parseStatement(); this.read("while"); this.read("("); final Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); this.read(";"); return new DoStatement( location, // location body, // body condition // condition ); } /** *
* TryStatement := * 'try' Block Catches [ Finally ] | * 'try' Block Finally * * Catches := CatchClause { CatchClause } * * CatchClause := 'catch' '(' FormalParameter ')' Block * * Finally := 'finally' Block **/ public Statement parseTryStatement() throws CompileException, IOException { Location location = this.location(); this.read("try"); final Block body = this.parseBlock(); // { CatchClause } List
* SwitchStatement := * 'switch' '(' Expression ')' '{' { SwitchLabels BlockStatements } '}' * * SwitchLabels := SwitchLabels { SwitchLabels } * * SwitchLabel := 'case' Expression ':' | 'default' ':' **/ public Statement parseSwitchStatement() throws CompileException, IOException { final Location location = this.location(); this.read("switch"); this.read("("); final Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); this.read("{"); List
* SynchronizedStatement := * 'synchronized' '(' expression ')' Block **/ public Statement parseSynchronizedStatement() throws CompileException, IOException { final Location location = this.location(); this.read("synchronized"); this.read("("); Rvalue expression = this.parseExpression().toRvalueOrCompileException(); this.read(")"); return new SynchronizedStatement( location, // location expression, // expression this.parseBlock() // body ); } /** *
* ReturnStatement := 'return' [ Expression ] ';' **/ public Statement parseReturnStatement() throws CompileException, IOException { final Location location = this.location(); this.read("return"); Rvalue returnValue = this.peek(";") ? null : this.parseExpression().toRvalueOrCompileException(); this.read(";"); return new ReturnStatement(location, returnValue); } /** *
* ThrowStatement := 'throw' Expression ';' **/ public Statement parseThrowStatement() throws CompileException, IOException { final Location location = this.location(); this.read("throw"); final Rvalue expression = this.parseExpression().toRvalueOrCompileException(); this.read(";"); return new ThrowStatement(location, expression); } /** *
* BreakStatement := 'break' [ Identifier ] ';' **/ public Statement parseBreakStatement() throws CompileException, IOException { final Location location = this.location(); this.read("break"); String optionalLabel = null; if (this.peekIdentifier() != null) optionalLabel = this.readIdentifier(); this.read(";"); return new BreakStatement(location, optionalLabel); } /** *
* ContinueStatement := 'continue' [ Identifier ] ';' **/ public Statement parseContinueStatement() throws CompileException, IOException { final Location location = this.location(); this.read("continue"); String optionalLabel = null; if (this.peekIdentifier() != null) optionalLabel = this.readIdentifier(); this.read(";"); return new ContinueStatement(location, optionalLabel); } /** *
* AssertStatement := 'assert' Expression [ ':' Expression ] ';' **/ public Statement parseAssertStatement() throws CompileException, IOException { this.read("assert"); Location loc = this.location(); Rvalue expression1 = this.parseExpression().toRvalueOrCompileException(); Rvalue optionalExpression2 = this.peekRead(":") ? this.parseExpression().toRvalueOrCompileException() : null; this.read(";"); return new AssertStatement(loc, expression1, optionalExpression2); } /** *
* EmptyStatement := ';' **/ public Statement parseEmptyStatement() throws CompileException, IOException { Location location = this.location(); this.read(";"); return new EmptyStatement(location); } /** *
* ExpressionList := Expression { ',' Expression } **/ public Rvalue[] parseExpressionList() throws CompileException, IOException { List
* Type := ( * 'byte' | 'short' | 'char' | 'int' | 'long' | * 'float' | 'double' | 'boolean' | * ReferenceType * ) { '[' ']' } **/ public Type parseType() throws CompileException, IOException { int idx = this.peekRead(Parser.BASIC_TYPE_NAMES); Type res = ( idx != -1 ? (Type) new BasicType(this.location(), Parser.BASIC_TYPE_CODES[idx]) : this.parseReferenceType() ); for (int i = this.parseBracketsOpt(); i > 0; --i) res = new ArrayType(res); return res; } private static final String[] BASIC_TYPE_NAMES = { "byte", "short", "char", "int", "long", "float", "double", "boolean" }; private static final int[] BASIC_TYPE_CODES = { BasicType.BYTE, BasicType.SHORT, BasicType.CHAR, BasicType.INT, BasicType.LONG, BasicType.FLOAT, BasicType.DOUBLE, BasicType.BOOLEAN }; /** *
* ReferenceType := QualifiedIdentifier [ TypeArguments ] **/ public ReferenceType parseReferenceType() throws CompileException, IOException { return new ReferenceType(this.location(), this.parseQualifiedIdentifier(), this.parseTypeArgumentsOpt()); } /** *
* TypeParameters := '<' TypeParameter { ',' TypeParameter } '>' **/ private TypeParameter[] parseTypeParametersOpt() throws CompileException, IOException { if (!this.peekRead("<")) return null; List
* TypeParameter := identifier [ 'extends' ( identifier | ReferenceType { '&' ReferenceType } **/ private TypeParameter parseTypeParameter() throws CompileException, IOException { String name = this.readIdentifier(); if (this.peekRead("extends")) { List
* TypeArguments := '<' TypeArgument { ',' TypeArgument } '>' **/ private TypeArgument[] parseTypeArgumentsOpt() throws CompileException, IOException { if (!this.peekRead("<")) return null; // Temporarily switch the scanner into 'expect greater' mode, where it doesn't recognize operators starting // with '>>'. boolean orig = this.scanner.setExpectGreater(true); try { List
* TypeArgument := * ReferenceType { '[' ']' } <= The optional brackets are mising in JLS7, section 18!? * | BasicType '[' ']' { '[' ']' } * | '?' extends ReferenceType * | '?' super ReferenceType **/ private TypeArgument parseTypeArgument() throws CompileException, IOException { if (this.peekRead("?")) { return ( this.peekRead("extends") ? new Wildcard(Wildcard.BOUNDS_EXTENDS, this.parseReferenceType()) : this.peekRead("super") ? new Wildcard(Wildcard.BOUNDS_SUPER, this.parseReferenceType()) : new Wildcard() ); } Type t = this.parseType(); int i = this.parseBracketsOpt(); for (; i > 0; i--) t = new ArrayType(t); if (!(t instanceof TypeArgument)) throw this.compileException("'" + t + "' is not a valid type argument"); return (TypeArgument) t; } /** *
* ReferenceTypeList := ReferenceType { ',' ReferenceType } **/ public ReferenceType[] parseReferenceTypeList() throws CompileException, IOException { List
* Expression := AssignmentExpression **/ public Atom parseExpression() throws CompileException, IOException { return this.parseAssignmentExpression(); } /** *
* AssignmentExpression := * ConditionalExpression [ AssignmentOperator AssignmentExpression ] * * AssignmentOperator := * '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | * '>>=' | '>>>=' | '&=' | '^=' | '|=' **/ public Atom parseAssignmentExpression() throws CompileException, IOException { Atom a = this.parseConditionalExpression(); if (this.peek( new String[] { "=", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=" } ) != -1) { final Lvalue lhs = a.toLvalueOrCompileException(); Location location = this.location(); String operator = this.readOperator(); final Rvalue rhs = this.parseAssignmentExpression().toRvalueOrCompileException(); return new Assignment(location, lhs, operator, rhs); } return a; } /** *
* ConditionalExpression := * ConditionalOrExpression [ '?' Expression ':' ConditionalExpression ] **/ public Atom parseConditionalExpression() throws CompileException, IOException { Atom a = this.parseConditionalOrExpression(); if (!this.peekRead("?")) return a; Location location = this.location(); Rvalue lhs = a.toRvalueOrCompileException(); Rvalue mhs = this.parseExpression().toRvalueOrCompileException(); this.read(":"); Rvalue rhs = this.parseConditionalExpression().toRvalueOrCompileException(); return new ConditionalExpression(location, lhs, mhs, rhs); } /** *
* ConditionalOrExpression := * ConditionalAndExpression { '||' ConditionalAndExpression ] **/ public Atom parseConditionalOrExpression() throws CompileException, IOException { Atom a = this.parseConditionalAndExpression(); while (this.peekRead("||")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "||", this.parseConditionalAndExpression().toRvalueOrCompileException() ); } return a; } /** *
* ConditionalAndExpression := * InclusiveOrExpression { '&&' InclusiveOrExpression } **/ public Atom parseConditionalAndExpression() throws CompileException, IOException { Atom a = this.parseInclusiveOrExpression(); while (this.peekRead("&&")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "&&", this.parseInclusiveOrExpression().toRvalueOrCompileException() ); } return a; } /** *
* InclusiveOrExpression := * ExclusiveOrExpression { '|' ExclusiveOrExpression } **/ public Atom parseInclusiveOrExpression() throws CompileException, IOException { Atom a = this.parseExclusiveOrExpression(); while (this.peekRead("|")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "|", this.parseExclusiveOrExpression().toRvalueOrCompileException() ); } return a; } /** *
* ExclusiveOrExpression := * AndExpression { '^' AndExpression } **/ public Atom parseExclusiveOrExpression() throws CompileException, IOException { Atom a = this.parseAndExpression(); while (this.peekRead("^")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "^", this.parseAndExpression().toRvalueOrCompileException() ); } return a; } /** *
* AndExpression := * EqualityExpression { '&' EqualityExpression } **/ public Atom parseAndExpression() throws CompileException, IOException { Atom a = this.parseEqualityExpression(); while (this.peekRead("&")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "&", this.parseEqualityExpression().toRvalueOrCompileException() ); } return a; } /** *
* EqualityExpression := * RelationalExpression { ( '==' | '!=' ) RelationalExpression } **/ public Atom parseEqualityExpression() throws CompileException, IOException { Atom a = this.parseRelationalExpression(); while (this.peek(new String[] { "==", "!=" }) != -1) { a = new BinaryOperation( this.location(), // location a.toRvalueOrCompileException(), // lhs this.read().value, // op this.parseRelationalExpression().toRvalueOrCompileException() // rhs ); } return a; } /** *
* RelationalExpression := * ShiftExpression { * 'instanceof' ReferenceType * | '<' ShiftExpression [ { ',' TypeArgument } '>' ] * | '<' TypeArgument [ { ',' TypeArgument } '>' ] * | ( '>' | '<=' | '>=' ) ShiftExpression * } **/ public Atom parseRelationalExpression() throws CompileException, IOException { Atom a = this.parseShiftExpression(); for (;;) { if (this.peekRead("instanceof")) { Location location = this.location(); a = new Instanceof( location, a.toRvalueOrCompileException(), this.parseType() ); } else if (this.peek(new String[] { "<", ">", "<=", ">=" }) != -1) { String op = this.read().value; if ("<".equals(op) && a instanceof Java.AmbiguousName && this.peek("?")) { final String[] identifiers = ((Java.AmbiguousName) a).identifiers; // Temporarily switch the scanner into 'expect greater' mode, where it doesn't recognize operators // starting with '>>'. boolean orig = this.scanner.setExpectGreater(true); try { // '<' TypeArgument [ { ',' TypeArgument } '>' ] List
* ShiftExpression := * AdditiveExpression { ( '<<' | '>>' | '>>>' ) AdditiveExpression } **/ public Atom parseShiftExpression() throws CompileException, IOException { Atom a = this.parseAdditiveExpression(); while (this.peek(new String[] { "<<", ">>", ">>>" }) != -1) { a = new BinaryOperation( this.location(), // location a.toRvalueOrCompileException(), // lhs this.read().value, // op this.parseAdditiveExpression().toRvalueOrCompileException() // rhs ); } return a; } /** *
* AdditiveExpression := * MultiplicativeExpression { ( '+' | '-' ) MultiplicativeExpression } **/ public Atom parseAdditiveExpression() throws CompileException, IOException { Atom a = this.parseMultiplicativeExpression(); while (this.peek(new String[] { "+", "-" }) != -1) { a = new BinaryOperation( this.location(), // location a.toRvalueOrCompileException(), // lhs this.read().value, // op this.parseMultiplicativeExpression().toRvalueOrCompileException() // rhs ); } return a; } /** *
* MultiplicativeExpression := * UnaryExpression { ( '*' | '/' | '%' ) UnaryExpression } **/ public Atom parseMultiplicativeExpression() throws CompileException, IOException { Atom a = this.parseUnaryExpression(); while (this.peek(new String[] { "*", "/", "%" }) != -1) { a = new BinaryOperation( this.location(), // location a.toRvalueOrCompileException(), // lhs this.read().value, // op this.parseUnaryExpression().toRvalueOrCompileException() // rhs ); } return a; } /** *
* UnaryExpression := * { PrefixOperator } Primary { Selector } { PostfixOperator } * * PrefixOperator := '++' | '--' | '+' | '-' | '~' | '!' * * PostfixOperator := '++' | '--' **/ public Atom parseUnaryExpression() throws CompileException, IOException { if (this.peek(new String[] { "++", "--" }) != -1) { return new Crement( this.location(), // location this.read().value, // operator this.parseUnaryExpression().toLvalueOrCompileException() // operand ); } if (this.peek(new String[] { "+", "-", "~", "!" }) != -1) { return new UnaryOperation( this.location(), // location this.read().value, // operator this.parseUnaryExpression().toRvalueOrCompileException() // operand ); } Atom a = this.parsePrimary(); while (this.peek(new String[] { ".", "[" }) != -1) { a = this.parseSelector(a); } while (this.peek(new String[] { "++", "--" }) != -1) { a = new Crement( this.location(), // location a.toLvalueOrCompileException(), // operand this.read().value // operator ); } return a; } /** *
* Primary := * CastExpression | // CastExpression 15.16 * '(' Expression ')' | // ParenthesizedExpression 15.8.5 * Literal | // Literal 15.8.1 * Name | // AmbiguousName * Name Arguments | // MethodInvocation * Name '[]' { '[]' } | // ArrayType 10.1 * Name '[]' { '[]' } '.' 'class' | // ClassLiteral 15.8.2 * 'this' | // This 15.8.3 * 'this' Arguments | // Alternate constructor invocation 8.8.5.1 * 'super' Arguments | // Unqualified superclass constructor invocation 8.8.5.1 * 'super' '.' Identifier | // SuperclassFieldAccess 15.11.2 * 'super' '.' Identifier Arguments | // SuperclassMethodInvocation 15.12.4.9 * NewClassInstance | * NewAnonymousClassInstance | // ClassInstanceCreationExpression 15.9 * NewArray | // ArrayCreationExpression 15.10 * NewInitializedArray | // ArrayInitializer 10.6 * BasicType { '[]' } | // Type * BasicType { '[]' } '.' 'class' | // ClassLiteral 15.8.2 * 'void' '.' 'class' // ClassLiteral 15.8.2 * * CastExpression := * '(' PrimitiveType { '[]' } ')' UnaryExpression | * '(' Expression ')' UnaryExpression * * NewClassInstance := 'new' ReferenceType Arguments * * NewAnonymousClassInstance := 'new' ReferenceType Arguments [ ClassBody ] * * NewArray := 'new' Type DimExprs { '[]' } * * NewInitializedArray := 'new' ArrayType ArrayInitializer **/ public Atom parsePrimary() throws CompileException, IOException { if (this.peekRead("(")) { if ( this.peek(new String[] { "boolean", "char", "byte", "short", "int", "long", "float", "double", }) != -1 ) { // '(' PrimitiveType { '[]' } ')' UnaryExpression Type type = this.parseType(); int brackets = this.parseBracketsOpt(); this.read(")"); for (int i = 0; i < brackets; ++i) type = new ArrayType(type); return new Cast( this.location(), // location type, // targetType this.parseUnaryExpression().toRvalueOrCompileException() // value ); } Atom a = this.parseExpression(); this.read(")"); if ( this.peekLiteral() || this.peekIdentifier() != null || this.peek(new String[] { "(", "~", "!", }) != -1 || this.peek(new String[] { "this", "super", "new", }) != -1 ) { // '(' Expression ')' UnaryExpression return new Cast( this.location(), // location a.toTypeOrCompileException(), // targetType this.parseUnaryExpression().toRvalueOrCompileException() // value ); } // '(' Expression ')' return new ParenthesizedExpression(a.getLocation(), a.toRvalueOrCompileException()); } if (this.peekLiteral()) { // Literal return this.parseLiteral(); } if (this.peekIdentifier() != null) { Location location = this.location(); String[] qi = this.parseQualifiedIdentifier(); if (this.peek("(")) { // Name Arguments return new MethodInvocation( this.location(), // location qi.length == 1 ? null : new AmbiguousName( // optionalTarget location, // location qi, // identifiers qi.length - 1 // n ), qi[qi.length - 1], // methodName this.parseArguments() // arguments ); } if (this.peek("[") && this.peekNextButOne("]")) { // Name '[]' { '[]' } // Name '[]' { '[]' } '.' 'class' Type res = new ReferenceType( location, // location qi, // identifiers null // optionalTypeArguments ); int brackets = this.parseBracketsOpt(); for (int i = 0; i < brackets; ++i) res = new ArrayType(res); if (this.peek(".") && this.peekNextButOne("class")) { this.read(); Location location2 = this.location(); this.read(); return new ClassLiteral(location2, res); } else { return res; } } // Name return new AmbiguousName( location, // location qi // identifiers ); } if (this.peekRead("this")) { Location location = this.location(); if (this.peek("(")) { // 'this' Arguments // Alternate constructor invocation (JLS7 8.8.7.1). return new AlternateConstructorInvocation( location, // location this.parseArguments() // arguments ); } else { // 'this' return new ThisReference(location); } } if (this.peekRead("super")) { if (this.peek("(")) { // 'super' Arguments // Unqualified superclass constructor invocation (JLS7 8.8.7.1). return new SuperConstructorInvocation( this.location(), // location (Rvalue) null, // optionalQualification this.parseArguments() // arguments ); } this.read("."); String name = this.readIdentifier(); if (this.peek("(")) { // 'super' '.' Identifier Arguments return new SuperclassMethodInvocation( this.location(), // location name, // methodName this.parseArguments() // arguments ); } else { // 'super' '.' Identifier return new SuperclassFieldAccessExpression( this.location(), // location (Type) null, // optionalQualification name // fieldName ); } } // 'new' if (this.peekRead("new")) { Location location = this.location(); Type type = this.parseType(); if (type instanceof ArrayType) { // 'new' ArrayType ArrayInitializer return new NewInitializedArray(location, (ArrayType) type, this.parseArrayInitializer()); } if (type instanceof ReferenceType && this.peek("(")) { // 'new' ReferenceType Arguments [ ClassBody ] Rvalue[] arguments = this.parseArguments(); if (this.peek("{")) { // 'new' ReferenceType Arguments ClassBody final AnonymousClassDeclaration anonymousClassDeclaration = new AnonymousClassDeclaration( this.location(), // location type // baseType ); this.parseClassBody(anonymousClassDeclaration); return new NewAnonymousClassInstance( location, // location (Rvalue) null, // optionalQualification anonymousClassDeclaration, // anonymousClassDeclaration arguments // arguments ); } else { // 'new' ReferenceType Arguments return new NewClassInstance( location, // location (Rvalue) null, // optionalQualification type, // type arguments // arguments ); } } // 'new' Type DimExprs { '[]' } return new NewArray( location, // location type, // type this.parseDimExprs(), // dimExprs this.parseBracketsOpt() // dims ); } // BasicType if (this.peek(new String[] { "boolean", "char", "byte", "short", "int", "long", "float", "double" }) != -1) { Type res = this.parseType(); int brackets = this.parseBracketsOpt(); for (int i = 0; i < brackets; ++i) res = new ArrayType(res); if (this.peek(".") && this.peekNextButOne("class")) { // BasicType { '[]' } '.' 'class' this.read(); Location location = this.location(); this.read(); return new ClassLiteral(location, res); } // BasicType { '[]' } return res; } // 'void' if (this.peekRead("void")) { if (this.peek(".") && this.peekNextButOne("class")) { // 'void' '.' 'class' this.read(); Location location = this.location(); this.read(); return new ClassLiteral(location, new BasicType(location, BasicType.VOID)); } throw this.compileException("\"void\" encountered in wrong context"); } throw this.compileException("Unexpected token \"" + this.peek().value + "\" in primary"); } /** *
* Selector := * '.' Identifier | // FieldAccess 15.11.1 * '.' Identifier Arguments | // MethodInvocation * '.' 'this' // QualifiedThis 15.8.4 * '.' 'super' Arguments // Qualified superclass constructor invocation (JLS7 8.8.7.1) * '.' 'super' '.' Identifier | // SuperclassFieldReference (JLS7 15.11.2) * '.' 'super' '.' Identifier Arguments | // SuperclassMethodInvocation (JLS7 15.12.3) * '.' 'new' Identifier Arguments [ ClassBody ] | // QualifiedClassInstanceCreationExpression 15.9 * '.' 'class' * '[' Expression ']' // ArrayAccessExpression 15.13 **/ public Atom parseSelector(Atom atom) throws CompileException, IOException { if (this.peekRead(".")) { if (this.peek().type == Token.IDENTIFIER) { String identifier = this.readIdentifier(); if (this.peek("(")) { // '.' Identifier Arguments return new MethodInvocation( this.location(), // location atom.toRvalueOrCompileException(), // optionalTarget identifier, // methodName this.parseArguments() // arguments ); } // '.' Identifier return new FieldAccessExpression( this.location(), // location atom.toRvalueOrCompileException(), // lhs identifier // fieldName ); } if (this.peekRead("this")) { // '.' 'this' Location location = this.location(); return new QualifiedThisReference( location, // location atom.toTypeOrCompileException() // qualification ); } if (this.peekRead("super")) { Location location = this.location(); if (this.peek("(")) { // '.' 'super' Arguments // Qualified superclass constructor invocation (JLS7 8.7.1.2.2) (LHS is an Rvalue) return new SuperConstructorInvocation( location, // location atom.toRvalueOrCompileException(), // optionalQualification this.parseArguments() // arguments ); } this.read("."); String identifier = this.readIdentifier(); if (this.peek("(")) { // '.' 'super' '.' Identifier Arguments // Qualified superclass method invocation (JLS7 15.12.1.1.4) (LHS is a ClassName). // TODO: Qualified superclass method invocation throw this.compileException("Qualified superclass method invocation NYI"); } else { // '.' 'super' '.' Identifier // Qualified superclass field access (JLS7 15.11.2) (LHS is an Rvalue). return new SuperclassFieldAccessExpression( location, // location atom.toTypeOrCompileException(), // optionalQualification identifier // fieldName ); } } if (this.peekRead("new")) { // '.' 'new' Identifier Arguments [ ClassBody ] Rvalue lhs = atom.toRvalue(); Location location = this.location(); String identifier = this.readIdentifier(); Type type = new RvalueMemberType( location, // location lhs, // rValue identifier // identifier ); Rvalue[] arguments = this.parseArguments(); if (this.peek("{")) { // '.' 'new' Identifier Arguments ClassBody (LHS is an Rvalue) final AnonymousClassDeclaration anonymousClassDeclaration = new AnonymousClassDeclaration( this.location(), // location type // baseType ); this.parseClassBody(anonymousClassDeclaration); return new NewAnonymousClassInstance( location, // location lhs, // optionalQualification anonymousClassDeclaration, // anonymousClassDeclaration arguments // arguments ); } else { // '.' 'new' Identifier Arguments (LHS is an Rvalue) return new NewClassInstance( location, // location lhs, // optionalQualification type, // referenceType arguments // arguments ); } } if (this.peekRead("class")) { // '.' 'class' Location location = this.location(); return new ClassLiteral(location, atom.toTypeOrCompileException()); } throw this.compileException("Unexpected selector '" + this.peek().value + "' after \".\""); } if (this.peekRead("[")) { // '[' Expression ']' Location location = this.location(); Rvalue index = this.parseExpression().toRvalueOrCompileException(); this.read("]"); return new ArrayAccessExpression( location, // location atom.toRvalueOrCompileException(), // lhs index // index ); } throw this.compileException("Unexpected token '" + this.peek().value + "' in selector"); } /** *
* DimExprs := DimExpr { DimExpr } **/ public Rvalue[] parseDimExprs() throws CompileException, IOException { List
* DimExpr := '[' Expression ']' **/ public Rvalue parseDimExpr() throws CompileException, IOException { this.read("["); Rvalue res = this.parseExpression().toRvalueOrCompileException(); this.read("]"); return res; } /** *
* Arguments := '(' [ ArgumentList ] ')' **/ public Rvalue[] parseArguments() throws CompileException, IOException { this.read("("); if (this.peekRead(")")) return new Rvalue[0]; Rvalue[] arguments = this.parseArgumentList(); this.read(")"); return arguments; } /** *
* ArgumentList := Expression { ',' Expression } **/ public Rvalue[] parseArgumentList() throws CompileException, IOException { List
* Literal := * IntegerLiteral * | FloatingPointLiteral * | BooleanLiteral * | CharacterLiteral * | StringLiteral * | NullLiteral **/ public Rvalue parseLiteral() throws CompileException, IOException { Token t = this.read(); switch (t.type) { case Token.INTEGER_LITERAL: return new IntegerLiteral(t.getLocation(), t.value); case Token.FLOATING_POINT_LITERAL: return new FloatingPointLiteral(t.getLocation(), t.value); case Token.BOOLEAN_LITERAL: return new BooleanLiteral(t.getLocation(), t.value); case Token.CHARACTER_LITERAL: return new CharacterLiteral(t.getLocation(), t.value); case Token.STRING_LITERAL: return new StringLiteral(t.getLocation(), t.value); case Token.NULL_LITERAL: return new NullLiteral(t.getLocation(), t.value); default: throw this.compileException("Literal expected"); } } /** *
* ExpressionStatement := Expression ';' **/ public Statement parseExpressionStatement() throws CompileException, IOException { Rvalue rv = this.parseExpression().toRvalueOrCompileException(); this.read(";"); return new ExpressionStatement(rv); } /** @return The location of the first character of the previously {@link #peek()}ed or {@link #read()} token */ public Location location() { return this.scanner.location(); } private Token nextToken, nextButOneToken; // Token-level methods. /** @return The next token, but does not consume it */ public Token peek() throws CompileException, IOException { if (this.nextToken == null) this.nextToken = this.scanner.produce(); return this.nextToken; } /** @return The next-but-one token, but consumes neither the next nor the next-but-one token */ public Token peekNextButOne() throws CompileException, IOException { if (this.nextToken == null) this.nextToken = this.scanner.produce(); if (this.nextButOneToken == null) this.nextButOneToken = this.scanner.produce(); return this.nextButOneToken; } /** @return The next and also consumes it, or {@code null} iff the scanner is at end-of-input */ public Token read() throws CompileException, IOException { if (this.nextToken == null) return this.scanner.produce(); final Token result = this.nextToken; this.nextToken = this.nextButOneToken; this.nextButOneToken = null; return result; } // Peek/read/peekRead convenience methods. /** @return Whether the value of the next token equals {@code suspected}; does not consume the next token */ public boolean peek(String suspected) throws CompileException, IOException { return this.peek().value.equals(suspected); } /** * Checks whether the value of the next token equals any of the {@code suspected}; does not consume the next * token. * * @return The index of the first of the {@code suspected} that equals the value of the next token, or -1 if the * value of the next token equals none of the {@code suspected} */ public int peek(String[] suspected) throws CompileException, IOException { return Parser.indexOf(suspected, this.peek().value); } /** * Checks whether the type of the next token is any of the {@code suspected}; does not consume the next token. * * @return The index of the first of the {@code suspected} types that is the next token's type, or -1 if the type * of the next token is none of the {@code suspected} types */ public int peek(int[] suspected) throws CompileException, IOException { return Parser.indexOf(suspected, this.peek().type); } /** * @return Whether the value of the next-but-one token equals the {@code suspected}; consumes neither the next * nor the next-but-one token */ public boolean peekNextButOne(String suspected) throws CompileException, IOException { return this.peekNextButOne().value.equals(suspected); } /** * Verifies that the value of the next token equals {@code expected}, and consumes the token. * * @throws CompileException The value of the next token does not equal {@code expected} (this includes the case * that the scanner is at end-of-input) */ public void read(String expected) throws CompileException, IOException { String s = this.read().value; if (!s.equals(expected)) throw this.compileException("'" + expected + "' expected instead of '" + s + "'"); } /** * Verifies that the value of the next token equals one of the {@code expected}, and consumes the token. * * @return The index of the consumed token within {@code expected} * @throws CompileException The value of the next token does not equal any of the {@code expected} (this includes * the case where the scanner is at end-of-input) */ public int read(String[] expected) throws CompileException, IOException { String s = this.read().value; int idx = Parser.indexOf(expected, s); if (idx == -1) { throw this.compileException("One of '" + Parser.join(expected, " ") + "' expected instead of '" + s + "'"); } return idx; } /** * @return Whether the value of the next token equals the {@code suspected}; if so, it consumes the next token * @throws CompileException * @throws IOException */ public boolean peekRead(String suspected) throws CompileException, IOException { if (this.nextToken == null) { Token t = this.scanner.produce(); if (t.value.equals(suspected)) return true; this.nextToken = t; return false; } if (!this.nextToken.value.equals(suspected)) return false; this.nextToken = this.nextButOneToken; this.nextButOneToken = null; return true; } /** @return -1 iff the next token is none of
values
*/
public int
peekRead(String[] values) throws CompileException, IOException {
if (this.nextToken == null) {
Token t = this.scanner.produce();
int idx = Parser.indexOf(values, t.value);
if (idx != -1) return idx;
this.nextToken = t;
return -1;
}
int idx = Parser.indexOf(values, this.nextToken.value);
if (idx == -1) return -1;
this.nextToken = this.nextButOneToken;
this.nextButOneToken = null;
return idx;
}
/** @return Whether the scanner is at end-of-input */
public boolean
peekEof() throws CompileException, IOException {
return this.peek().type == Token.EOF;
}
/** @return {@code null} iff the next token is not an identifier, otherwise the value of the identifier token */
public String
peekIdentifier() throws CompileException, IOException {
Token t = this.peek();
return t.type == Token.IDENTIFIER ? t.value : null;
}
/** @return Whether the next token is a literal */
public boolean
peekLiteral() throws CompileException, IOException {
return this.peek(new int[] {
Token.INTEGER_LITERAL, Token.FLOATING_POINT_LITERAL, Token.BOOLEAN_LITERAL, Token.CHARACTER_LITERAL,
Token.STRING_LITERAL, Token.NULL_LITERAL,
}) != -1;
}
/**
* @return The value of the next token, which is an indentifier
* @throws CompileException The next token is not an identifier
*/
public String
readIdentifier() throws CompileException, IOException {
Token t = this.read();
if (t.type != Token.IDENTIFIER) throw this.compileException("Identifier expected instead of '" + t.value + "'");
return t.value;
}
/**
* @return The value of the next token, which is an operator
* @throws CompileException The next token is not an operator
*/
public String
readOperator() throws CompileException, IOException {
Token t = this.read();
if (t.type != Token.OPERATOR) throw this.compileException("Operator expected instead of '" + t.value + "'");
return t.value;
}
private static int
indexOf(String[] strings, String subject) {
for (int i = 0; i < strings.length; ++i) {
if (strings[i].equals(subject)) return i;
}
return -1;
}
private static int
indexOf(int[] values, int subject) {
for (int i = 0; i < values.length; ++i) {
if (values[i] == subject) return i;
}
return -1;
}
/** Issue a warning if the given string does not comply with the package naming conventions. */
private void
verifyStringIsConventionalPackageName(String s, Location loc) throws CompileException {
if (!Character.isLowerCase(s.charAt(0))) {
this.warning(
"UPN",
"Package name \"" + s + "\" does not begin with a lower-case letter (see JLS7 6.8.1)",
loc
);
return;
}
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (!Character.isLowerCase(c) && c != '_' && c != '.') {
this.warning("PPN", "Poorly chosen package name \"" + s + "\" contains bad character '" + c + "'", loc);
return;
}
}
}
/**
* Issue a warning if the given identifier does not comply with the class and interface type naming conventions
* (JLS7 6.8.2).
*/
private void
verifyIdentifierIsConventionalClassOrInterfaceName(String id, Location loc) throws CompileException {
if (!Character.isUpperCase(id.charAt(0))) {
this.warning(
"UCOIN1",
"Class or interface name \"" + id + "\" does not begin with an upper-case letter (see JLS7 6.8.2)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning("UCOIN", (
"Class or interface name \""
+ id
+ "\" contains unconventional character \""
+ c
+ "\" (see JLS7 6.8.2)"
), loc);
return;
}
}
}
/** Issue a warning if the given identifier does not comply with the method naming conventions (JLS7 6.8.3). */
private void
verifyIdentifierIsConventionalMethodName(String id, Location loc) throws CompileException {
if (!Character.isLowerCase(id.charAt(0))) {
this.warning(
"UMN1",
"Method name \"" + id + "\" does not begin with a lower-case letter (see JLS7 6.8.3)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning(
"UMN",
"Method name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.3)",
loc
);
return;
}
}
}
/**
* Issue a warning if the given identifier does not comply with the field naming conventions (JLS7 6.8.4) and
* constant naming conventions (JLS7 6.8.5).
*/
private void
verifyIdentifierIsConventionalFieldName(String id, Location loc) throws CompileException {
// In practice, a field is not always a constant iff it is static-final. So let's
// always tolerate both field and constant names.
if (Character.isUpperCase(id.charAt(0))) {
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isUpperCase(c) && !Character.isDigit(c) && c != '_') {
this.warning(
"UCN",
"Constant name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.5)",
loc
);
return;
}
}
} else
if (Character.isLowerCase(id.charAt(0))) {
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning(
"UFN",
"Field name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.4)",
loc
);
return;
}
}
} else {
this.warning("UFN1", (
"\""
+ id
+ "\" is neither a conventional field name (JLS7 6.8.4) nor a conventional constant name (JLS7 6.8.5)"
), loc);
}
}
/**
* Issue a warning if the given identifier does not comply with the local variable and parameter naming conventions
* (JLS7 6.8.6).
*/
private void
verifyIdentifierIsConventionalLocalVariableOrParameterName(String id, Location loc) throws CompileException {
if (!Character.isLowerCase(id.charAt(0))) {
this.warning(
"ULVN1",
"Local variable name \"" + id + "\" does not begin with a lower-case letter (see JLS7 6.8.6)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning("ULVN", (
"Local variable name \""
+ id
+ "\" contains unconventional character \""
+ c
+ "\" (see JLS7 6.8.6)"
), loc);
return;
}
}
}
/**
* By default, warnings are discarded, but an application my install a {@link WarningHandler}.
*
* Notice that there is no Parser.setErrorHandler()
method, but parse errors always throw a {@link
* CompileException}. The reason being is that there is no reasonable way to recover from parse errors and continue
* parsing, so there is no need to install a custom parse error handler.
*
* @param optionalWarningHandler null
to indicate that no warnings be issued
*/
public void
setWarningHandler(WarningHandler optionalWarningHandler) {
this.optionalWarningHandler = optionalWarningHandler;
}
// Used for elaborate warning handling.
private WarningHandler optionalWarningHandler;
/**
* Issues a warning with the given message and location and returns. This is done through
* a {@link WarningHandler} that was installed through
* {@link #setWarningHandler(WarningHandler)}.
*
* The handle
argument qulifies the warning and is typically used by
* the {@link WarningHandler} to suppress individual warnings.
*
* @throws CompileException The optionally installed {@link WarningHandler} decided to throw a {@link
* CompileException}
*/
private void
warning(String handle, String message, Location optionalLocation) throws CompileException {
if (this.optionalWarningHandler != null) {
this.optionalWarningHandler.handleWarning(handle, message, optionalLocation);
}
}
/** Convenience method for throwing a {@link CompileException}. */
protected final CompileException
compileException(String message) {
return new CompileException(message, this.location());
}
private static String
join(String[] sa, String separator) {
if (sa == null) return ("(null)");
if (sa.length == 0) return ("(zero length array)");
StringBuilder sb = new StringBuilder(sa[0]);
for (int i = 1; i < sa.length; ++i) {
sb.append(separator).append(sa[i]);
}
return sb.toString();
}
}