, QueryExp {
/**
* A structure recording property structure and
@@ -1779,10 +1780,16 @@
*
* @return a string representation of this object name.
*/
+ @Override
public String toString() {
return getSerializedNameString();
}
+ @Override
+ String toQueryString() {
+ return "LIKE " + Query.value(toString());
+ }
+
/**
* Compares the current object name with another object name. Two
* ObjectName instances are equal if and only if their canonical
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/OrQueryExp.java
--- a/jdk/src/share/classes/javax/management/OrQueryExp.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/OrQueryExp.java Mon Mar 03 10:32:38 2008 +0100
@@ -98,9 +98,29 @@
}
/**
- * Returns a string representation of this AndQueryExp
+ * Returns a string representation of this OrQueryExp
*/
public String toString() {
return "(" + exp1 + ") or (" + exp2 + ")";
}
+
+ @Override
+ String toQueryString() {
+ return parens(exp1) + " or " + parens(exp2);
+ }
+
+ // Add parentheses to avoid possible confusion. If we have an expression
+ // such as Query.or(Query.and(a, b), c), then we return
+ // (a and b) or c
+ // rather than just
+ // a and b or c
+ // In fact the precedence rules are such that the parentheses are not
+ // strictly necessary, but omitting them would be confusing.
+ private static String parens(QueryExp exp) {
+ String s = Query.toString(exp);
+ if (exp instanceof AndQueryExp)
+ return "(" + s + ")";
+ else
+ return s;
+ }
}
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/QualifiedAttributeValueExp.java
--- a/jdk/src/share/classes/javax/management/QualifiedAttributeValueExp.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/QualifiedAttributeValueExp.java Mon Mar 03 10:32:38 2008 +0100
@@ -27,9 +27,11 @@
/**
- * This class represents indexed attributes used as arguments to relational
- * constraints. An QualifiedAttributeValueExp may be used anywhere a
- * ValueExp is required.
+ * Represents attributes used as arguments to relational constraints,
+ * where the attribute must be in an MBean of a specified {@linkplain
+ * MBeanInfo#getClassName() class}. A QualifiedAttributeValueExp may be used
+ * anywhere a ValueExp is required.
+ *
* @serial include
*
* @since 1.5
@@ -48,7 +50,9 @@
/**
* Basic Constructor.
+ * @deprecated see {@link AttributeValueExp#AttributeValueExp()}
*/
+ @Deprecated
public QualifiedAttributeValueExp() {
}
@@ -81,6 +85,7 @@
* @exception BadAttributeValueExpException
* @exception InvalidApplicationException
*/
+ @Override
public ValueExp apply(ObjectName name) throws BadStringOperationException, BadBinaryOpValueExpException,
BadAttributeValueExpException, InvalidApplicationException {
try {
@@ -105,9 +110,11 @@
/**
* Returns the string representing its value
*/
+ @Override
public String toString() {
if (className != null) {
- return className + "." + super.toString();
+ return QueryParser.quoteId(className) + "#" +
+ QueryParser.quoteId(super.toString());
} else {
return super.toString();
}
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/Query.java
--- a/jdk/src/share/classes/javax/management/Query.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/Query.java Mon Mar 03 10:32:38 2008 +0100
@@ -27,19 +27,346 @@
/**
- *
Constructs query object constraints. The static methods provided
- * return query expressions that may be used in listing and
- * enumerating MBeans. Individual constraint construction methods
- * allow only appropriate types as arguments. Composition of calls can
- * construct arbitrary nestings of constraints, as the following
- * example illustrates:
+ * Constructs query object constraints.
+ *
+ * The MBean Server can be queried for MBeans that meet a particular
+ * condition, using its {@link MBeanServer#queryNames queryNames} or
+ * {@link MBeanServer#queryMBeans queryMBeans} method. The {@link QueryExp}
+ * parameter to the method can be any implementation of the interface
+ * {@code QueryExp}, but it is usually best to obtain the {@code QueryExp}
+ * value by calling the static methods in this class. This is particularly
+ * true when querying a remote MBean Server: a custom implementation of the
+ * {@code QueryExp} interface might not be present in the remote MBean Server,
+ * but the methods in this class return only standard classes that are
+ * part of the JMX implementation.
+ *
+ * There are two ways to create {@code QueryExp} objects using the methods
+ * in this class. The first is to build them by chaining together calls to
+ * the various methods. The second is to use the Query Language described
+ * below and produce the {@code QueryExp} by calling
+ * {@link #fromString Query.fromString}. The two ways are equivalent:
+ * every {@code QueryExp} returned by {@code fromString} can also be
+ * constructed by chaining method calls.
+ *
+ * As an example, suppose you wanted to find all MBeans where the {@code
+ * Enabled} attribute is {@code true} and the {@code Owner} attribute is {@code
+ * "Duke"}. Here is how you could construct the appropriate {@code QueryExp} by
+ * chaining together method calls:
+ *
+ *
+ * QueryExp query =
+ * Query.and(Query.eq(Query.attr("Enabled"), Query.value(true)),
+ * Query.eq(Query.attr("Owner"), Query.value("Duke")));
+ *
+ *
+ * Here is how you could construct the same {@code QueryExp} using the
+ * Query Language:
*
*
- * QueryExp exp = Query.and(Query.gt(Query.attr("age"),Query.value(5)),
- * Query.match(Query.attr("name"),
- * Query.value("Smith")));
+ * QueryExp query = Query.fromString("Enabled = true and Owner = 'Duke'");
*
*
+ * The principal advantage of the method-chaining approach is that the
+ * compiler will check that the query makes sense. The principal advantage
+ * of the Query Language approach is that it is easier to write and especially
+ * read.
+ *
+ *
+ * Query Language
+ *
+ * The query language is closely modeled on the WHERE clause of
+ * SQL SELECT statements. The formal specification of the language
+ * appears below, but it is probably easier to
+ * understand it with examples such as the following.
+ *
+ *
+ * - {@code Message = 'OK'}
+ *
- Selects MBeans that have a {@code Message} attribute whose value
+ * is the string {@code OK}.
+ *
+ *
- {@code FreeSpacePercent < 10}
+ *
- Selects MBeans that have a {@code FreeSpacePercent} attribute whose
+ * value is a number less than 10.
+ *
+ *
- {@code FreeSpacePercent < 10 and WarningSent = false}
+ *
- Selects the same MBeans as the previous example, but they must
+ * also have a boolean attribute {@code WarningSent} whose value
+ * is false.
+ *
+ *
- {@code SpaceUsed > TotalSpace * (2.0 / 3.0)}
+ *
- Selects MBeans that have {@code SpaceUsed} and {@code TotalSpace}
+ * attributes where the first is more than two-thirds the second.
+ *
+ *
- {@code not (FreeSpacePercent between 10 and 90)}
+ *
- Selects MBeans that have a {@code FreeSpacePercent} attribute whose
+ * value is not between 10 and 90, inclusive.
+ *
+ *
- {@code FreeSpacePercent not between 10 and 90}
+ *
- Another way of writing the previous query.
+ *
+ *
- {@code Status in ('STOPPED', 'STARTING', 'STARTED')}
+ *
- Selects MBeans that have a {@code Status} attribute whose value
+ * is one of those three strings.
+ *
+ *
- {@code Message like 'OK: %'}
+ *
- Selects MBeans that have a {@code Message} attribute whose value
+ * is a string beginning with {@code "OK: "}. Notice that the
+ * wildcard characters are SQL's ones. In the query language,
+ * {@code %} means "any sequence of characters" and {@code _}
+ * means "any single character". In the rest of the JMX API, these
+ * correspond to {@code *} and {@code %} respectively.
+ *
+ *
- {@code instanceof 'javax.management.NotificationBroadcaster'}
+ *
- Selects MBeans that are instances of
+ * {@link javax.management.NotificationBroadcaster}, as reported by
+ * {@link javax.management.MBeanServer#isInstanceOf MBeanServer.isInstanceOf}.
+ *
+ *
- {@code like 'mydomain:*'}
+ *
- Selects MBeans whose {@link ObjectName}s have the domain {@code mydomain}.
+ *
+ *
+ *
+ * The last two examples do not correspond to valid SQL syntax, but all
+ * the others do.
+ *
+ * The remainder of this description is a formal specification of the
+ * query language.
+ *
+ *
+ *
+ *
+ * Keywords such as and, like, and between are not
+ * case sensitive. You can write between, BETWEEN, or
+ * BeTwEeN with the same effect.
+ *
+ * On the other hand, attribute names are case sensitive. The
+ * attribute {@code Name} is not the same as the attribute {@code name}.
+ *
+ * To access an attribute whose name, ignoring case, is the same as one of
+ * the keywords {@code not}, {@code instanceof}, {@code like}, {@code true},
+ * or {@code false}, you can use double quotes, for example {@code "not"}.
+ * Double quotes can also be used to include non-identifier characters in
+ * the name of an attribute, for example {@code "attribute-name-with-hyphens"}.
+ * To include the double quote character in the attribute name, write it
+ * twice. {@code "foo""bar""baz"} represents the attribute called
+ * {@code foo"bar"baz}.
+ *
+ *
String constants are written with single quotes like {@code 'this'}. A
+ * single quote within a string constant must be doubled, for example
+ * {@code 'can''t'}.
+ *
+ * Integer constants are written as a sequence of decimal digits,
+ * optionally preceded by a plus or minus sign. An integer constant must be
+ * a valid input to {@link Long#valueOf(String)}.
+ *
+ * Floating-point constants are written using the Java syntax. A
+ * floating-point constant must be a valid input to
+ * {@link Double#valueOf(String)}.
+ *
+ * A boolean constant is either {@code true} or {@code false}, ignoring
+ * case.
+ *
+ * Spaces cannot appear inside identifiers (unless written with double
+ * quotes) or keywords or multi-character tokens such as {@code <=}. Spaces can
+ * appear anywhere else, but are not required except to separate tokens. For
+ * example, the query {@code a < b and 5 = c} could also be written {@code a
+ *
+ *
+ * Grammar
+ *
+ *
+ * - query:
+ *
- andquery [OR query]
+ *
+ *
- andquery:
+ *
- predicate [AND andquery]
+ *
+ *
- predicate:
+ *
- ( query ) |
+ * NOT predicate |
+ * INSTANCEOF stringvalue |
+ * LIKE objectnamepattern |
+ * value predrhs
+ *
+ * - predrhs:
+ *
- compare value |
+ * [NOT] BETWEEN value AND
+ * value |
+ * [NOT] IN ( value
+ * commavalues ) |
+ * [NOT] LIKE stringvalue
+ *
+ * - commavalues:
+ *
- [ , value commavalues ]
+ *
+ *
- compare:
+ *
- = | < | > |
+ * <= | >= | <> | !=
+ *
+ *
- value:
+ *
- factor [plusorminus
+ * value]
+ *
+ *
- plusorminus:
+ *
- + | -
+ *
+ *
- factor:
+ *
- term [timesordivide
+ * factor]
+ *
+ *
- timesordivide:
+ *
- * | /
+ *
+ *
- term:
+ *
- attr | literal |
+ * ( value )
+ *
+ *
- attr:
+ *
- name [# name]
+ *
+ *
- name:
+ *
- identifier [.name]
+ *
+ *
- identifier:
+ *
- Java-identifier | double-quoted-identifier
+ *
+ *
- literal:
+ *
- booleanlit | longlit |
+ * doublelit | stringlit
+ *
+ *
- booleanlit:
+ *
- FALSE | TRUE
+ *
+ *
- stringvalue:
+ *
- stringlit
+ *
+ *
- objectnamepattern:
+ *
- stringlit
+ *
+ *
+ *
+ *
+ * Semantics
+ *
+ *
The meaning of the grammar is described in the table below.
+ * This defines a function q that maps a string to a Java object
+ * such as a {@link QueryExp} or a {@link ValueExp}.
+ *
+ *
+ * String s | q(s) |
+ *
+ * query1 OR query2
+ * | {@link Query#or Query.or}(q(query1), q(query2))
+ *
+ * |
query1 AND query2
+ * | {@link Query#and Query.and}(q(query1), q(query2))
+ *
+ * |
( queryOrValue )
+ * | q(queryOrValue)
+ *
+ * |
NOT query
+ * | {@link Query#not Query.not}(q(query))
+ *
+ * |
INSTANCEOF stringLiteral
+ * | {@link Query#isInstanceOf Query.isInstanceOf}({@link Query#value(String) Query.value}(q(stringLiteral)))
+ *
+ * |
LIKE stringLiteral
+ * | {@link ObjectName#ObjectName(String) new ObjectName}(q(stringLiteral))
+ *
+ * |
value1 = value2
+ * | {@link Query#eq Query.eq}(q(value1), q(value2))
+ *
+ * |
value1 < value2
+ * | {@link Query#lt Query.lt}(q(value1), q(value2))
+ *
+ * |
value1 > value2
+ * | {@link Query#gt Query.gt}(q(value1), q(value2))
+ *
+ * |
value1 <= value2
+ * | {@link Query#leq Query.leq}(q(value1), q(value2))
+ *
+ * |
value1 >= value2
+ * | {@link Query#geq Query.geq}(q(value1), q(value2))
+ *
+ * |
value1 <> value2
+ * | {@link Query#not Query.not}({@link Query#eq Query.eq}(q(value1), q(value2)))
+ *
+ * |
value1 != value2
+ * | {@link Query#not Query.not}({@link Query#eq Query.eq}(q(value1), q(value2)))
+ *
+ * |
value1 BETWEEN value2 AND value3
+ * | {@link Query#between Query.between}(q(value1),
+ * q(value2), q(value3))
+ *
+ * |
value1 NOT BETWEEN value2 AND value3
+ * | {@link Query#not Query.not}({@link Query#between Query.between}(q(value1), q(value2), q(value3)))
+ *
+ * |
value1 IN ( value2, value3 )
+ * | {@link Query#in Query.in}(q(value1),
+ * new ValueExp[] {
+ * q(value2), q(value3)} )
+ *
+ * |
value1 NOT IN ( value2, value3 )
+ * | {@link Query#not Query.not}({@link Query#in Query.in}(q(value1),
+ * new ValueExp[] {
+ * q(value2), q(value3)} ))
+ *
+ * |
value LIKE stringLiteral
+ * | {@link Query#match Query.match}(q(value),
+ * translateWildcards(q(stringLiteral)))
+ *
+ * |
value NOT LIKE stringLiteral
+ * | {@link Query#not Query.not}({@link Query#match Query.match}(q(value),
+ * translateWildcards(q(stringLiteral))))
+ *
+ * |
value1 + value2
+ * | {@link Query#plus Query.plus}(q(value1), q(value2))
+ *
+ * |
value1 - value2
+ * | {@link Query#minus Query.minus}(q(value1), q(value2))
+ *
+ * |
value1 * value2
+ * | {@link Query#times Query.times}(q(value1), q(value2))
+ *
+ * |
value1 / value2
+ * | {@link Query#div Query.div}(q(value1), q(value2))
+ *
+ * |
name
+ * | {@link Query#attr(String) Query.attr}(q(name))
+ *
+ * |
name1#name2
+ * | {@link Query#attr(String,String) Query.attr}(q(name1),
+ * q(name2))
+ *
+ * |
FALSE
+ * | {@link Query#value(boolean) Query.value}(false)
+ *
+ * |
TRUE
+ * | {@link Query#value(boolean) Query.value}(true)
+ *
+ * |
decimalLiteral
+ * | {@link Query#value(long) Query.value}({@link Long#valueOf(String) Long.valueOf}(decimalLiteral))
+ *
+ * |
floatingPointLiteral
+ * | {@link Query#value(double) Query.value}({@link Double#valueOf(String) Double.valueOf}(floatingPointLiteral))
+ * |
+ *
+ * Here, translateWildcards is a function
+ * that translates from the SQL notation for wildcards, using {@code %} and
+ * {@code _}, to the JMX API notation, using {@code *} and {@code ?}. If the
+ * LIKE string already contains {@code *} or {@code ?}, these characters
+ * have their literal meanings, and will be quoted in the call to
+ * {@link Query#match Query.match}.
+ *
* @since 1.5
*/
public class Query extends Object {
@@ -277,16 +604,12 @@
}
/**
- * Returns a new attribute expression.
- *
- * Evaluating this expression for a given
- * objectName
includes performing {@link
- * MBeanServer#getAttribute MBeanServer.getAttribute(objectName,
- * name)}.
+ * Returns a new attribute expression. See {@link AttributeValueExp}
+ * for a detailed description of the semantics of the expression.
*
* @param name The name of the attribute.
*
- * @return An attribute expression for the attribute named name.
+ * @return An attribute expression for the attribute named {@code name}.
*/
public static AttributeValueExp attr(String name) {
return new AttributeValueExp(name);
@@ -628,6 +951,63 @@
}
/**
+ * Return a string representation of the given query. The string
+ * returned by this method can be converted back into an equivalent
+ * query using {@link #fromString fromString}.
+ *
+ * (Two queries are equivalent if they produce the same result in
+ * all cases. Equivalent queries are not necessarily identical:
+ * for example the queries {@code Query.lt(Query.attr("A"), Query.attr("B"))}
+ * and {@code Query.not(Query.ge(Query.attr("A"), Query.attr("B")))} are
+ * equivalent but not identical.)
+ *
+ * The string returned by this method is only guaranteed to be converted
+ * back into an equivalent query if {@code query} was constructed, or
+ * could have been constructed, using the methods of this class.
+ * If you make a custom query {@code myQuery} by implementing
+ * {@link QueryExp} yourself then the result of
+ * {@code Query.toString(myQuery)} is unspecified.
+ *
+ * @param query the query to convert. If it is null, the result will
+ * also be null.
+ * @return the string representation of the query, or null if the
+ * query is null.
+ *
+ * @since 1.7
+ */
+ public static String toString(QueryExp query) {
+ if (query == null)
+ return null;
+
+ if (query instanceof ToQueryString)
+ return ((ToQueryString) query).toQueryString();
+
+ return query.toString();
+ }
+
+ /**
+ * Produce a query from the given string. The query returned
+ * by this method can be converted back into a string using
+ * {@link #toString(QueryExp) toString}. The resultant string will
+ * not necessarily be equal to {@code s}.
+ *
+ * @param s the string to convert.
+ *
+ * @return a {@code QueryExp} derived by parsing the string, or
+ * null if the string is null.
+ *
+ * @throws IllegalArgumentException if the string is not a valid
+ * query string.
+ *
+ * @since 1.7
+ */
+ public static QueryExp fromString(String s) {
+ if (s == null)
+ return null;
+ return new QueryParser(s).parseQuery();
+ }
+
+ /**
* Utility method to escape strings used with
* Query.{initial|any|final}SubString() methods.
*/
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/QueryEval.java
--- a/jdk/src/share/classes/javax/management/QueryEval.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/QueryEval.java Mon Mar 03 10:32:38 2008 +0100
@@ -38,7 +38,7 @@
*
* @since 1.5
*/
-public abstract class QueryEval implements Serializable {
+public abstract class QueryEval extends ToQueryString implements Serializable {
/* Serial version */
private static final long serialVersionUID = 2675899265640874796L;
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/QueryExp.java
--- a/jdk/src/share/classes/javax/management/QueryExp.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/QueryExp.java Mon Mar 03 10:32:38 2008 +0100
@@ -30,9 +30,9 @@
/**
- * Represents relational constraints that can be used in database
- * query "where clauses". Instances of QueryExp are returned by the
- * static methods of the {@link Query} class.
+ * Represents relational constraints similar to database query "where
+ * clauses". Instances of QueryExp are returned by the static methods of the
+ * {@link Query} class.
*
* It is possible, but not
* recommended, to create custom queries by implementing this
@@ -40,6 +40,7 @@
* QueryEval} class than to implement the interface directly, so that
* the {@link #setMBeanServer} method works correctly.
*
+ * @see MBeanServer#queryNames MBeanServer.queryNames
* @since 1.5
*/
public interface QueryExp extends Serializable {
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/QueryParser.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/QueryParser.java Mon Mar 03 10:32:38 2008 +0100
@@ -0,0 +1,663 @@
+/*
+ * 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;
+
+/**
+ *
Parser for JMX queries represented in an SQL-like language.
+ */
+/*
+ * 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(""),
+ 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 idKeywords =
+ new TreeSet(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.isEmpty())
+ throw new NumberFormatException("Underflow: " + s);
+ }
+ return d;
+ }
+
+ // PARSER STARTS HERE
+
+ private final List 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();
+ 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();
+ String s = sve.getValue();
+ q = Query.match(alhs, patternValueExp(s));
+ break;
+ }
+
+ case 2: { // IN
+ expect(LPAR);
+ List values = new ArrayList();
+ 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);
+ }
+
+ // Convert the SQL pattern syntax, using % and _, to the Query.match
+ // syntax, using * and ?. The tricky part is recognizing \% and
+ // \_ as literal values, and also not replacing them inside [].
+ // But Query.match does not recognize \ inside [], which makes our
+ // job a tad easier.
+ private StringValueExp patternValueExp(String s) {
+ int c;
+ for (int i = 0; i < s.length(); i += Character.charCount(c)) {
+ c = s.codePointAt(i);
+ switch (c) {
+ case '\\':
+ i++; // i += Character.charCount(c), but we know it's 1!
+ if (i >= s.length())
+ throw new IllegalArgumentException("\\ at end of pattern");
+ break;
+ case '[':
+ i = s.indexOf(']', i);
+ if (i < 0)
+ throw new IllegalArgumentException("[ without ]");
+ break;
+ case '%':
+ s = s.substring(0, i) + "*" + s.substring(i + 1);
+ break;
+ case '_':
+ s = s.substring(0, i) + "?" + s.substring(i + 1);
+ break;
+ case '*':
+ case '?':
+ s = s.substring(0, i) + '\\' + (char) c + s.substring(i + 1);
+ i++;
+ break;
+ }
+ }
+ return Query.value(s);
+ }
+}
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/StringValueExp.java
--- a/jdk/src/share/classes/javax/management/StringValueExp.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/StringValueExp.java Mon Mar 03 10:32:38 2008 +0100
@@ -73,7 +73,7 @@
* Returns the string representing the object.
*/
public String toString() {
- return "'" + val + "'";
+ return "'" + val.replace("'", "''") + "'";
}
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/ToQueryString.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/ToQueryString.java Mon Mar 03 10:32:38 2008 +0100
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/* QueryExp classes can extend this to get non-default treatment for
+ * Query.toString(q). We're reluctant to change the public toString()
+ * methods of the classes because people might be parsing them, even
+ * though that's rather fragile. But Query.toString(q) has no such
+ * constraint so it can use the new toQueryString() method defined here.
+ */
+class ToQueryString {
+ String toQueryString() {
+ return toString();
+ }
+}
diff -r 74fe6922716d -r 2d042367885f jdk/src/share/classes/javax/management/monitor/Monitor.java
--- a/jdk/src/share/classes/javax/management/monitor/Monitor.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/src/share/classes/javax/management/monitor/Monitor.java Mon Mar 03 10:32:38 2008 +0100
@@ -27,13 +27,8 @@
import static com.sun.jmx.defaults.JmxProperties.MONITOR_LOGGER;
import com.sun.jmx.mbeanserver.GetPropertyAction;
-import com.sun.jmx.remote.util.EnvHelp;
-import java.beans.BeanInfo;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
+import com.sun.jmx.mbeanserver.Introspector;
import java.io.IOException;
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -64,7 +59,6 @@
import javax.management.ObjectName;
import javax.management.ReflectionException;
import static javax.management.monitor.MonitorNotification.*;
-import javax.management.openmbean.CompositeData;
/**
* Defines the part common to all monitor MBeans.
@@ -876,44 +870,13 @@
if (isComplexTypeAttribute) {
Object v = value;
for (String attr : remainingAttributes)
- v = introspect(object, attr, v);
+ v = Introspector.elementFromComplex(v, attr);
return (Comparable>) v;
} else {
return (Comparable>) value;
}
}
- Object introspect(ObjectName object,
- String attribute,
- Object value)
- throws AttributeNotFoundException {
- try {
- if (value.getClass().isArray() && attribute.equals("length")) {
- return Array.getLength(value);
- } else if (value instanceof CompositeData) {
- return ((CompositeData) value).get(attribute);
- } else {
- // Java Beans introspection
- //
- BeanInfo bi = Introspector.getBeanInfo(value.getClass());
- PropertyDescriptor[] pds = bi.getPropertyDescriptors();
- for (PropertyDescriptor pd : pds)
- if (pd.getName().equals(attribute))
- return pd.getReadMethod().invoke(value);
- throw new AttributeNotFoundException(
- "Could not find the getter method for the property " +
- attribute + " using the Java Beans introspector");
- }
- } catch (InvocationTargetException e) {
- throw new IllegalArgumentException(e);
- } catch (AttributeNotFoundException e) {
- throw e;
- } catch (Exception e) {
- throw EnvHelp.initCause(
- new AttributeNotFoundException(e.getMessage()), e);
- }
- }
-
boolean isComparableTypeValid(ObjectName object,
String attribute,
Comparable> value) {
diff -r 74fe6922716d -r 2d042367885f jdk/test/javax/management/query/QueryDottedAttrTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/query/QueryDottedAttrTest.java Mon Mar 03 10:32:38 2008 +0100
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test QueryDottedAttrTest
+ * @bug 6602310
+ * @summary Test that Query.attr can understand a.b etc.
+ * @author Eamonn McManus
+ */
+
+import java.beans.ConstructorProperties;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collections;
+import java.util.Set;
+import javax.management.AttributeNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import javax.management.Query;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.StandardMBean;
+
+public class QueryDottedAttrTest {
+ public static class Complex {
+ private final double re, im;
+
+ @ConstructorProperties({"real", "imaginary"})
+ public Complex(double re, double im) {
+ this.re = re;
+ this.im = im;
+ }
+
+ public double getRe() {
+ return re;
+ }
+
+ public double getIm() {
+ return im;
+ }
+ }
+
+ public static interface Intf {
+ Complex getComplex();
+ int[] getIntArray();
+ String[] getStringArray();
+ }
+
+ public static class Impl implements Intf {
+ public Complex getComplex() {
+ return new Complex(1.0, 1.0);
+ }
+
+ public int[] getIntArray() {
+ return new int[] {1, 2, 3};
+ }
+
+ public String[] getStringArray() {
+ return new String[] {"one", "two", "three"};
+ }
+ }
+
+ public static interface TestMBean extends Intf {}
+
+ public static class Test extends Impl implements TestMBean {}
+
+ public static interface TestMXBean extends Intf {}
+
+ public static class TestMX extends Impl implements TestMXBean {}
+
+ public static class AttrWithDot extends StandardMBean {
+ public AttrWithDot(Object impl, Class intf) {
+ super(intf.cast(impl), intf, (intf == TestMXBean.class));
+ }
+
+ public Object getAttribute(String attribute)
+ throws AttributeNotFoundException, MBeanException, ReflectionException {
+ if (attribute.equals("Complex.re"))
+ return 2.0;
+ else
+ return super.getAttribute(attribute);
+ }
+ }
+
+ private static final boolean[] booleans = {false, true};
+
+ private static final QueryExp[] alwaysTrueQueries = {
+ Query.eq(Query.attr("IntArray.length"), Query.value(3)),
+ Query.eq(Query.attr("StringArray.length"), Query.value(3)),
+ Query.eq(Query.attr("Complex.im"), Query.value(1.0)),
+ };
+
+ private static final QueryExp[] alwaysFalseQueries = {
+ Query.eq(Query.attr("IntArray.length"), Query.value("3")),
+ Query.eq(Query.attr("IntArray.length"), Query.value(2)),
+ Query.eq(Query.attr("Complex.im"), Query.value(-1.0)),
+ Query.eq(Query.attr("Complex.xxx"), Query.value(0)),
+ };
+
+ private static final QueryExp[] attrWithDotTrueQueries = {
+ Query.eq(Query.attr("Complex.re"), Query.value(2.0)),
+ };
+
+ private static final QueryExp[] attrWithDotFalseQueries = {
+ Query.eq(Query.attr("Complex.re"), Query.value(1.0)),
+ };
+
+ private static String failure;
+
+ public static void main(String[] args) throws Exception {
+ ObjectName name = new ObjectName("a:b=c");
+ for (boolean attrWithDot : booleans) {
+ for (boolean mx : booleans) {
+ String what =
+ (mx ? "MXBean" : "Standard MBean") +
+ (attrWithDot ? " having attribute with dot in its name" : "");
+ System.out.println("Testing " + what);
+ Class> intf = mx ? TestMXBean.class : TestMBean.class;
+ Object impl = mx ? new TestMX() : new Test();
+ if (attrWithDot)
+ impl = new AttrWithDot(impl, intf);
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ mbs.registerMBean(impl, name);
+ boolean ismx = "true".equals(
+ mbs.getMBeanInfo(name).getDescriptor().getFieldValue("mxbean"));
+ if (mx != ismx)
+ fail("MBean should " + (mx ? "" : "not ") + "be MXBean");
+ test(mbs, name, alwaysTrueQueries, true);
+ test(mbs, name, alwaysFalseQueries, false);
+ test(mbs, name, attrWithDotTrueQueries, attrWithDot);
+ test(mbs, name, attrWithDotFalseQueries, !attrWithDot);
+ }
+ }
+ if (failure != null)
+ throw new Exception("TEST FAILED: " + failure);
+ }
+
+ private static void test(
+ MBeanServer mbs, ObjectName name, QueryExp[] queries, boolean expect)
+ throws Exception {
+ for (QueryExp query : queries) {
+ // Serialize and deserialize the query to ensure that its
+ // serialization is correct
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ObjectOutputStream oout = new ObjectOutputStream(bout);
+ oout.writeObject(query);
+ oout.close();
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ ObjectInputStream oin = new ObjectInputStream(bin);
+ query = (QueryExp) oin.readObject();
+ Set names = mbs.queryNames(null, query);
+ if (names.isEmpty()) {
+ if (expect)
+ fail("Query is false but should be true: " + query);
+ } else if (names.equals(Collections.singleton(name))) {
+ if (!expect)
+ fail("Query is true but should be false: " + query);
+ } else {
+ fail("Query returned unexpected set: " + names);
+ }
+ }
+ }
+
+ private static void fail(String msg) {
+ failure = msg;
+ System.out.println("..." + msg);
+ }
+}
diff -r 74fe6922716d -r 2d042367885f jdk/test/javax/management/query/QueryExpStringTest.java
--- a/jdk/test/javax/management/query/QueryExpStringTest.java Fri Feb 29 20:04:01 2008 -0800
+++ b/jdk/test/javax/management/query/QueryExpStringTest.java Mon Mar 03 10:32:38 2008 +0100
@@ -31,6 +31,10 @@
* @run main QueryExpStringTest
*/
+// This test is mostly obsolete, since we now have Query.fromString.
+// The test includes its own parser, from which Query.fromString was derived.
+// The parsers are not identical and the one here is no longer maintained.
+
import java.util.*;
import javax.management.*;
@@ -39,6 +43,11 @@
private static final ValueExp
attr = Query.attr("attr"),
qattr = Query.attr("className", "attr"),
+ aa = Query.attr("A"),
+ bb = Query.attr("B"),
+ cc = Query.attr("C"),
+ dd = Query.attr("D"),
+ zero = Query.value(0),
classattr = Query.classattr(),
simpleString = Query.value("simpleString"),
complexString = Query.value("a'b\\'\""),
@@ -66,10 +75,14 @@
(StringValueExp) simpleString),
initialStar = Query.initialSubString((AttributeValueExp) attr,
Query.value("*")),
+ initialPercent = Query.initialSubString((AttributeValueExp) attr,
+ Query.value("%")),
any = Query.anySubString((AttributeValueExp) attr,
(StringValueExp) simpleString),
anyStar = Query.anySubString((AttributeValueExp) attr,
Query.value("*")),
+ anyPercent = Query.anySubString((AttributeValueExp) attr,
+ Query.value("%")),
ffinal = Query.finalSubString((AttributeValueExp) attr,
(StringValueExp) simpleString),
finalMagic = Query.finalSubString((AttributeValueExp) attr,
@@ -77,16 +90,20 @@
in = Query.in(intValue, new ValueExp[] {intValue, floatValue}),
and = Query.and(gt, lt),
or = Query.or(gt, lt),
- not = Query.not(gt);
+ not = Query.not(gt),
+ aPlusB_PlusC = Query.gt(Query.plus(Query.plus(aa, bb), cc), zero),
+ aPlus_BPlusC = Query.gt(Query.plus(aa, Query.plus(bb, cc)), zero);
// Commented-out tests below require change to implementation
private static final Object tests[] = {
attr, "attr",
- qattr, "className.attr",
+// qattr, "className.attr",
+// Preceding form now appears as className#attr, an incompatible change
+// which we don't mind much because nobody uses the two-arg Query.attr.
classattr, "Class",
simpleString, "'simpleString'",
-// complexString, "'a\\'b\\\\\\'\"'",
+ complexString, "'a''b\\\''\"'",
intValue, "12345678",
integerValue, "12345678",
longValue, "12345678",
@@ -104,16 +121,20 @@
eq, "(12345678) = (2.5)",
between, "(12345678) between (2.5) and (2.5)",
match, "attr like 'simpleString'",
-// initial, "attr like 'simpleString*'",
-// initialStar, "attr like '\\\\**'",
-// any, "attr like '*simpleString*'",
-// anyStar, "attr like '*\\\\**'",
-// ffinal, "attr like '*simpleString'",
-// finalMagic, "attr like '*\\\\?\\\\*\\\\[\\\\\\\\'",
+ initial, "attr like 'simpleString%'",
+ initialStar, "attr like '\\*%'",
+ initialPercent, "attr like '\\%%'",
+ any, "attr like '%simpleString%'",
+ anyStar, "attr like '%\\*%'",
+ anyPercent, "attr like '%\\%%'",
+ ffinal, "attr like '%simpleString'",
+ finalMagic, "attr like '%\\?\\*\\[\\\\'",
in, "12345678 in (12345678, 2.5)",
and, "((12345678) > (2.5)) and ((12345678) < (2.5))",
or, "((12345678) > (2.5)) or ((12345678) < (2.5))",
not, "not ((12345678) > (2.5))",
+ aPlusB_PlusC, "(A + B + C) > (0)",
+// aPlus_BPlusC, "(A + (B + C)) > (0)",
};
public static void main(String[] args) throws Exception {
@@ -185,7 +206,9 @@
throw new Exception("Expected types `attr like string': " +
exp + " like " + pat);
}
- return Query.match((AttributeValueExp) exp, (StringValueExp) pat);
+ StringValueExp spat = (StringValueExp) pat;
+ spat = Query.value(translateMatch(spat.getValue()));
+ return Query.match((AttributeValueExp) exp, spat);
}
if (skip(ss, " in (")) {
@@ -203,6 +226,28 @@
throw new Exception("Expected in or like after expression");
}
+ private static String translateMatch(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < s.length(); i++) { // logic not correct for wide chars
+ char c = s.charAt(i);
+ switch (c) {
+ case '\\':
+ sb.append(c).append(s.charAt(++i)); break;
+ case '%':
+ sb.append('*'); break;
+ case '_':
+ sb.append('?'); break;
+ case '*':
+ sb.append("\\*"); break;
+ case '?':
+ sb.append("\\?"); break;
+ default:
+ sb.append(c); break;
+ }
+ }
+ return sb.toString();
+ }
+
private static QueryExp parseQueryAfterParen(String[] ss)
throws Exception {
/* This is very ugly. We might have "(q1) and (q2)" here, or
@@ -229,7 +274,7 @@
ss[0] = start;
ValueExp lhs = parseExp(ss);
if (!skip(ss, ") "))
- throw new Exception("Expected `) ' after subexpression");
+ throw new Exception("Expected `) ' after subexpression: " + ss[0]);
String op = scanWord(ss);
if (!skip(ss, " ("))
throw new Exception("Expected ` (' after `" + op + "'");
@@ -258,15 +303,16 @@
}
private static ValueExp parseExp(String[] ss) throws Exception {
- final ValueExp prim = parsePrimary(ss);
+ ValueExp lhs = parsePrimary(ss);
+ while (true) {
/* Look ahead to see if we have an arithmetic operator. */
String back = ss[0];
if (!skip(ss, " "))
- return prim;
+ return lhs;
if (ss[0].equals("") || "+-*/".indexOf(ss[0].charAt(0)) < 0) {
ss[0] = back;
- return prim;
+ return lhs;
}
final String op = scanWord(ss);
@@ -276,15 +322,16 @@
throw new Exception("Unknown arithmetic operator: " + op);
if (!skip(ss, " "))
throw new Exception("Expected space after arithmetic operator");
- ValueExp rhs = parseExp(ss);
+ ValueExp rhs = parsePrimary(ss);
switch (op.charAt(0)) {
- case '+': return Query.plus(prim, rhs);
- case '-': return Query.minus(prim, rhs);
- case '*': return Query.times(prim, rhs);
- case '/': return Query.div(prim, rhs);
+ case '+': lhs = Query.plus(lhs, rhs); break;
+ case '-': lhs = Query.minus(lhs, rhs); break;
+ case '*': lhs = Query.times(lhs, rhs); break;
+ case '/': lhs = Query.div(lhs, rhs); break;
default: throw new Exception("Can't happen: " + op.charAt(0));
}
}
+ }
private static ValueExp parsePrimary(String[] ss) throws Exception {
String s = ss[0];
@@ -324,14 +371,19 @@
private static String scanWord(String[] ss) throws Exception {
String s = ss[0];
int space = s.indexOf(' ');
- if (space < 0) {
+ int rpar = s.indexOf(')');
+ if (space < 0 && rpar < 0) {
ss[0] = "";
return s;
- } else {
- String word = s.substring(0, space);
- ss[0] = s.substring(space);
- return word;
}
+ int stop;
+ if (space >= 0 && rpar >= 0) // string has both space and ), stop at first
+ stop = Math.min(space, rpar);
+ else // string has only one, stop at it
+ stop = Math.max(space, rpar);
+ String word = s.substring(0, stop);
+ ss[0] = s.substring(stop);
+ return word;
}
private static boolean matchWord(String[] ss, String word)
@@ -381,13 +433,11 @@
for (i = 0; i < len; i++) {
char c = s.charAt(i);
if (c == '\'') {
- ss[0] = s.substring(i + 1);
+ ++i;
+ if (i >= len || s.charAt(i) != '\'') {
+ ss[0] = s.substring(i);
return Query.value(buf.toString());
}
- if (c == '\\') {
- if (++i == len)
- throw new Exception("\\ at end of string");
- c = s.charAt(i);
}
buf.append(c);
}
diff -r 74fe6922716d -r 2d042367885f jdk/test/javax/management/query/QueryParseTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/query/QueryParseTest.java Mon Mar 03 10:32:38 2008 +0100
@@ -0,0 +1,778 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test QueryParseTest
+ * @bug 6602310 6604768
+ * @summary Test Query.fromString and Query.toString.
+ * @author Eamonn McManus
+ */
+
+import java.util.Collections;
+import java.util.Set;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import javax.management.Query;
+import javax.management.QueryExp;
+
+public class QueryParseTest {
+ // In this table, each string constant corresponds to a test case.
+ // The objects following the string up to the next string are MBeans.
+ // Each MBean must implement ExpectedValue to return true or false
+ // according as it should return that value for the query parsed
+ // from the given string. The test will parse the string into a
+ // a query and verify that the MBeans return the expected value
+ // for that query. Then it will convert the query back into a string
+ // and into a second query, and check that the MBeans return the
+ // expected value for that query too. The reason we need to do all
+ // this is that the spec talks about "equivalent queries", and gives
+ // the implementation wide scope to rearrange queries. So we cannot
+ // just compare string values.
+ //
+ // We could also write an implementation-dependent test that knew what
+ // the strings look like, and that would have to be changed if the
+ // implementation changed. But the approach here is cleaner.
+ //
+ // To simplify the creation of MBeans, most use the expectTrue or
+ // expectFalse methods. The parameters of these methods end up in
+ // attributes called "A", "B", "C", etc.
+ private static final Object[] queryTests = {
+ // RELATIONS
+
+ "A < B",
+ expectTrue(1, 2), expectTrue(1.0, 2.0), expectTrue("one", "two"),
+ expectTrue(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY),
+ expectFalse(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY),
+ expectFalse(1, 1), expectFalse(1.0, 1.0), expectFalse("one", "one"),
+ expectFalse(2, 1), expectFalse(2.0, 1.0), expectFalse("two", "one"),
+ expectFalse(Double.NaN, Double.NaN),
+
+ "One = two",
+ expectTrueOneTwo(1, 1), expectTrueOneTwo(1.0, 1.0),
+ expectFalseOneTwo(1, 2), expectFalseOneTwo(2, 1),
+
+ "A <= B",
+ expectTrue(1, 1), expectTrue(1, 2), expectTrue("one", "one"),
+ expectTrue("one", "two"),
+ expectFalse(2, 1), expectFalse("two", "one"),
+ expectFalse(Double.NaN, Double.NaN),
+
+ "A >= B",
+ expectTrue(1, 1), expectTrue(2, 1), expectTrue("two", "one"),
+ expectFalse(1, 2), expectFalse("one", "two"),
+
+ "A > B",
+ expectTrue(2, 1), expectTrue("two", "one"),
+ expectFalse(2, 2), expectFalse(1, 2), expectFalse(1.0, 2.0),
+ expectFalse("one", "two"),
+
+ "A <> B",
+ expectTrue(1, 2), expectTrue("foo", "bar"),
+ expectFalse(1, 1), expectFalse("foo", "foo"),
+
+ "A != B",
+ expectTrue(1, 2), expectTrue("foo", "bar"),
+ expectFalse(1, 1), expectFalse("foo", "foo"),
+
+ // PARENTHESES
+
+ "(((A))) = (B)",
+ expectTrue(1, 1), expectFalse(1, 2),
+
+ "(A = B)",
+ expectTrue(1, 1), expectFalse(1, 2),
+
+ "(((A = (B))))",
+ expectTrue(1, 1), expectFalse(1, 2),
+
+ // INTEGER LITERALS
+
+ "A = 1234567890123456789",
+ expectTrue(1234567890123456789L), expectFalse(123456789L),
+
+ "A = +1234567890123456789",
+ expectTrue(1234567890123456789L), expectFalse(123456789L),
+
+ "A = -1234567890123456789",
+ expectTrue(-1234567890123456789L), expectFalse(-123456789L),
+
+
+ "A = + 1234567890123456789",
+ expectTrue(1234567890123456789L), expectFalse(123456789L),
+
+ "A = - 1234567890123456789",
+ expectTrue(-1234567890123456789L), expectFalse(-123456789L),
+
+ "A = " + Long.MAX_VALUE,
+ expectTrue(Long.MAX_VALUE), expectFalse(Long.MIN_VALUE),
+
+ "A = " + Long.MIN_VALUE,
+ expectTrue(Long.MIN_VALUE), expectFalse(Long.MAX_VALUE),
+
+ // DOUBLE LITERALS
+
+ "A = 0.0",
+ expectTrue(0.0), expectFalse(1.0),
+
+ "A = 0.0e23",
+ expectTrue(0.0), expectFalse(1.0),
+
+ "A = 1.2e3",
+ expectTrue(1.2e3), expectFalse(1.2),
+
+ "A = +1.2",
+ expectTrue(1.2), expectFalse(-1.2),
+
+ "A = 1.2e+3",
+ expectTrue(1.2e3), expectFalse(1.2),
+
+ "A = 1.2e-3",
+ expectTrue(1.2e-3), expectFalse(1.2),
+
+ "A = 1.2E3",
+ expectTrue(1.2e3), expectFalse(1.2),
+
+ "A = -1.2e3",
+ expectTrue(-1.2e3), expectFalse(1.2),
+
+ "A = " + Double.MAX_VALUE,
+ expectTrue(Double.MAX_VALUE), expectFalse(Double.MIN_VALUE),
+
+ "A = " + -Double.MAX_VALUE,
+ expectTrue(-Double.MAX_VALUE), expectFalse(-Double.MIN_VALUE),
+
+ "A = " + Double.MIN_VALUE,
+ expectTrue(Double.MIN_VALUE), expectFalse(Double.MAX_VALUE),
+
+ "A = " + -Double.MIN_VALUE,
+ expectTrue(-Double.MIN_VALUE), expectFalse(-Double.MAX_VALUE),
+
+ Query.toString( // A = Infinity -> A = (1.0/0.0)
+ Query.eq(Query.attr("A"), Query.value(Double.POSITIVE_INFINITY))),
+ expectTrue(Double.POSITIVE_INFINITY),
+ expectFalse(0.0), expectFalse(Double.NEGATIVE_INFINITY),
+
+ Query.toString( // A = -Infinity -> A = (-1.0/0.0)
+ Query.eq(Query.attr("A"), Query.value(Double.NEGATIVE_INFINITY))),
+ expectTrue(Double.NEGATIVE_INFINITY),
+ expectFalse(0.0), expectFalse(Double.POSITIVE_INFINITY),
+
+ Query.toString( // A < NaN -> A < (0.0/0.0)
+ Query.lt(Query.attr("A"), Query.value(Double.NaN))),
+ expectFalse(0.0), expectFalse(Double.NEGATIVE_INFINITY),
+ expectFalse(Double.POSITIVE_INFINITY), expectFalse(Double.NaN),
+
+ Query.toString( // A >= NaN -> A < (0.0/0.0)
+ Query.geq(Query.attr("A"), Query.value(Double.NaN))),
+ expectFalse(0.0), expectFalse(Double.NEGATIVE_INFINITY),
+ expectFalse(Double.POSITIVE_INFINITY), expectFalse(Double.NaN),
+
+ // STRING LITERALS
+
+ "A = 'blim'",
+ expectTrue("blim"), expectFalse("blam"),
+
+ "A = 'can''t'",
+ expectTrue("can't"), expectFalse("cant"), expectFalse("can''t"),
+
+ "A = '''blim'''",
+ expectTrue("'blim'"), expectFalse("'blam'"),
+
+ "A = ''",
+ expectTrue(""), expectFalse((Object) null),
+
+ // BOOLEAN LITERALS
+
+ "A = true",
+ expectTrue(true), expectFalse(false), expectFalse((Object) null),
+
+ "A = TRUE",
+ expectTrue(true), expectFalse(false),
+
+ "A = TrUe",
+ expectTrue(true), expectFalse(false),
+
+ "A = false",
+ expectTrue(false), expectFalse(true),
+
+ "A = fAlSe",
+ expectTrue(false), expectFalse(true),
+
+ "A = \"true\"", // An attribute called "true"
+ expectFalse(true), expectFalse(false), expectFalse("\"true\""),
+ newTester(new String[] {"A", "true"}, new Object[] {2.2, 2.2}, true),
+ newTester(new String[] {"A", "true"}, new Object[] {2.2, 2.3}, false),
+
+ "A = \"False\"",
+ expectFalse(true), expectFalse(false), expectFalse("\"False\""),
+ newTester(new String[] {"A", "False"}, new Object[] {2.2, 2.2}, true),
+ newTester(new String[] {"A", "False"}, new Object[] {2.2, 2.3}, false),
+
+ // ARITHMETIC
+
+ "A + B = 10",
+ expectTrue(4, 6), expectFalse(3, 8),
+
+ "A + B = 'blim'",
+ expectTrue("bl", "im"), expectFalse("bl", "am"),
+
+ "A - B = 10",
+ expectTrue(16, 6), expectFalse(16, 3),
+
+ "A * B = 10",
+ expectTrue(2, 5), expectFalse(3, 3),
+
+ "A / B = 10",
+ expectTrue(70, 7), expectTrue(70.0, 7), expectFalse(70.01, 7),
+
+ "A + B + C = 10",
+ expectTrue(2, 3, 5), expectFalse(2, 4, 8),
+
+ "A+B+C=10",
+ expectTrue(2, 3, 5), expectFalse(2, 4, 8),
+
+ "A + B + C + D = 10",
+ expectTrue(1, 2, 3, 4), expectFalse(2, 3, 4, 5),
+
+ "A + (B + C) = 10",
+ expectTrue(2, 3, 5), expectFalse(2, 4, 8),
+
+ // It is not correct to rearrange A + (B + C) as A + B + C
+ // (which means (A + B) + C), because of overflow.
+ // In particular Query.toString must not do this.
+ "A + (B + C) = " + Double.MAX_VALUE, // ensure no false associativity
+ expectTrue(Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE),
+ expectFalse(-Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE),
+
+ "A * (B * C) < " + Double.MAX_VALUE, // same test for multiplication
+ expectTrue(Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE),
+ expectFalse(Double.MIN_VALUE, Double.MAX_VALUE, Double.MAX_VALUE),
+
+ "A * B + C = 10",
+ expectTrue(3, 3, 1), expectTrue(2, 4, 2), expectFalse(1, 2, 3),
+
+ "A*B+C=10",
+ expectTrue(3, 3, 1), expectTrue(2, 4, 2), expectFalse(1, 2, 3),
+
+ "(A * B) + C = 10",
+ expectTrue(3, 3, 1), expectTrue(2, 4, 2), expectFalse(1, 2, 3),
+
+ "A + B * C = 10",
+ expectTrue(1, 3, 3), expectTrue(2, 2, 4), expectFalse(1, 2, 3),
+
+ "A - B * C = 10",
+ expectTrue(16, 2, 3), expectFalse(15, 2, 2),
+
+ "A + B / C = 10",
+ expectTrue(5, 15, 3), expectFalse(5, 16, 4),
+
+ "A - B / C = 10",
+ expectTrue(16, 12, 2), expectFalse(15, 10, 3),
+
+ "A * (B + C) = 10",
+ expectTrue(2, 2, 3), expectFalse(1, 2, 3),
+
+ "A / (B + C) = 10",
+ expectTrue(70, 4, 3), expectFalse(70, 3, 5),
+
+ "A * (B - C) = 10",
+ expectTrue(2, 8, 3), expectFalse(2, 3, 8),
+
+ "A / (B - C) = 10",
+ expectTrue(70, 11, 4), expectFalse(70, 4, 11),
+
+ "A / B / C = 10",
+ expectTrue(140, 2, 7), expectFalse(100, 5, 5),
+
+ "A / (B / C) = 10",
+ expectTrue(70, 14, 2), expectFalse(70, 10, 7),
+
+ // LOGIC
+
+ "A = B or C = D",
+ expectTrue(1, 1, 2, 3), expectTrue(1, 2, 3, 3), expectTrue(1, 1, 2, 2),
+ expectFalse(1, 2, 3, 4), expectFalse("!", "!!", "?", "??"),
+
+ "A = B and C = D",
+ expectTrue(1, 1, 2, 2),
+ expectFalse(1, 1, 2, 3), expectFalse(1, 2, 3, 3),
+
+ "A = 1 and B = 2 and C = 3",
+ expectTrue(1, 2, 3), expectFalse(1, 2, 4),
+
+ "A = 1 or B = 2 or C = 3",
+ expectTrue(1, 2, 3), expectTrue(1, 0, 0), expectTrue(0, 0, 3),
+ expectFalse(2, 3, 4),
+
+ // grouped as (a and b) or (c and d)
+ "A = 1 AND B = 2 OR C = 3 AND D = 4",
+ expectTrue(1, 2, 3, 4), expectTrue(1, 2, 1, 2), expectTrue(3, 4, 3, 4),
+ expectFalse(3, 4, 1, 2), expectFalse(1, 1, 1, 1),
+
+ "(A = 1 AND B = 2) OR (C = 3 AND D = 4)",
+ expectTrue(1, 2, 3, 4), expectTrue(1, 2, 1, 2), expectTrue(3, 4, 3, 4),
+ expectFalse(3, 4, 1, 2), expectFalse(1, 1, 1, 1),
+
+ "(A = 1 or B = 2) AND (C = 3 or C = 4)",
+ expectTrue(1, 1, 3, 3), expectTrue(2, 2, 4, 4), expectTrue(1, 2, 3, 4),
+ expectFalse(1, 2, 1, 2), expectFalse(3, 4, 3, 4),
+
+ // LIKE
+
+ "A like 'b%m'",
+ expectTrue("blim"), expectTrue("bm"),
+ expectFalse(""), expectFalse("blimmo"), expectFalse("mmm"),
+
+ "A not like 'b%m'",
+ expectFalse("blim"), expectFalse("bm"),
+ expectTrue(""), expectTrue("blimmo"), expectTrue("mmm"),
+
+ "A like 'b_m'",
+ expectTrue("bim"), expectFalse("blim"),
+
+ "A like '%can''t%'",
+ expectTrue("can't"),
+ expectTrue("I'm sorry Dave, I'm afraid I can't do that"),
+ expectFalse("cant"), expectFalse("can''t"),
+
+ "A like '\\%%\\%'",
+ expectTrue("%blim%"), expectTrue("%%"),
+ expectFalse("blim"), expectFalse("%asdf"), expectFalse("asdf%"),
+
+ "A LIKE '*%?_'",
+ expectTrue("*blim?!"), expectTrue("*?_"),
+ expectFalse("blim"), expectFalse("blim?"),
+ expectFalse("?*"), expectFalse("??"), expectFalse(""), expectFalse("?"),
+
+ Query.toString(
+ Query.initialSubString(Query.attr("A"), Query.value("*?%_"))),
+ expectTrue("*?%_tiddly"), expectTrue("*?%_"),
+ expectFalse("?%_tiddly"), expectFalse("*!%_"), expectFalse("*??_"),
+ expectFalse("*?%!"), expectFalse("*?%!tiddly"),
+
+ Query.toString(
+ Query.finalSubString(Query.attr("A"), Query.value("*?%_"))),
+ expectTrue("tiddly*?%_"), expectTrue("*?%_"),
+ expectFalse("tiddly?%_"), expectFalse("*!%_"), expectFalse("*??_"),
+ expectFalse("*?%!"), expectFalse("tiddly*?%!"),
+
+ // BETWEEN
+
+ "A between B and C",
+ expectTrue(1, 1, 2), expectTrue(2, 1, 2), expectTrue(2, 1, 3),
+ expectFalse(3, 1, 2), expectFalse(0, 1, 2), expectFalse(2, 3, 1),
+ expectTrue(1.0, 0.0, 2.0), expectFalse(2.0, 0.0, 1.0),
+ expectTrue(0.0, 0.0, 0.0), expectTrue(1.0, 0.0, 1.0),
+ expectTrue(1.0, 0.0, Double.POSITIVE_INFINITY),
+ expectFalse(1.0, Double.NEGATIVE_INFINITY, 0.0),
+ expectFalse(false, false, true), expectFalse(true, false, true),
+ expectTrue("jim", "fred", "sheila"), expectFalse("fred", "jim", "sheila"),
+
+ "A between B and C and 1+2=3",
+ expectTrue(2, 1, 3), expectFalse(2, 3, 1),
+
+ "A not between B and C",
+ expectTrue(1, 2, 3), expectFalse(2, 1, 3),
+
+ // IN
+
+ "A in (1, 2, 3)",
+ expectTrue(1), expectTrue(2), expectTrue(3),
+ expectFalse(0), expectFalse(4),
+
+ "A in (1)",
+ expectTrue(1), expectFalse(0),
+
+ "A in (1.2, 3.4)",
+ expectTrue(1.2), expectTrue(3.4), expectFalse(0.0),
+
+ "A in ('foo', 'bar')",
+ expectTrue("foo"), expectTrue("bar"), expectFalse("baz"),
+
+ "A in ('foo', 'bar') and 'bl'+'im'='blim'",
+ expectTrue("foo"), expectTrue("bar"), expectFalse("baz"),
+
+ "A in (B, C, D)", // requires fix for CR 6604768
+ expectTrue(1, 1, 2, 3), expectFalse(1, 2, 3, 4),
+
+ "A not in (B, C, D)",
+ expectTrue(1, 2, 3, 4), expectFalse(1, 1, 2, 3),
+
+ // QUOTING
+
+ "\"LIKE\" = 1 and \"NOT\" = 2 and \"INSTANCEOF\" = 3 and " +
+ "\"TRUE\" = 4 and \"FALSE\" = 5",
+ newTester(
+ new String[] {"LIKE", "NOT", "INSTANCEOF", "TRUE", "FALSE"},
+ new Object[] {1, 2, 3, 4, 5},
+ true),
+ newTester(
+ new String[] {"LIKE", "NOT", "INSTANCEOF", "TRUE", "FALSE"},
+ new Object[] {5, 4, 3, 2, 1},
+ false),
+
+ "\"\"\"woo\"\"\" = 5",
+ newTester(new String[] {"\"woo\""}, new Object[] {5}, true),
+ newTester(new String[] {"\"woo\""}, new Object[] {4}, false),
+ expectFalse(),
+
+ // INSTANCEOF
+
+ "instanceof '" + Tester.class.getName() + "'",
+ expectTrue(),
+
+ "instanceof '" + String.class.getName() + "'",
+ expectFalse(),
+
+ // LIKE OBJECTNAME
+
+ // The test MBean is registered as a:b=c
+ "like 'a:b=c'", expectTrue(),
+ "like 'a:*'", expectTrue(),
+ "like '*:b=c'", expectTrue(),
+ "like 'a:b=*'", expectTrue(),
+ "like 'a:b=?'", expectTrue(),
+ "like 'd:b=c'", expectFalse(),
+ "like 'a:b=??*'", expectFalse(),
+ "like 'a:b=\"can''t\"'", expectFalse(),
+
+ // QUALIFIED ATTRIBUTE
+
+ Tester.class.getName() + "#A = 5",
+ expectTrue(5), expectFalse(4),
+
+ Tester.class.getName() + " # A = 5",
+ expectTrue(5), expectFalse(4),
+
+ Tester.class.getSuperclass().getName() + "#A = 5",
+ expectFalse(5),
+
+ DynamicMBean.class.getName() + "#A = 5",
+ expectFalse(5),
+
+ Tester.class.getName() + "#A = 5",
+ new Tester(new String[] {"A"}, new Object[] {5}, false) {},
+ // note the little {} at the end which means this is a subclass
+ // and therefore QualifiedAttributeValue should return false.
+
+ MBeanServerDelegate.class.getName() + "#SpecificationName LIKE '%'",
+ new Wrapped(new MBeanServerDelegate(), true),
+ new Tester(new String[] {"SpecificationName"}, new Object[] {"JMX"}, false),
+
+ // DOTTED ATTRIBUTE
+
+ "A.canonicalName = '" +
+ MBeanServerDelegate.DELEGATE_NAME.getCanonicalName() + "'",
+ expectTrue(MBeanServerDelegate.DELEGATE_NAME),
+ expectFalse(ObjectName.WILDCARD),
+
+ "A.class.name = 'java.lang.String'",
+ expectTrue("blim"), expectFalse(95), expectFalse((Object) null),
+
+ "A.canonicalName like 'JMImpl%:%'",
+ expectTrue(MBeanServerDelegate.DELEGATE_NAME),
+ expectFalse(ObjectName.WILDCARD),
+
+ "A.true = 'blim'",
+ new Tester(new String[] {"A.true"}, new Object[] {"blim"}, true),
+ new Tester(new String[] {"A.true"}, new Object[] {"blam"}, false),
+
+ "\"A.true\" = 'blim'",
+ new Tester(new String[] {"A.true"}, new Object[] {"blim"}, true),
+ new Tester(new String[] {"A.true"}, new Object[] {"blam"}, false),
+
+ MBeanServerDelegate.class.getName() +
+ "#SpecificationName.class.name = 'java.lang.String'",
+ new Wrapped(new MBeanServerDelegate(), true),
+ new Tester(new String[] {"SpecificationName"}, new Object[] {"JMX"}, false),
+
+ MBeanServerDelegate.class.getName() +
+ " # SpecificationName.class.name = 'java.lang.String'",
+ new Wrapped(new MBeanServerDelegate(), true),
+ new Tester(new String[] {"SpecificationName"}, new Object[] {"JMX"}, false),
+
+ // CLASS
+
+ "class = '" + Tester.class.getName() + "'",
+ expectTrue(),
+ new Wrapped(new MBeanServerDelegate(), false),
+
+ "Class = '" + Tester.class.getName() + "'",
+ expectTrue(),
+ new Wrapped(new MBeanServerDelegate(), false),
+ };
+
+ private static final String[] incorrectQueries = {
+ "", " ", "25", "()", "(a = b", "a = b)", "a.3 = 5",
+ "a = " + Long.MAX_VALUE + "0",
+ "a = " + Double.MAX_VALUE + "0",
+ "a = " + Double.MIN_VALUE + "0",
+ "a = 12a5", "a = 12e5e5", "a = 12.23.34",
+ "a = 'can't'", "a = 'unterminated", "a = 'asdf''",
+ "a = \"oops", "a = \"oops\"\"",
+ "a like 5", "true or false",
+ "a ! b", "? = 3", "a = @", "a##b",
+ "a between b , c", "a between and c",
+ "a in b, c", "a in 23", "a in (2, 3", "a in (2, 3x)",
+ "a like \"foo\"", "a like b", "a like 23",
+ "like \"foo\"", "like b", "like 23", "like 'a:b'",
+ "5 like 'a'", "'a' like '%'",
+ "a not= b", "a not = b", "a not b", "a not b c",
+ "a = +b", "a = +'b'", "a = +true", "a = -b", "a = -'b'",
+ "a#5 = b", "a#'b' = c",
+ "a instanceof b", "a instanceof 17", "a instanceof",
+ "a like 'oops\\'", "a like '[oops'",
+
+ // Check that -Long.MIN_VALUE is an illegal constant. This is one more
+ // than Long.MAX_VALUE and, like the Java language, we only allow it
+ // if it is the operand of unary minus.
+ "a = " + Long.toString(Long.MIN_VALUE).substring(1),
+ };
+
+ public static void main(String[] args) throws Exception {
+ int nexti;
+ String failed = null;
+
+ System.out.println("TESTING CORRECT QUERY STRINGS");
+ for (int i = 0; i < queryTests.length; i = nexti) {
+ for (nexti = i + 1; nexti < queryTests.length; nexti++) {
+ if (queryTests[nexti] instanceof String)
+ break;
+ }
+ if (!(queryTests[i] instanceof String))
+ throw new Exception("Test bug: should be string: " + queryTests[i]);
+
+ String qs = (String) queryTests[i];
+ System.out.println("Test: " + qs);
+
+ QueryExp qe = Query.fromString(qs);
+ String qes = Query.toString(qe);
+ System.out.println("...parses to: " + qes);
+ final QueryExp[] queries;
+ if (qes.equals(qs))
+ queries = new QueryExp[] {qe};
+ else {
+ QueryExp qe2 = Query.fromString(qes);
+ String qes2 = Query.toString(qe2);
+ System.out.println("...which parses to: " + qes2);
+ if (qes.equals(qes2))
+ queries = new QueryExp[] {qe};
+ else
+ queries = new QueryExp[] {qe, qe2};
+ }
+
+ for (int j = i + 1; j < nexti; j++) {
+ Object mbean;
+ if (queryTests[j] instanceof Wrapped)
+ mbean = ((Wrapped) queryTests[j]).mbean();
+ else
+ mbean = queryTests[j];
+ boolean expect = ((ExpectedValue) queryTests[j]).expectedValue();
+ for (QueryExp qet : queries) {
+ boolean actual = runQuery(qet, mbean);
+ boolean ok = (expect == actual);
+ System.out.println(
+ "..." + mbean + " -> " + actual +
+ (ok ? " (OK)" : " ####INCORRECT####"));
+ if (!ok)
+ failed = qs;
+ }
+ }
+ }
+
+ System.out.println();
+ System.out.println("TESTING INCORRECT QUERY STRINGS");
+ for (String s : incorrectQueries) {
+ try {
+ QueryExp qe = Query.fromString(s);
+ System.out.println("###DID NOT GET ERROR:### \"" + s + "\"");
+ failed = s;
+ } catch (IllegalArgumentException e) {
+ String es = (e.getClass() == IllegalArgumentException.class) ?
+ e.getMessage() : e.toString();
+ System.out.println("OK: exception for \"" + s + "\": " + es);
+ }
+ }
+
+ if (failed == null)
+ System.out.println("TEST PASSED");
+ else
+ throw new Exception("TEST FAILED: Last failure: " + failed);
+ }
+
+ private static boolean runQuery(QueryExp qe, Object mbean)
+ throws Exception {
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ ObjectName name = new ObjectName("a:b=c");
+ mbs.registerMBean(mbean, name);
+ Set names = mbs.queryNames(new ObjectName("a:*"), qe);
+ if (names.isEmpty())
+ return false;
+ if (names.equals(Collections.singleton(name)))
+ return true;
+ throw new Exception("Unexpected query result set: " + names);
+ }
+
+ private static interface ExpectedValue {
+ public boolean expectedValue();
+ }
+
+ private static class Wrapped implements ExpectedValue {
+ private final Object mbean;
+ private final boolean expect;
+
+ Wrapped(Object mbean, boolean expect) {
+ this.mbean = mbean;
+ this.expect = expect;
+ }
+
+ Object mbean() {
+ return mbean;
+ }
+
+ public boolean expectedValue() {
+ return expect;
+ }
+ }
+
+ private static class Tester implements DynamicMBean, ExpectedValue {
+ private final AttributeList attributes;
+ private final boolean expectedValue;
+
+ Tester(AttributeList attributes, boolean expectedValue) {
+ this.attributes = attributes;
+ this.expectedValue = expectedValue;
+ }
+
+ Tester(String[] names, Object[] values, boolean expectedValue) {
+ this(makeAttributeList(names, values), expectedValue);
+ }
+
+ private static AttributeList makeAttributeList(
+ String[] names, Object[] values) {
+ if (names.length != values.length)
+ throw new Error("Test bug: names and values different length");
+ AttributeList list = new AttributeList();
+ for (int i = 0; i < names.length; i++)
+ list.add(new Attribute(names[i], values[i]));
+ return list;
+ }
+
+ public Object getAttribute(String attribute)
+ throws AttributeNotFoundException {
+ for (Attribute a : attributes.asList()) {
+ if (a.getName().equals(attribute))
+ return a.getValue();
+ }
+ throw new AttributeNotFoundException(attribute);
+ }
+
+ public void setAttribute(Attribute attribute) {
+ throw new UnsupportedOperationException();
+ }
+
+ public AttributeList getAttributes(String[] attributes) {
+ AttributeList list = new AttributeList();
+ for (String attribute : attributes) {
+ try {
+ list.add(new Attribute(attribute, getAttribute(attribute)));
+ } catch (AttributeNotFoundException e) {
+ // OK: ignore, per semantics of getAttributes
+ }
+ }
+ return list;
+ }
+
+ public AttributeList setAttributes(AttributeList attributes) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object invoke(String actionName, Object[] params, String[] signature) {
+ throw new UnsupportedOperationException();
+ }
+
+ public MBeanInfo getMBeanInfo() {
+ MBeanAttributeInfo mbais[] = new MBeanAttributeInfo[attributes.size()];
+ for (int i = 0; i < mbais.length; i++) {
+ Attribute attr = attributes.asList().get(i);
+ String name = attr.getName();
+ Object value = attr.getValue();
+ String type =
+ ((value == null) ? new Object() : value).getClass().getName();
+ mbais[i] = new MBeanAttributeInfo(
+ name, type, name, true, false, false);
+ }
+ return new MBeanInfo(
+ getClass().getName(), "descr", mbais, null, null, null);
+ }
+
+ public boolean expectedValue() {
+ return expectedValue;
+ }
+
+ @Override
+ public String toString() {
+ return attributes.toString();
+ }
+ }
+
+ // Method rather than field, to avoid circular init dependencies
+ private static String[] abcd() {
+ return new String[] {"A", "B", "C", "D"};
+ }
+
+ private static String[] onetwo() {
+ return new String[] {"One", "two"};
+ }
+
+ private static Object expectTrue(Object... attrs) {
+ return newTester(abcd(), attrs, true);
+ }
+
+ private static Object expectFalse(Object... attrs) {
+ return newTester(abcd(), attrs, false);
+ }
+
+ private static Object expectTrueOneTwo(Object... attrs) {
+ return newTester(onetwo(), attrs, true);
+ }
+
+ private static Object expectFalseOneTwo(Object... attrs) {
+ return newTester(onetwo(), attrs, false);
+ }
+
+ private static Object newTester(String[] names, Object[] attrs, boolean expect) {
+ AttributeList list = new AttributeList();
+ for (int i = 0; i < attrs.length; i++)
+ list.add(new Attribute(names[i], attrs[i]));
+ return new Tester(list, expect);
+ }
+}