/* * 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.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.HashMap; import java.util.Map; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.ICookable; import org.codehaus.commons.compiler.Location; import org.codehaus.commons.compiler.WarningHandler; import org.codehaus.janino.util.TeeReader; /** * Splits up a character stream into tokens and returns them as * {@link java.lang.String String} objects. *

* The optionalFileName parameter passed to many * constructors should point */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class Scanner { // Public Scanners that read from a file. /** * Set up a scanner that reads tokens from the given file in the default charset. *

* This method is deprecated because it leaves the input file open. * * @deprecated // SUPPRESS CHECKSTYLE MissingDeprecated */ @Deprecated public Scanner(String fileName) throws CompileException, IOException { this( fileName, // optionalFileName new FileInputStream(fileName) // is ); } /** * Set up a scanner that reads tokens from the given file in the given encoding. *

* This method is deprecated because it leaves the input file open. * * @deprecated // SUPPRESS CHECKSTYLE MissingDeprecated */ @Deprecated public Scanner(String fileName, String encoding) throws CompileException, IOException { this( fileName, // optionalFileName new FileInputStream(fileName), // is encoding // optionalEncoding ); } /** * Set up a scanner that reads tokens from the given file in the platform * default encoding. *

* This method is deprecated because it leaves the input file open. * * @deprecated // SUPPRESS CHECKSTYLE MissingDeprecated */ @Deprecated public Scanner(File file) throws CompileException, IOException { this( file.getAbsolutePath(), // optionalFileName new FileInputStream(file), // is null // optionalEncoding ); } /** * Set up a scanner that reads tokens from the given file in the given encoding. *

* This method is deprecated because it leaves the input file open. * * @deprecated // SUPPRESS CHECKSTYLE MissingDeprecated */ @Deprecated public Scanner(File file, String optionalEncoding) throws CompileException, IOException { this( file.getAbsolutePath(), // optionalFileName new FileInputStream(file), // is optionalEncoding // optionalEncoding ); } // Public Scanners that read from an InputStream /** * Set up a scanner that reads tokens from the given * {@link InputStream} in the platform default encoding. *

* The fileName is solely used for reporting in thrown * exceptions. */ public Scanner(String optionalFileName, InputStream is) throws CompileException, IOException { this( optionalFileName, new InputStreamReader(is), // in (short) 1, // initialLineNumber (short) 0 // initialColumnNumber ); } /** * Set up a scanner that reads tokens from the given * {@link InputStream} with the given optionalEncoding * (null means platform default encoding). *

* The optionalFileName is used for reporting errors during * compilation and for source level debugging, and should name an existing * file. If null is passed, and the system property * org.codehaus.janino.source_debugging.enable is set to "true", then * a temporary file in org.codehaus.janino.source_debugging.dir or the * system's default temp dir is created in order to make the source code * available to a debugger. */ public Scanner(String optionalFileName, InputStream is, String optionalEncoding) throws CompileException, IOException { this( optionalFileName, // optionalFileName ( // in optionalEncoding == null ? new InputStreamReader(is) : new InputStreamReader(is, optionalEncoding) ), (short) 1, // initialLineNumber (short) 0 // initialColumnNumber ); } // Public Scanners that read from a Reader. /** * Set up a scanner that reads tokens from the given * {@link Reader}. *

* The optionalFileName is used for reporting errors during * compilation and for source level debugging, and should name an existing * file. If null is passed, and the system property * org.codehaus.janino.source_debugging.enable is set to "true", then * a temporary file in org.codehaus.janino.source_debugging.dir or the * system's default temp dir is created in order to make the source code * available to a debugger. */ public Scanner(String optionalFileName, Reader in) throws CompileException, IOException { this( optionalFileName, // optionalFileName in, // in (short) 1, // initialLineNumber (short) 0 // initialColumnNumber ); } /** Creates a {@link Scanner} that counts lines and columns from non-default initial values. */ public Scanner( String optionalFileName, Reader in, short initialLineNumber, // "1" is a good idea short initialColumnNumber // "0" is a good idea ) throws CompileException, IOException { // Debugging on source code level is only possible if the code comes from // a "real" Java source file which the debugger can read. If this is not the // case, and we absolutely want source code level debugging, then we write // a verbatim copy of the source code into a temporary file in the system // temp directory. // This behavior is controlled by the two system properties // org.codehaus.janino.source_debugging.enable // org.codehaus.janino.source_debugging.dir // JANINO is designed to compile in memory to save the overhead of disk // I/O, so writing this file is only recommended for source code level // debugging purposes. if (optionalFileName == null && Boolean.getBoolean(ICookable.SYSTEM_PROPERTY_SOURCE_DEBUGGING_ENABLE)) { String dirName = System.getProperty(ICookable.SYSTEM_PROPERTY_SOURCE_DEBUGGING_DIR); File dir = dirName == null ? null : new File(dirName); File temporaryFile = File.createTempFile("janino", ".java", dir); temporaryFile.deleteOnExit(); in = new TeeReader( in, // in new FileWriter(temporaryFile), // out true // closeWriterOnEOF ); optionalFileName = temporaryFile.getAbsolutePath(); } this.optionalFileName = optionalFileName; this.in = new UnicodeUnescapeReader(in); this.nextCharLineNumber = initialLineNumber; this.nextCharColumnNumber = initialColumnNumber; this.readNextChar(); } /** @return The file name optionally passed to the constructor */ public String getFileName() { return this.optionalFileName; } /** * Closes the character source (file, {@link InputStream}, {@link Reader}) associated with this object. The results * of future calls to {@link #produce()} are undefined. * * @deprecated This method is deprecated, because the concept described above is confusing. An application should * close the underlying {@link InputStream} or {@link Reader} itself. */ @Deprecated public void close() throws IOException { this.in.close(); } /** * Get the text of the doc comment (a.k.a. "JAVADOC comment") preceeding * the next token. * @return null if the next token is not preceeded by a doc comment */ public String doc() { String s = this.docComment; this.docComment = null; return s; } /** @return The {@link Location} of the next character */ public Location location() { return new Location(this.optionalFileName, this.nextCharLineNumber, this.nextCharColumnNumber); } /** Representation of a Java™ token. */ public final class Token { private final String optionalFileName; private final short lineNumber; private final short columnNumber; private Location location; /** The type of this token; legal values are the various public constant declared in this class. */ public final int type; /** Indication of the 'end-of-input' condition. */ public static final int EOF = 0; /** The token represents an identifier. */ public static final int IDENTIFIER = 1; /** The token represents a keyword. */ public static final int KEYWORD = 2; /** * The token represents an integer literal; its {@link #value} is the text of the integer literal exactly as it * appears in the source code. */ public static final int INTEGER_LITERAL = 3; /** * The token represents a floating-point literal; its {@link #value} is the text of the floating-point literal * exactly as it appears in the source code. */ public static final int FLOATING_POINT_LITERAL = 4; /** The token represents a boolean literal; its {@link #value} is either 'true' or 'false'. */ public static final int BOOLEAN_LITERAL = 5; /** * The token represents a character literal; its {@link #value} is the text of the character literal exactly as * it appears in the source code (including the single quotes around it). */ public static final int CHARACTER_LITERAL = 6; /** * The token represents a string literal; its {@link #value} is the text of the string literal exactly as it * appears in the source code (including the double quotes around it). */ public static final int STRING_LITERAL = 7; /** The token represents the {@code null} literal; its {@link #value} is 'null'. */ public static final int NULL_LITERAL = 8; /** The token represents an operator; its {@link #value} is exactly the particular operator (e.g. "<<<="). */ public static final int OPERATOR = 9; /** The text of the token exactly as it appears in the source code. */ public final String value; private Token(int type, String value) { this.optionalFileName = Scanner.this.optionalFileName; this.lineNumber = Scanner.this.tokenLineNumber; this.columnNumber = Scanner.this.tokenColumnNumber; this.type = type; this.value = value; } /** @return The location of the first character of this token */ public Location getLocation() { if (this.location == null) { this.location = new Location(this.optionalFileName, this.lineNumber, this.columnNumber); } return this.location; } @Override public String toString() { return this.value; } } /** * Preduces and returns the next token. Notice that end-of-input is not signalized with a {@code null} * product, but by the special {@link Token#EOF} token. */ public Token produce() throws CompileException, IOException { if (this.docComment != null) { this.warning("MDC", "Misplaced doc comment", this.location()); this.docComment = null; } // Skip whitespace and process comments. int state = 0; StringBuilder dcsb = null; // For doc comment PROCESS_COMMENTS: for (;;) { switch (state) { case 0: // Outside any comment if (this.nextChar == -1) { return new Token(Token.EOF, "EOF"); } else if (Character.isWhitespace((char) this.nextChar)) { ; } else if (this.nextChar == '/') { state = 1; } else { break PROCESS_COMMENTS; } break; case 1: // After "/" if (this.nextChar == -1) { return new Token(Token.OPERATOR, "/"); } else if (this.nextChar == '=') { this.readNextChar(); return new Token(Token.OPERATOR, "/="); } else if (this.nextChar == '/') { state = 2; } else if (this.nextChar == '*') { state = 3; } else { return new Token(Token.OPERATOR, "/"); } break; case 2: // After "//..." if (this.nextChar == -1) { return new Token(Token.EOF, "EOF"); } else if (this.nextChar == '\r' || this.nextChar == '\n') { state = 0; } else { ; } break; case 3: // After "/*" if (this.nextChar == -1) { throw new CompileException("EOF in traditional comment", this.location()); } else if (this.nextChar == '*') { state = 4; } else { state = 9; } break; case 4: // After "/**" if (this.nextChar == -1) { throw new CompileException("EOF in doc comment", this.location()); } else if (this.nextChar == '/') { state = 0; } else { if (this.docComment != null) { this.warning( "MDC", "Multiple doc comments", new Location(this.optionalFileName, this.nextCharLineNumber, this.nextCharColumnNumber) ); } dcsb = new StringBuilder().append((char) this.nextChar); state = ( (this.nextChar == '\r' || this.nextChar == '\n') ? 6 : this.nextChar == '*' ? 8 : 5 ); } break; case 5: // After "/**..." if (this.nextChar == -1) { throw new CompileException("EOF in doc comment", this.location()); } else if (this.nextChar == '*') { state = 8; } else if (this.nextChar == '\r' || this.nextChar == '\n') { dcsb.append((char) this.nextChar); state = 6; } else { dcsb.append((char) this.nextChar); } break; case 6: // After "/**...\n" if (this.nextChar == -1) { throw new CompileException("EOF in doc comment", this.location()); } else if (this.nextChar == '*') { state = 7; } else if (this.nextChar == '\r' || this.nextChar == '\n') { dcsb.append((char) this.nextChar); } else if (this.nextChar == ' ' || this.nextChar == '\t') { ; } else { dcsb.append((char) this.nextChar); state = 5; } break; case 7: // After "/**...\n *" if (this.nextChar == -1) { throw new CompileException("EOF in doc comment", this.location()); } else if (this.nextChar == '*') { ; } else if (this.nextChar == '/') { this.docComment = dcsb.toString(); state = 0; } else { dcsb.append((char) this.nextChar); state = 5; } break; case 8: // After "/**...*" if (this.nextChar == -1) { throw new CompileException("EOF in doc comment", this.location()); } else if (this.nextChar == '/') { this.docComment = dcsb.toString(); state = 0; } else if (this.nextChar == '*') { dcsb.append('*'); } else { dcsb.append('*'); dcsb.append((char) this.nextChar); state = 5; } break; case 9: // After "/*..." if (this.nextChar == -1) { throw new CompileException("EOF in traditional comment", this.location()); } else if (this.nextChar == '*') { state = 10; } else { ; } break; case 10: // After "/*...*" if (this.nextChar == -1) { throw new CompileException("EOF in traditional comment", this.location()); } else if (this.nextChar == '/') { state = 0; } else if (this.nextChar == '*') { ; } else { state = 9; } break; default: throw new JaninoRuntimeException(Integer.toString(state)); } this.readNextChar(); } /* * Whitespace and comments are now skipped; "nextChar" is definitely * the first character of the token. */ this.tokenLineNumber = this.nextCharLineNumber; this.tokenColumnNumber = this.nextCharColumnNumber; // Scan identifier. if (Character.isJavaIdentifierStart((char) this.nextChar)) { StringBuilder sb = new StringBuilder(); sb.append((char) this.nextChar); for (;;) { this.readNextChar(); if (this.nextChar == -1 || !Character.isJavaIdentifierPart((char) this.nextChar)) break; sb.append((char) this.nextChar); } String s = sb.toString(); if ("true".equals(s)) return new Token(Token.BOOLEAN_LITERAL, "true"); if ("false".equals(s)) return new Token(Token.BOOLEAN_LITERAL, "false"); if ("null".equals(s)) return new Token(Token.NULL_LITERAL, "null"); { String v = (String) Scanner.JAVA_KEYWORDS.get(s); if (v != null) return new Token(Token.KEYWORD, v); } return new Token(Token.IDENTIFIER, s); } // Scan numeric literal. if (Character.isDigit((char) this.nextChar)) { return this.scanNumericLiteral(false); } // A "." is special: Could either be a floating-point constant like ".001", or the "." // operator. if (this.nextChar == '.') { this.readNextChar(); if (Character.isDigit((char) this.nextChar)) { return this.scanNumericLiteral(true); } else { return new Token(Token.OPERATOR, "."); } } // Scan string literal. if (this.nextChar == '"') { StringBuilder sb = new StringBuilder("\""); this.readNextChar(); while (this.nextChar != '"') { this.scanLiteralCharacter(sb); } this.readNextChar(); return new Token(Token.STRING_LITERAL, sb.append('"').toString()); } // Scan character literal. if (this.nextChar == '\'') { this.readNextChar(); if (this.nextChar == '\'') { throw new CompileException( "Single quote must be backslash-escaped in character literal", this.location() ); } StringBuilder sb = new StringBuilder("'"); this.scanLiteralCharacter(sb); if (this.nextChar != '\'') throw new CompileException("Closing single quote missing", this.location()); this.readNextChar(); return new Token(Token.CHARACTER_LITERAL, sb.append('\'').toString()); } // Scan separator / operator. { String v = (String) Scanner.JAVA_OPERATORS.get(new String(new char[] { (char) this.nextChar })); if (v != null) { for (;;) { this.readNextChar(); String v2 = (String) ( this.expectGreater ? Scanner.JAVA_OPERATORS_EXPECT_GREATER : Scanner.JAVA_OPERATORS ).get(v + (char) this.nextChar); if (v2 == null) return new Token(Token.OPERATOR, v); v = v2; } } } throw new CompileException( "Invalid character input \"" + (char) this.nextChar + "\" (character code " + this.nextChar + ")", this.location() ); } /** @return Whether the scanner is currently in 'expect greater' mode */ public boolean getExpectGreater() { return this.expectGreater; } /** * Sets or resets the 'expect greater' mode. * * @return Whether the 'expect greater' mode was previously active */ public boolean setExpectGreater(boolean value) { boolean tmp = this.expectGreater; this.expectGreater = value; return tmp; } private Token scanNumericLiteral(boolean hadDecimalPoint) throws CompileException, IOException { StringBuilder sb = hadDecimalPoint ? new StringBuilder(".") : new StringBuilder(); int state = hadDecimalPoint ? 2 : 0; for (;;) { switch (state) { case 0: // First character. if (this.nextChar == '0') { sb.append('0'); state = 6; } else if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); state = 1; } else { throw new CompileException( "Numeric literal begins with invalid character '" + (char) this.nextChar + "'", this.location() ); } break; case 1: // Decimal digits. if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); } else if (this.nextChar == 'l' || this.nextChar == 'L') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.INTEGER_LITERAL, sb.toString()); } else if (this.nextChar == 'f' || this.nextChar == 'F' || this.nextChar == 'd' || this.nextChar == 'D') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } else if (this.nextChar == '.') { sb.append('.'); state = 2; } else if (this.nextChar == 'E' || this.nextChar == 'e') { sb.append('E'); state = 3; } else { return new Token(Token.INTEGER_LITERAL, sb.toString()); } break; case 2: // After decimal point. if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); } else if (this.nextChar == 'e' || this.nextChar == 'E') { sb.append('E'); state = 3; } else if (this.nextChar == 'f' || this.nextChar == 'F' || this.nextChar == 'd' || this.nextChar == 'D') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } else { return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } break; case 3: // Read exponent. if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); state = 5; } else if (this.nextChar == '-' || this.nextChar == '+') { sb.append((char) this.nextChar); state = 4; } else { throw new CompileException("Exponent missing after \"E\"", this.location()); } break; case 4: // After exponent sign. if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); state = 5; } else { throw new CompileException("Exponent missing after 'E' and sign", this.location()); } break; case 5: // After first exponent digit. if (Character.isDigit((char) this.nextChar)) { sb.append((char) this.nextChar); } else if (this.nextChar == 'f' || this.nextChar == 'F' || this.nextChar == 'd' || this.nextChar == 'D') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } else { return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } break; case 6: // After leading zero if ("01234567".indexOf(this.nextChar) != -1) { sb.append((char) this.nextChar); state = 7; } else if (this.nextChar == 'l' || this.nextChar == 'L') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.INTEGER_LITERAL, sb.toString()); } else if (this.nextChar == 'f' || this.nextChar == 'F' || this.nextChar == 'd' || this.nextChar == 'D') { sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.FLOATING_POINT_LITERAL, sb.toString()); } else if (this.nextChar == '.') { sb.append('.'); state = 2; } else if (this.nextChar == 'E' || this.nextChar == 'e') { sb.append((char) this.nextChar); state = 3; } else if (this.nextChar == 'x' || this.nextChar == 'X') { sb.append((char) this.nextChar); state = 8; } else { return new Token(Token.INTEGER_LITERAL, "0"); } break; case 7: // In octal literal. if ("01234567".indexOf(this.nextChar) != -1) { sb.append((char) this.nextChar); } else if (this.nextChar == '8' || this.nextChar == '9') { throw new CompileException( "Digit '" + (char) this.nextChar + "' not allowed in octal literal", this.location() ); } else if (this.nextChar == 'l' || this.nextChar == 'L') { // Octal long literal. sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.INTEGER_LITERAL, sb.toString()); } else { // Octal int literal return new Token(Token.INTEGER_LITERAL, sb.toString()); } break; case 8: // After '0x'. if (Character.digit((char) this.nextChar, 16) != -1) { sb.append((char) this.nextChar); state = 9; } else { throw new CompileException("Hex digit expected after \"0x\"", this.location()); } break; case 9: // After first hex digit. if (Character.digit((char) this.nextChar, 16) != -1) { sb.append((char) this.nextChar); } else if (this.nextChar == 'l' || this.nextChar == 'L') { // Hex long literal sb.append((char) this.nextChar); this.readNextChar(); return new Token(Token.INTEGER_LITERAL, sb.toString()); } else { // Hex int literal return new Token(Token.INTEGER_LITERAL, sb.toString()); } break; default: throw new JaninoRuntimeException(Integer.toString(state)); } this.readNextChar(); } } /** Scans the next literal character into a {@link StringBuilder}. */ private void scanLiteralCharacter(StringBuilder sb) throws CompileException, IOException { if (this.nextChar == -1) throw new CompileException("EOF in literal", this.location()); if (this.nextChar == '\r' || this.nextChar == '\n') { throw new CompileException("Line break in literal not allowed", this.location()); } if (this.nextChar != '\\') { // Not an escape sequence. sb.append((char) this.nextChar); this.readNextChar(); return; } // JLS7 3.10.6: Escape sequences for character and string literals. sb.append('\\'); this.readNextChar(); { int idx = "btnfr\"'\\".indexOf(this.nextChar); if (idx != -1) { // "\t" and friends. sb.append((char) this.nextChar); this.readNextChar(); return; } } { int idx = "01234567".indexOf(this.nextChar); if (idx != -1) { // Octal escapes: "\0" through "\3ff". char firstChar = (char) this.nextChar; sb.append(firstChar); this.readNextChar(); idx = "01234567".indexOf(this.nextChar); if (idx == -1) return; sb.append((char) this.nextChar); this.readNextChar(); idx = "01234567".indexOf(this.nextChar); if (idx == -1) return; if ("0123".indexOf(firstChar) == -1) { throw new CompileException("Invalid octal escape", this.location()); } sb.append((char) this.nextChar); this.readNextChar(); return; } } throw new CompileException("Invalid escape sequence", this.location()); } // Read one character and store in "nextChar". private void readNextChar() throws IOException, CompileException { try { this.nextChar = this.in.read(); } catch (UnicodeUnescapeException ex) { throw new CompileException(ex.getMessage(), this.location(), ex); } if (this.nextChar == '\r') { ++this.nextCharLineNumber; this.nextCharColumnNumber = 0; this.crLfPending = true; } else if (this.nextChar == '\n') { if (this.crLfPending) { this.crLfPending = false; } else { ++this.nextCharLineNumber; this.nextCharColumnNumber = 0; } } else { ++this.nextCharColumnNumber; } //System.out.println("'" + (char) nextChar + "' = " + (int) nextChar); } private final String optionalFileName; private final Reader in; private int nextChar = -1; // Always valid (one character read-ahead). private boolean crLfPending; private short nextCharLineNumber; private short nextCharColumnNumber; /** Line number of the previously produced token (typically starting at one). */ private short tokenLineNumber; /** * Column number of the first character of the previously produced token (1 if token is immediately preceded by a * line break). */ private short tokenColumnNumber; /** The optional JAVADOC comment preceding the {@link #nextToken}. */ private String docComment; /** * Whether the scanner is in 'expect greater' mode: If so, it parses character sequences like ">>>=" as * ">", ">", ">", "=". */ private boolean expectGreater; private static final Map JAVA_KEYWORDS = new HashMap(); static { String[] ks = { "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while", }; for (int i = 0; i < ks.length; ++i) Scanner.JAVA_KEYWORDS.put(ks[i], ks[i]); } private static final Map JAVA_OPERATORS = new HashMap(); private static final Map JAVA_OPERATORS_EXPECT_GREATER = new HashMap(); static { String[] ops = { // Separators: "(", ")", "{", "}", "[", "]", ";", ",", ".", "@", // Operators: "=", ">", "<", "!", "~", "?", ":", "==", "<=", ">=", "!=", "&&", "||", "++", "--", "+", "-", "*", "/", "&", "|", "^", "%", "<<", ">>", ">>>", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=", }; for (String op : ops) { Scanner.JAVA_OPERATORS.put(op, op); if (!op.startsWith(">>")) Scanner.JAVA_OPERATORS_EXPECT_GREATER.put(op, op); } } /** * By default, warnings are discarded, but an application my install a * {@link WarningHandler}. *

* Notice that there is no Scanner.setErrorHandler() method, but scan errors * always throw a {@link CompileException}. The reason being is that there is no reasonable * way to recover from scan errors and continue scanning, so there is no need to install * a custom scan 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 */ private void warning(String handle, String message, Location optionalLocation) throws CompileException { if (this.optionalWarningHandler != null) { this.optionalWarningHandler.handleWarning(handle, message, optionalLocation); } } }