--- a/jdk/src/share/classes/javax/management/QueryParser.java Wed Oct 21 15:47:09 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,626 +0,0 @@
-/*
- * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package javax.management;
-
-import java.util.ArrayList;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * <p>Parser for JMX queries represented in an SQL-like language.</p>
- */
-/*
- * Note that if a query starts with ( then we don't know whether it is
- * a predicate or just a value that is parenthesized. So, inefficiently,
- * we try to parse a predicate and if that doesn't work we try to parse
- * a value.
- */
-class QueryParser {
- // LEXER STARTS HERE
-
- private static class Token {
- final String string;
- Token(String s) {
- this.string = s;
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-
- private static final Token
- END = new Token("<end of string>"),
- LPAR = new Token("("), RPAR = new Token(")"),
- COMMA = new Token(","), DOT = new Token("."), SHARP = new Token("#"),
- PLUS = new Token("+"), MINUS = new Token("-"),
- TIMES = new Token("*"), DIVIDE = new Token("/"),
- LT = new Token("<"), GT = new Token(">"),
- LE = new Token("<="), GE = new Token(">="),
- NE = new Token("<>"), EQ = new Token("="),
- NOT = new Id("NOT"), INSTANCEOF = new Id("INSTANCEOF"),
- FALSE = new Id("FALSE"), TRUE = new Id("TRUE"),
- BETWEEN = new Id("BETWEEN"), AND = new Id("AND"),
- OR = new Id("OR"), IN = new Id("IN"),
- LIKE = new Id("LIKE"), CLASS = new Id("CLASS");
-
- // Keywords that can appear where an identifier can appear.
- // If an attribute is one of these, then it must be quoted when
- // converting a query into a string.
- // We use a TreeSet so we can look up case-insensitively.
- private static final Set<String> idKeywords =
- new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
- static {
- for (Token t : new Token[] {NOT, INSTANCEOF, FALSE, TRUE, LIKE, CLASS})
- idKeywords.add(t.string);
- };
-
- public static String quoteId(String id) {
- if (id.contains("\"") || idKeywords.contains(id))
- return '"' + id.replace("\"", "\"\"") + '"';
- else
- return id;
- }
-
- private static class Id extends Token {
- Id(String id) {
- super(id);
- }
-
- // All other tokens use object identity, which means e.g. that one
- // occurrence of the string constant 'x' is not the same as another.
- // For identifiers, we ignore case when testing for equality so that
- // for a keyword such as AND you can also spell it as "And" or "and".
- // But we keep the original case of the identifier, so if it's not
- // a keyword we will distinguish between the attribute Foo and the
- // attribute FOO.
- @Override
- public boolean equals(Object o) {
- return (o instanceof Id && (((Id) o).toString().equalsIgnoreCase(toString())));
- }
- }
-
- private static class QuotedId extends Token {
- QuotedId(String id) {
- super(id);
- }
-
- @Override
- public String toString() {
- return '"' + string.replace("\"", "\"\"") + '"';
- }
- }
-
- private static class StringLit extends Token {
- StringLit(String s) {
- super(s);
- }
-
- @Override
- public String toString() {
- return '\'' + string.replace("'", "''") + '\'';
- }
- }
-
- private static class LongLit extends Token {
- long number;
-
- LongLit(long number) {
- super(Long.toString(number));
- this.number = number;
- }
- }
-
- private static class DoubleLit extends Token {
- double number;
-
- DoubleLit(double number) {
- super(Double.toString(number));
- this.number = number;
- }
- }
-
- private static class Tokenizer {
- private final String s;
- private final int len;
- private int i = 0;
-
- Tokenizer(String s) {
- this.s = s;
- this.len = s.length();
- }
-
- private int thisChar() {
- if (i == len)
- return -1;
- return s.codePointAt(i);
- }
-
- private void advance() {
- i += Character.charCount(thisChar());
- }
-
- private int thisCharAdvance() {
- int c = thisChar();
- advance();
- return c;
- }
-
- Token nextToken() {
- // In this method, c is the character we're looking at, and
- // thisChar() is the character after that. Everything must
- // preserve these invariants. When we return we then have
- // thisChar() being the start of the following token, so
- // the next call to nextToken() will begin from there.
- int c;
-
- // Skip space
- do {
- if (i == len)
- return null;
- c = thisCharAdvance();
- } while (Character.isWhitespace(c));
-
- // Now c is the first character of the token, and tokenI points
- // to the character after that.
- switch (c) {
- case '(': return LPAR;
- case ')': return RPAR;
- case ',': return COMMA;
- case '.': return DOT;
- case '#': return SHARP;
- case '*': return TIMES;
- case '/': return DIVIDE;
- case '=': return EQ;
- case '-': return MINUS;
- case '+': return PLUS;
-
- case '>':
- if (thisChar() == '=') {
- advance();
- return GE;
- } else
- return GT;
-
- case '<':
- c = thisChar();
- switch (c) {
- case '=': advance(); return LE;
- case '>': advance(); return NE;
- default: return LT;
- }
-
- case '!':
- if (thisCharAdvance() != '=')
- throw new IllegalArgumentException("'!' must be followed by '='");
- return NE;
-
- case '"':
- case '\'': {
- int quote = c;
- StringBuilder sb = new StringBuilder();
- while (true) {
- while ((c = thisChar()) != quote) {
- if (c < 0) {
- throw new IllegalArgumentException(
- "Unterminated string constant");
- }
- sb.appendCodePoint(thisCharAdvance());
- }
- advance();
- if (thisChar() == quote) {
- sb.appendCodePoint(quote);
- advance();
- } else
- break;
- }
- if (quote == '\'')
- return new StringLit(sb.toString());
- else
- return new QuotedId(sb.toString());
- }
- }
-
- // Is it a numeric constant?
- if (Character.isDigit(c) || c == '.') {
- StringBuilder sb = new StringBuilder();
- int lastc = -1;
- while (true) {
- sb.appendCodePoint(c);
- c = Character.toLowerCase(thisChar());
- if (c == '+' || c == '-') {
- if (lastc != 'e')
- break;
- } else if (!Character.isDigit(c) && c != '.' && c != 'e')
- break;
- lastc = c;
- advance();
- }
- String s = sb.toString();
- if (s.indexOf('.') >= 0 || s.indexOf('e') >= 0) {
- double d = parseDoubleCheckOverflow(s);
- return new DoubleLit(d);
- } else {
- // Like the Java language, we allow the numeric constant
- // x where -x = Long.MIN_VALUE, even though x is not
- // representable as a long (it is Long.MAX_VALUE + 1).
- // Code in the parser will reject this value if it is
- // not the operand of unary minus.
- long l = -Long.parseLong("-" + s);
- return new LongLit(l);
- }
- }
-
- // It must be an identifier.
- if (!Character.isJavaIdentifierStart(c)) {
- StringBuilder sb = new StringBuilder();
- Formatter f = new Formatter(sb);
- f.format("Bad character: %c (%04x)", c, c);
- throw new IllegalArgumentException(sb.toString());
- }
-
- StringBuilder id = new StringBuilder();
- while (true) { // identifier
- id.appendCodePoint(c);
- c = thisChar();
- if (!Character.isJavaIdentifierPart(c))
- break;
- advance();
- }
-
- return new Id(id.toString());
- }
- }
-
- /* Parse a double as a Java compiler would do it, throwing an exception
- * if the input does not fit in a double. We assume that the input
- * string is not "Infinity" and does not have a leading sign.
- */
- private static double parseDoubleCheckOverflow(String s) {
- double d = Double.parseDouble(s);
- if (Double.isInfinite(d))
- throw new NumberFormatException("Overflow: " + s);
- if (d == 0.0) { // Underflow checking is hard! CR 6604864
- String ss = s;
- int e = s.indexOf('e'); // we already forced E to lowercase
- if (e > 0)
- ss = s.substring(0, e);
- ss = ss.replace("0", "").replace(".", "");
- if (!ss.equals(""))
- throw new NumberFormatException("Underflow: " + s);
- }
- return d;
- }
-
- // PARSER STARTS HERE
-
- private final List<Token> tokens;
- private int tokenI;
- // The current token is always tokens[tokenI].
-
- QueryParser(String s) {
- // Construct the complete list of tokens immediately and append
- // a sentinel (END).
- tokens = new ArrayList<Token>();
- Tokenizer tokenizer = new Tokenizer(s);
- Token t;
- while ((t = tokenizer.nextToken()) != null)
- tokens.add(t);
- tokens.add(END);
- }
-
- private Token current() {
- return tokens.get(tokenI);
- }
-
- // If the current token is t, then skip it and return true.
- // Otherwise, return false.
- private boolean skip(Token t) {
- if (t.equals(current())) {
- tokenI++;
- return true;
- }
- return false;
- }
-
- // If the current token is one of the ones in 'tokens', then skip it
- // and return its index in 'tokens'. Otherwise, return -1.
- private int skipOne(Token... tokens) {
- for (int i = 0; i < tokens.length; i++) {
- if (skip(tokens[i]))
- return i;
- }
- return -1;
- }
-
- // If the current token is t, then skip it and return.
- // Otherwise throw an exception.
- private void expect(Token t) {
- if (!skip(t))
- throw new IllegalArgumentException("Expected " + t + ", found " + current());
- }
-
- private void next() {
- tokenI++;
- }
-
- QueryExp parseQuery() {
- QueryExp qe = query();
- if (current() != END)
- throw new IllegalArgumentException("Junk at end of query: " + current());
- return qe;
- }
-
- // The remainder of this class is a classical recursive-descent parser.
- // We only need to violate the recursive-descent scheme in one place,
- // where parentheses make the grammar not LL(1).
-
- private QueryExp query() {
- QueryExp lhs = andquery();
- while (skip(OR))
- lhs = Query.or(lhs, andquery());
- return lhs;
- }
-
- private QueryExp andquery() {
- QueryExp lhs = predicate();
- while (skip(AND))
- lhs = Query.and(lhs, predicate());
- return lhs;
- }
-
- private QueryExp predicate() {
- // Grammar hack. If we see a paren, it might be (query) or
- // it might be (value). We try to parse (query), and if that
- // fails, we parse (value). For example, if the string is
- // "(2+3)*4 < 5" then we will try to parse the query
- // "2+3)*4 < 5", which will fail at the ), so we'll back up to
- // the paren and let value() handle it.
- if (skip(LPAR)) {
- int parenIndex = tokenI - 1;
- try {
- QueryExp qe = query();
- expect(RPAR);
- return qe;
- } catch (IllegalArgumentException e) {
- // OK: try parsing a value
- }
- tokenI = parenIndex;
- }
-
- if (skip(NOT))
- return Query.not(predicate());
-
- if (skip(INSTANCEOF))
- return Query.isInstanceOf(stringvalue());
-
- if (skip(LIKE)) {
- StringValueExp sve = stringvalue();
- String s = sve.getValue();
- try {
- return new ObjectName(s);
- } catch (MalformedObjectNameException e) {
- throw new IllegalArgumentException(
- "Bad ObjectName pattern after LIKE: '" + s + "'", e);
- }
- }
-
- ValueExp lhs = value();
-
- return predrhs(lhs);
- }
-
- // The order of elements in the following arrays is important. The code
- // in predrhs depends on integer indexes. Change with caution.
- private static final Token[] relations = {
- EQ, LT, GT, LE, GE, NE,
- // 0, 1, 2, 3, 4, 5,
- };
- private static final Token[] betweenLikeIn = {
- BETWEEN, LIKE, IN
- // 0, 1, 2,
- };
-
- private QueryExp predrhs(ValueExp lhs) {
- Token start = current(); // for errors
-
- // Look for < > = etc
- int i = skipOne(relations);
- if (i >= 0) {
- ValueExp rhs = value();
- switch (i) {
- case 0: return Query.eq(lhs, rhs);
- case 1: return Query.lt(lhs, rhs);
- case 2: return Query.gt(lhs, rhs);
- case 3: return Query.leq(lhs, rhs);
- case 4: return Query.geq(lhs, rhs);
- case 5: return Query.not(Query.eq(lhs, rhs));
- // There is no Query.ne so <> is shorthand for the above.
- default:
- throw new AssertionError();
- }
- }
-
- // Must be BETWEEN LIKE or IN, optionally preceded by NOT
- boolean not = skip(NOT);
- i = skipOne(betweenLikeIn);
- if (i < 0)
- throw new IllegalArgumentException("Expected relation at " + start);
-
- QueryExp q;
- switch (i) {
- case 0: { // BETWEEN
- ValueExp lower = value();
- expect(AND);
- ValueExp upper = value();
- q = Query.between(lhs, lower, upper);
- break;
- }
-
- case 1: { // LIKE
- if (!(lhs instanceof AttributeValueExp)) {
- throw new IllegalArgumentException(
- "Left-hand side of LIKE must be an attribute");
- }
- AttributeValueExp alhs = (AttributeValueExp) lhs;
- StringValueExp sve = stringvalue();
- q = Query.match(alhs, sve);
- break;
- }
-
- case 2: { // IN
- expect(LPAR);
- List<ValueExp> values = new ArrayList<ValueExp>();
- values.add(value());
- while (skip(COMMA))
- values.add(value());
- expect(RPAR);
- q = Query.in(lhs, values.toArray(new ValueExp[values.size()]));
- break;
- }
-
- default:
- throw new AssertionError();
- }
-
- if (not)
- q = Query.not(q);
-
- return q;
- }
-
- private ValueExp value() {
- ValueExp lhs = factor();
- int i;
- while ((i = skipOne(PLUS, MINUS)) >= 0) {
- ValueExp rhs = factor();
- if (i == 0)
- lhs = Query.plus(lhs, rhs);
- else
- lhs = Query.minus(lhs, rhs);
- }
- return lhs;
- }
-
- private ValueExp factor() {
- ValueExp lhs = term();
- int i;
- while ((i = skipOne(TIMES, DIVIDE)) >= 0) {
- ValueExp rhs = term();
- if (i == 0)
- lhs = Query.times(lhs, rhs);
- else
- lhs = Query.div(lhs, rhs);
- }
- return lhs;
- }
-
- private ValueExp term() {
- boolean signed = false;
- int sign = +1;
- if (skip(PLUS))
- signed = true;
- else if (skip(MINUS)) {
- signed = true; sign = -1;
- }
-
- Token t = current();
- next();
-
- if (t instanceof DoubleLit)
- return Query.value(sign * ((DoubleLit) t).number);
- if (t instanceof LongLit) {
- long n = ((LongLit) t).number;
- if (n == Long.MIN_VALUE && sign != -1)
- throw new IllegalArgumentException("Illegal positive integer: " + n);
- return Query.value(sign * n);
- }
- if (signed)
- throw new IllegalArgumentException("Expected number after + or -");
-
- if (t == LPAR) {
- ValueExp v = value();
- expect(RPAR);
- return v;
- }
- if (t.equals(FALSE) || t.equals(TRUE)) {
- return Query.value(t.equals(TRUE));
- }
- if (t.equals(CLASS))
- return Query.classattr();
-
- if (t instanceof StringLit)
- return Query.value(t.string); // Not toString(), which would requote '
-
- // At this point, all that remains is something that will call Query.attr
-
- if (!(t instanceof Id) && !(t instanceof QuotedId))
- throw new IllegalArgumentException("Unexpected token " + t);
-
- String name1 = name(t);
-
- if (skip(SHARP)) {
- Token t2 = current();
- next();
- String name2 = name(t2);
- return Query.attr(name1, name2);
- }
- return Query.attr(name1);
- }
-
- // Initially, t is the first token of a supposed name and current()
- // is the second.
- private String name(Token t) {
- StringBuilder sb = new StringBuilder();
- while (true) {
- if (!(t instanceof Id) && !(t instanceof QuotedId))
- throw new IllegalArgumentException("Unexpected token " + t);
- sb.append(t.string);
- if (current() != DOT)
- break;
- sb.append('.');
- next();
- t = current();
- next();
- }
- return sb.toString();
- }
-
- private StringValueExp stringvalue() {
- // Currently the only way to get a StringValueExp when constructing
- // a QueryExp is via Query.value(String), so we only recognize
- // string literals here. But if we expand queries in the future
- // that might no longer be true.
- Token t = current();
- next();
- if (!(t instanceof StringLit))
- throw new IllegalArgumentException("Expected string: " + t);
- return Query.value(t.string);
- }
-}