6602310: Extensions to Query API for JMX 2.0

6604768: IN queries require their arguments to be constants

New JMX query language and support for dotted attributes in queries.

Reviewed-by: dfuchs
This commit is contained in:
Eamonn McManus 2008-03-03 10:32:38 +01:00
parent 1fd0bb2370
commit 104cc86359
24 changed files with 2488 additions and 149 deletions

View File

@ -43,6 +43,13 @@ import javax.management.MBeanInfo;
import javax.management.NotCompliantMBeanException;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.remote.util.EnvHelp;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import javax.management.AttributeNotFoundException;
import javax.management.openmbean.CompositeData;
/**
* This class contains the methods for performing all the tests needed to verify
@ -482,4 +489,33 @@ public class Introspector {
return null;
}
public static Object elementFromComplex(Object complex, String element)
throws AttributeNotFoundException {
try {
if (complex.getClass().isArray() && element.equals("length")) {
return Array.getLength(complex);
} else if (complex instanceof CompositeData) {
return ((CompositeData) complex).get(element);
} else {
// Java Beans introspection
//
BeanInfo bi = java.beans.Introspector.getBeanInfo(complex.getClass());
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
for (PropertyDescriptor pd : pds)
if (pd.getName().equals(element))
return pd.getReadMethod().invoke(complex);
throw new AttributeNotFoundException(
"Could not find the getter method for the property " +
element + " 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);
}
}
}

View File

@ -104,4 +104,25 @@ class AndQueryExp extends QueryEval implements QueryExp {
return "(" + exp1 + ") and (" + exp2 + ")";
}
}
@Override
String toQueryString() {
// Parentheses are only added if needed to disambiguate.
return parens(exp1) + " and " + parens(exp2);
}
// Add parens if needed to disambiguate an expression such as
// Query.and(Query.or(a, b), c). We need to return
// (a or b) and c
// in such a case, because
// a or b and c
// would mean
// a or (b and c)
private static String parens(QueryExp exp) {
String s = Query.toString(exp);
if (exp instanceof OrQueryExp)
return "(" + s + ")";
else
return s;
}
}

View File

@ -26,12 +26,17 @@
package javax.management;
// RI import
import javax.management.MBeanServer;
import com.sun.jmx.mbeanserver.Introspector;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* Represents attributes used as arguments to relational constraints.
* An <CODE>AttributeValueExp</CODE> may be used anywhere a <CODE>ValueExp</CODE> is required.
* <p>Represents attributes used as arguments to relational constraints.
* Instances of this class are usually obtained using {@link Query#attr(String)
* Query.attr}.</p>
*
* <p>An <CODE>AttributeValueExp</CODE> may be used anywhere a
* <CODE>ValueExp</CODE> is required.
*
* @since 1.5
*/
@ -46,6 +51,8 @@ public class AttributeValueExp implements ValueExp {
*/
private String attr;
private transient int dotIndex;
/**
* An <code>AttributeValueExp</code> with a null attribute.
* @deprecated An instance created with this constructor cannot be
@ -64,6 +71,18 @@ public class AttributeValueExp implements ValueExp {
*/
public AttributeValueExp(String attr) {
this.attr = attr;
setDotIndex();
}
private void setDotIndex() {
if (attr != null)
dotIndex = attr.indexOf('.');
}
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException {
in.defaultReadObject();
setDotIndex();
}
/**
@ -76,7 +95,13 @@ public class AttributeValueExp implements ValueExp {
}
/**
* Applies the <CODE>AttributeValueExp</CODE> on an MBean.
* <p>Applies the <CODE>AttributeValueExp</CODE> on an MBean.
* This method calls {@link #getAttribute getAttribute(name)} and wraps
* the result as a {@code ValueExp}. The value returned by
* {@code getAttribute} must be a {@code Number}, {@code String},
* or {@code Boolean}; otherwise this method throws a
* {@code BadAttributeValueExpException}, which will cause
* the containing query to be false for this {@code name}.</p>
*
* @param name The name of the MBean on which the <CODE>AttributeValueExp</CODE> will be applied.
*
@ -88,6 +113,7 @@ public class AttributeValueExp implements ValueExp {
* @exception BadBinaryOpValueExpException
*
*/
@Override
public ValueExp apply(ObjectName name) throws BadStringOperationException, BadBinaryOpValueExpException,
BadAttributeValueExpException, InvalidApplicationException {
Object result = getAttribute(name);
@ -106,8 +132,9 @@ public class AttributeValueExp implements ValueExp {
/**
* Returns the string representing its value.
*/
@Override
public String toString() {
return attr;
return QueryParser.quoteId(attr);
}
@ -115,18 +142,38 @@ public class AttributeValueExp implements ValueExp {
* Sets the MBean server on which the query is to be performed.
*
* @param s The MBean server on which the query is to be performed.
*
* @deprecated This method has no effect. The MBean Server used to
* obtain an attribute value is {@link QueryEval#getMBeanServer()}.
*/
/* There is no need for this method, because if a query is being
evaluted an AttributeValueExp can only appear inside a QueryExp,
and that QueryExp will itself have done setMBeanServer. */
@Deprecated
@Override
public void setMBeanServer(MBeanServer s) {
}
/**
* Return the value of the given attribute in the named MBean.
* <p>Return the value of the given attribute in the named MBean.
* If the attempt to access the attribute generates an exception,
* return null.
* return null.</p>
*
* <p>Let <em>n</em> be the {@linkplain #getAttributeName attribute
* name}. Then this method proceeds as follows. First it calls
* {@link MBeanServer#getAttribute getAttribute(name, <em>n</em>)}. If that
* generates an {@link AttributeNotFoundException}, and if <em>n</em>
* contains at least one dot ({@code .}), then the method calls {@code
* getAttribute(name, }<em>n</em>{@code .substring(0, }<em>n</em>{@code
* .indexOf('.')))}; in other words it calls {@code getAttribute}
* with the substring of <em>n</em> before the first dot. Then it
* extracts a component from the retrieved value, as described in the <a
* href="monitor/package-summary.html#complex">documentation for the {@code
* monitor} package</a>.</p>
*
* <p>The MBean Server used is the one returned by {@link
* QueryEval#getMBeanServer()}.</p>
*
* @param name the name of the MBean whose attribute is to be returned.
*
@ -139,10 +186,34 @@ public class AttributeValueExp implements ValueExp {
MBeanServer server = QueryEval.getMBeanServer();
try {
return server.getAttribute(name, attr);
} catch (AttributeNotFoundException e) {
if (dotIndex < 0)
throw e;
}
String toGet = attr.substring(0, dotIndex);
Object value = server.getAttribute(name, toGet);
return extractElement(value, attr.substring(dotIndex + 1));
} catch (Exception re) {
return null;
}
}
private Object extractElement(Object value, String elementWithDots)
throws AttributeNotFoundException {
while (true) {
int dot = elementWithDots.indexOf('.');
String element = (dot < 0) ?
elementWithDots : elementWithDots.substring(0, dot);
value = Introspector.elementFromComplex(value, element);
if (dot < 0)
return value;
elementWithDots = elementWithDots.substring(dot + 1);
}
}
}

View File

@ -109,34 +109,25 @@ class BetweenQueryExp extends QueryEval implements QueryExp {
ValueExp val1 = exp1.apply(name);
ValueExp val2 = exp2.apply(name);
ValueExp val3 = exp3.apply(name);
String sval1;
String sval2;
String sval3;
double dval1;
double dval2;
double dval3;
long lval1;
long lval2;
long lval3;
boolean numeric = val1 instanceof NumericValueExp;
if (numeric) {
if (((NumericValueExp)val1).isLong()) {
lval1 = ((NumericValueExp)val1).longValue();
lval2 = ((NumericValueExp)val2).longValue();
lval3 = ((NumericValueExp)val3).longValue();
long lval1 = ((NumericValueExp)val1).longValue();
long lval2 = ((NumericValueExp)val2).longValue();
long lval3 = ((NumericValueExp)val3).longValue();
return lval2 <= lval1 && lval1 <= lval3;
} else {
dval1 = ((NumericValueExp)val1).doubleValue();
dval2 = ((NumericValueExp)val2).doubleValue();
dval3 = ((NumericValueExp)val3).doubleValue();
double dval1 = ((NumericValueExp)val1).doubleValue();
double dval2 = ((NumericValueExp)val2).doubleValue();
double dval3 = ((NumericValueExp)val3).doubleValue();
return dval2 <= dval1 && dval1 <= dval3;
}
} else {
sval1 = ((StringValueExp)val1).toString();
sval2 = ((StringValueExp)val2).toString();
sval3 = ((StringValueExp)val3).toString();
String sval1 = ((StringValueExp)val1).getValue();
String sval2 = ((StringValueExp)val2).getValue();
String sval3 = ((StringValueExp)val3).getValue();
return sval2.compareTo(sval1) <= 0 && sval1.compareTo(sval3) <= 0;
}
}
@ -148,4 +139,8 @@ class BetweenQueryExp extends QueryEval implements QueryExp {
return "(" + exp1 + ") between (" + exp2 + ") and (" + exp3 + ")";
}
}
@Override
String toQueryString() {
return exp1 + " between " + exp2 + " and " + exp3;
}
}

View File

@ -167,12 +167,74 @@ class BinaryOpValueExp extends QueryEval implements ValueExp {
*/
public String toString() {
try {
return exp1 + " " + opString() + " " + exp2;
return parens(exp1, true) + " " + opString() + " " + parens(exp2, false);
} catch (BadBinaryOpValueExpException ex) {
return "invalid expression";
}
}
/*
* Add parentheses to the given subexpression if necessary to
* preserve meaning. Suppose this BinaryOpValueExp is
* Query.times(Query.plus(Query.attr("A"), Query.attr("B")), Query.attr("C")).
* Then the original toString() logic would return A + B * C.
* We check precedences in order to return (A + B) * C, which is the
* meaning of the ValueExp.
*
* We need to add parentheses if the unparenthesized expression would
* be parsed as a different ValueExp from the original.
* We cannot omit parentheses even when mathematically
* the result would be equivalent, because we do not know whether the
* numeric values will be integer or floating-point. Addition and
* multiplication are associative for integers but not always for
* floating-point.
*
* So the rule is that we omit parentheses if the ValueExp
* is (A op1 B) op2 C and the precedence of op1 is greater than or
* equal to that of op2; or if the ValueExp is A op1 (B op2 C) and
* the precedence of op2 is greater than that of op1. (There are two
* precedences: that of * and / is greater than that of + and -.)
* The case of (A op1 B) op2 (C op3 D) applies each rule in turn.
*
* The following examples show the rules in action. On the left,
* the original ValueExp. On the right, the string representation.
*
* (A + B) + C A + B + C
* (A * B) + C A * B + C
* (A + B) * C (A + B) * C
* (A * B) * C A * B * C
* A + (B + C) A + (B + C)
* A + (B * C) A + B * C
* A * (B + C) A * (B + C)
* A * (B * C) A * (B * C)
*/
private String parens(ValueExp subexp, boolean left)
throws BadBinaryOpValueExpException {
boolean omit;
if (subexp instanceof BinaryOpValueExp) {
int subop = ((BinaryOpValueExp) subexp).op;
if (left)
omit = (precedence(subop) >= precedence(op));
else
omit = (precedence(subop) > precedence(op));
} else
omit = true;
if (omit)
return subexp.toString();
else
return "(" + subexp + ")";
}
private int precedence(int xop) throws BadBinaryOpValueExpException {
switch (xop) {
case Query.PLUS: case Query.MINUS: return 0;
case Query.TIMES: case Query.DIV: return 1;
default:
throw new BadBinaryOpValueExpException(this);
}
}
private String opString() throws BadBinaryOpValueExpException {
switch (op) {
case Query.PLUS:
@ -188,4 +250,10 @@ class BinaryOpValueExp extends QueryEval implements ValueExp {
throw new BadBinaryOpValueExpException(this);
}
@Deprecated
public void setMBeanServer(MBeanServer s) {
super.setMBeanServer(s);
}
}

View File

@ -108,20 +108,12 @@ class BinaryRelQueryExp extends QueryEval implements QueryExp {
BadAttributeValueExpException, InvalidApplicationException {
Object val1 = exp1.apply(name);
Object val2 = exp2.apply(name);
String sval1;
String sval2;
double dval1;
double dval2;
long lval1;
long lval2;
boolean bval1;
boolean bval2;
boolean numeric = val1 instanceof NumericValueExp;
boolean bool = val1 instanceof BooleanValueExp;
if (numeric) {
if (((NumericValueExp)val1).isLong()) {
lval1 = ((NumericValueExp)val1).longValue();
lval2 = ((NumericValueExp)val2).longValue();
long lval1 = ((NumericValueExp)val1).longValue();
long lval2 = ((NumericValueExp)val2).longValue();
switch (relOp) {
case Query.GT:
@ -136,8 +128,8 @@ class BinaryRelQueryExp extends QueryEval implements QueryExp {
return lval1 == lval2;
}
} else {
dval1 = ((NumericValueExp)val1).doubleValue();
dval2 = ((NumericValueExp)val2).doubleValue();
double dval1 = ((NumericValueExp)val1).doubleValue();
double dval2 = ((NumericValueExp)val2).doubleValue();
switch (relOp) {
case Query.GT:
@ -155,8 +147,8 @@ class BinaryRelQueryExp extends QueryEval implements QueryExp {
} else if (bool) {
bval1 = ((BooleanValueExp)val1).getValue().booleanValue();
bval2 = ((BooleanValueExp)val2).getValue().booleanValue();
boolean bval1 = ((BooleanValueExp)val1).getValue().booleanValue();
boolean bval2 = ((BooleanValueExp)val2).getValue().booleanValue();
switch (relOp) {
case Query.GT:
@ -172,8 +164,8 @@ class BinaryRelQueryExp extends QueryEval implements QueryExp {
}
} else {
sval1 = ((StringValueExp)val1).getValue();
sval2 = ((StringValueExp)val2).getValue();
String sval1 = ((StringValueExp)val1).getValue();
String sval2 = ((StringValueExp)val2).getValue();
switch (relOp) {
case Query.GT:
@ -199,6 +191,11 @@ class BinaryRelQueryExp extends QueryEval implements QueryExp {
return "(" + exp1 + ") " + relOpString() + " (" + exp2 + ")";
}
@Override
String toQueryString() {
return exp1 + " " + relOpString() + " " + exp2;
}
private String relOpString() {
switch (relOp) {
case Query.GT:

View File

@ -84,4 +84,10 @@ class BooleanValueExp extends QueryEval implements ValueExp {
return this;
}
@Deprecated
public void setMBeanServer(MBeanServer s) {
super.setMBeanServer(s);
}
}

View File

@ -91,21 +91,23 @@ class InQueryExp extends QueryEval implements QueryExp {
* @exception BadAttributeValueExpException
* @exception InvalidApplicationException
*/
public boolean apply(ObjectName name) throws BadStringOperationException, BadBinaryOpValueExpException,
public boolean apply(ObjectName name)
throws BadStringOperationException, BadBinaryOpValueExpException,
BadAttributeValueExpException, InvalidApplicationException {
if (valueList != null) {
ValueExp v = val.apply(name);
boolean numeric = v instanceof NumericValueExp;
for (int i = 0; i < valueList.length; i++) {
for (ValueExp element : valueList) {
element = element.apply(name);
if (numeric) {
if (((NumericValueExp)valueList[i]).doubleValue() ==
((NumericValueExp)v).doubleValue()) {
if (((NumericValueExp) element).doubleValue() ==
((NumericValueExp) v).doubleValue()) {
return true;
}
} else {
if (((StringValueExp)valueList[i]).getValue().equals(
((StringValueExp)v).getValue())) {
if (((StringValueExp) element).getValue().equals(
((StringValueExp) v).getValue())) {
return true;
}
}

View File

@ -113,7 +113,32 @@ class MatchQueryExp extends QueryEval implements QueryExp {
}
private static String likeTranslate(String s) {
return s.replace('?', '_').replace('*', '%');
StringBuilder sb = new StringBuilder();
int c;
for (int i = 0; i < s.length(); i += Character.charCount(c)) {
c = s.codePointAt(i);
switch (c) {
case '\\':
i += Character.charCount(c);
sb.append('\\');
if (i < s.length()) {
c = s.codePointAt(i);
sb.appendCodePoint(c);
}
break;
case '*':
sb.append('%'); break;
case '?':
sb.append('_'); break;
case '%':
sb.append("\\%"); break;
case '_':
sb.append("\\_"); break;
default:
sb.appendCodePoint(c); break;
}
}
return sb.toString();
}
/*

View File

@ -86,8 +86,14 @@ class NotQueryExp extends QueryEval implements QueryExp {
/**
* Returns the string representing the object.
*/
@Override
public String toString() {
return "not (" + exp + ")";
}
@Override
String toQueryString() {
return "not (" + Query.toString(exp) + ")";
}
}

View File

@ -151,11 +151,18 @@ class NumericValueExp extends QueryEval implements ValueExp {
* Returns the string representing the object
*/
public String toString() {
if (val == null)
return "null";
if (val instanceof Long || val instanceof Integer)
{
return String.valueOf(val.longValue());
return Long.toString(val.longValue());
}
return String.valueOf(val.doubleValue());
double d = val.doubleValue();
if (Double.isInfinite(d))
return (d > 0) ? "(1.0 / 0.0)" : "(-1.0 / 0.0)";
if (Double.isNaN(d))
return "(0.0 / 0.0)";
return Double.toString(d);
}
/**
@ -244,4 +251,10 @@ class NumericValueExp extends QueryEval implements ValueExp {
out.defaultWriteObject();
}
}
@Deprecated
public void setMBeanServer(MBeanServer s) {
super.setMBeanServer(s);
}
}

View File

@ -222,7 +222,8 @@ import javax.management.QueryExp;
* @since 1.5
*/
@SuppressWarnings("serial") // don't complain serialVersionUID not constant
public class ObjectName implements Comparable<ObjectName>, QueryExp {
public class ObjectName extends ToQueryString
implements Comparable<ObjectName>, QueryExp {
/**
* A structure recording property structure and
@ -1779,10 +1780,16 @@ public class ObjectName implements Comparable<ObjectName>, QueryExp {
*
* @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

View File

@ -98,9 +98,29 @@ class OrQueryExp extends QueryEval implements QueryExp {
}
/**
* 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;
}
}

View File

@ -27,9 +27,11 @@ package javax.management;
/**
* This class represents indexed attributes used as arguments to relational
* constraints. An QualifiedAttributeValueExp may be used anywhere a
* ValueExp is required.
* <p>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 @@ class QualifiedAttributeValueExp extends AttributeValueExp {
/**
* Basic Constructor.
* @deprecated see {@link AttributeValueExp#AttributeValueExp()}
*/
@Deprecated
public QualifiedAttributeValueExp() {
}
@ -81,6 +85,7 @@ class QualifiedAttributeValueExp extends AttributeValueExp {
* @exception BadAttributeValueExpException
* @exception InvalidApplicationException
*/
@Override
public ValueExp apply(ObjectName name) throws BadStringOperationException, BadBinaryOpValueExpException,
BadAttributeValueExpException, InvalidApplicationException {
try {
@ -105,9 +110,11 @@ class QualifiedAttributeValueExp extends AttributeValueExp {
/**
* 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();
}

View File

@ -27,19 +27,346 @@ package javax.management;
/**
* <p>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:</p>
* <p>Constructs query object constraints.</p>
*
* <p>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.</p>
*
* <p>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
* <a href="#ql">below</a> 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.</p>
*
* <p>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:</p>
*
* <pre>
* QueryExp exp = Query.and(Query.gt(Query.attr("age"),Query.value(5)),
* Query.match(Query.attr("name"),
* Query.value("Smith")));
* QueryExp query =
* Query.and(Query.eq(Query.attr("Enabled"), Query.value(true)),
* Query.eq(Query.attr("Owner"), Query.value("Duke")));
* </pre>
*
* <p>Here is how you could construct the same {@code QueryExp} using the
* Query Language:</p>
*
* <pre>
* QueryExp query = Query.fromString("Enabled = true and Owner = 'Duke'");
* </pre>
*
* <p>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.</p>
*
*
* <h4 id="ql">Query Language</h4>
*
* <p>The query language is closely modeled on the WHERE clause of
* SQL SELECT statements. The formal specification of the language
* appears <a href="#formal-ql">below</a>, but it is probably easier to
* understand it with examples such as the following.</p>
*
* <dl>
* <dt>{@code Message = 'OK'}
* <dd>Selects MBeans that have a {@code Message} attribute whose value
* is the string {@code OK}.
*
* <dt>{@code FreeSpacePercent < 10}
* <dd>Selects MBeans that have a {@code FreeSpacePercent} attribute whose
* value is a number less than 10.
*
* <dt>{@code FreeSpacePercent < 10 and WarningSent = false}
* <dd>Selects the same MBeans as the previous example, but they must
* also have a boolean attribute {@code WarningSent} whose value
* is false.
*
* <dt>{@code SpaceUsed > TotalSpace * (2.0 / 3.0)}
* <dd>Selects MBeans that have {@code SpaceUsed} and {@code TotalSpace}
* attributes where the first is more than two-thirds the second.
*
* <dt>{@code not (FreeSpacePercent between 10 and 90)}
* <dd>Selects MBeans that have a {@code FreeSpacePercent} attribute whose
* value is not between 10 and 90, inclusive.
*
* <dt>{@code FreeSpacePercent not between 10 and 90}
* <dd>Another way of writing the previous query.
*
* <dt>{@code Status in ('STOPPED', 'STARTING', 'STARTED')}
* <dd>Selects MBeans that have a {@code Status} attribute whose value
* is one of those three strings.
*
* <dt>{@code Message like 'OK: %'}
* <dd>Selects MBeans that have a {@code Message} attribute whose value
* is a string beginning with {@code "OK: "}. <b>Notice that the
* wildcard characters are SQL's ones.</b> 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.
*
* <dt>{@code instanceof 'javax.management.NotificationBroadcaster'}
* <dd>Selects MBeans that are instances of
* {@link javax.management.NotificationBroadcaster}, as reported by
* {@link javax.management.MBeanServer#isInstanceOf MBeanServer.isInstanceOf}.
*
* <dt>{@code like 'mydomain:*'}
* <dd>Selects MBeans whose {@link ObjectName}s have the domain {@code mydomain}.
*
* </dl>
*
* <p>The last two examples do not correspond to valid SQL syntax, but all
* the others do.</p>
*
* <p>The remainder of this description is a formal specification of the
* query language.</p>
*
*
* <h4 id="formal-ql">Lexical elements</h4>
*
* <p>Keywords such as <b>and</b>, <b>like</b>, and <b>between</b> are not
* case sensitive. You can write <b>between</b>, <b>BETWEEN</b>, or
* <b>BeTwEeN</b> with the same effect.</p>
*
* <p>On the other hand, attribute names <i>are</i> case sensitive. The
* attribute {@code Name} is not the same as the attribute {@code name}.</p>
*
* <p>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}.
*
* <p>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'}.</p>
*
* <p>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)}.</p>
*
* <p>Floating-point constants are written using the Java syntax. A
* floating-point constant must be a valid input to
* {@link Double#valueOf(String)}.</p>
*
* <p>A boolean constant is either {@code true} or {@code false}, ignoring
* case.</p>
*
* <p>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<b
* and 5=c}, but no further spaces can be removed.</p>
*
*
* <h4 id="grammar-ql">Grammar</h4>
*
* <dl>
* <dt id="query">query:
* <dd><a href="#andquery">andquery</a> [<b>OR</b> <a href="#query">query</a>]
*
* <dt id="andquery">andquery:
* <dd><a href="#predicate">predicate</a> [<b>AND</b> <a href="#andquery">andquery</a>]
*
* <dt id="predicate">predicate:
* <dd><b>(</b> <a href="#query">query</a> <b>)</b> |<br>
* <b>NOT</b> <a href="#predicate">predicate</a> |<br>
* <b>INSTANCEOF</b> <a href="#stringvalue">stringvalue</a> |<br>
* <b>LIKE</b> <a href="#objectnamepattern">objectnamepattern</a> |<br>
* <a href="#value">value</a> <a href="#predrhs">predrhs</a>
*
* <dt id="predrhs">predrhs:
* <dd><a href="#compare">compare</a> <a href="#value">value</a> |<br>
* [<b>NOT</b>] <b>BETWEEN</b> <a href="#value">value</a> <b>AND</b>
* <a href="#value">value</a> |<br>
* [<b>NOT</b>] <b>IN (</b> <a href="#value">value</a>
* <a href="#commavalues">commavalues</a> <b>)</b> |<br>
* [<b>NOT</b>] <b>LIKE</b> <a href="#stringvalue">stringvalue</a>
*
* <dt id="commavalues">commavalues:
* <dd>[ <b>,</b> <a href="#value">value</a> <a href="#commavalues">commavalues</a> ]
*
* <dt id="compare">compare:
* <dd><b>=</b> | <b>&lt;</b> | <b>&gt;</b> |
* <b>&lt;=</b> | <b>&gt;=</b> | <b>&lt;&gt;</b> | <b>!=</b>
*
* <dt id="value">value:
* <dd><a href="#factor">factor</a> [<a href="#plusorminus">plusorminus</a>
* <a href="#value">value</a>]
*
* <dt id="plusorminus">plusorminus:
* <dd><b>+</b> | <b>-</b>
*
* <dt id="factor">factor:
* <dd><a href="#term">term</a> [<a href="#timesordivide">timesordivide</a>
* <a href="#factor">factor</a>]
*
* <dt id="timesordivide">timesordivide:
* <dd><b>*</b> | <b>/</b>
*
* <dt id="term">term:
* <dd><a href="#attr">attr</a> | <a href="#literal">literal</a> |
* <b>(</b> <a href="#value">value</a> <b>)</b>
*
* <dt id="attr">attr:
* <dd><a href="#name">name</a> [<b>#</b> <a href="#name">name</a>]
*
* <dt id="name">name:
* <dd><a href="#identifier">identifier</a> [<b>.</b><a href="#name">name</a>]
*
* <dt id="identifier">identifier:
* <dd><i>Java-identifier</i> | <i>double-quoted-identifier</i>
*
* <dt id="literal">literal:
* <dd><a href="#booleanlit">booleanlit</a> | <i>longlit</i> |
* <i>doublelit</i> | <i>stringlit</i>
*
* <dt id="booleanlit">booleanlit:
* <dd><b>FALSE</b> | <b>TRUE</b>
*
* <dt id="stringvalue">stringvalue:
* <dd><i>stringlit</i>
*
* <dt id="objectnamepattern">objectnamepattern:
* <dd><i>stringlit</i>
*
* </dl>
*
*
* <h4>Semantics</h4>
*
* <p>The meaning of the grammar is described in the table below.
* This defines a function <i>q</i> that maps a string to a Java object
* such as a {@link QueryExp} or a {@link ValueExp}.</p>
*
* <table border="1" cellpadding="5">
* <tr><th>String <i>s</i></th><th><i>q(s)</th></tr>
*
* <tr><td><i>query1</i> <b>OR</b> <i>query2</i>
* <td>{@link Query#or Query.or}(<i>q(query1)</i>, <i>q(query2)</i>)
*
* <tr><td><i>query1</i> <b>AND</b> <i>query2</i>
* <td>{@link Query#and Query.and}(<i>q(query1)</i>, <i>q(query2)</i>)
*
* <tr><td><b>(</b> <i>queryOrValue</i> <b>)</b>
* <td><i>q(queryOrValue)</i>
*
* <tr><td><b>NOT</b> <i>query</i>
* <td>{@link Query#not Query.not}(<i>q(query)</i>)
*
* <tr><td><b>INSTANCEOF</b> <i>stringLiteral</i>
* <td>{@link Query#isInstanceOf Query.isInstanceOf}(<!--
* -->{@link Query#value(String) Query.value}(<i>q(stringLiteral)</i>))
*
* <tr><td><b>LIKE</b> <i>stringLiteral</i>
* <td>{@link ObjectName#ObjectName(String) new ObjectName}(<!--
* --><i>q(stringLiteral)</i>)
*
* <tr><td><i>value1</i> <b>=</b> <i>value2</i>
* <td>{@link Query#eq Query.eq}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>&lt;</b> <i>value2</i>
* <td>{@link Query#lt Query.lt}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>&gt;</b> <i>value2</i>
* <td>{@link Query#gt Query.gt}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>&lt;=</b> <i>value2</i>
* <td>{@link Query#leq Query.leq}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>&gt;=</b> <i>value2</i>
* <td>{@link Query#geq Query.geq}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>&lt;&gt;</b> <i>value2</i>
* <td>{@link Query#not Query.not}({@link Query#eq Query.eq}(<!--
* --><i>q(value1)</i>, <i>q(value2)</i>))
*
* <tr><td><i>value1</i> <b>!=</b> <i>value2</i>
* <td>{@link Query#not Query.not}({@link Query#eq Query.eq}(<!--
* --><i>q(value1)</i>, <i>q(value2)</i>))
*
* <tr><td><i>value1</i> <b>BETWEEN</b> <i>value2</i> AND <i>value3</i>
* <td>{@link Query#between Query.between}(<i>q(value1)</i>,
* <i>q(value2)</i>, <i>q(value3)</i>)
*
* <tr><td><i>value1</i> <b>NOT BETWEEN</b> <i>value2</i> AND <i>value3</i>
* <td>{@link Query#not Query.not}({@link Query#between Query.between}(<!--
* --><i>q(value1)</i>, <i>q(value2)</i>, <i>q(value3)</i>))
*
* <tr><td><i>value1</i> <b>IN (</b> <i>value2</i>, <i>value3</i> <b>)</b>
* <td>{@link Query#in Query.in}(<i>q(value1)</i>,
* <code>new ValueExp[] {</code>
* <i>q(value2)</i>, <i>q(value3)</i><code>}</code>)
*
* <tr><td><i>value1</i> <b>NOT IN (</b> <i>value2</i>, <i>value3</i> <b>)</b>
* <td>{@link Query#not Query.not}({@link Query#in Query.in}(<i>q(value1)</i>,
* <code>new ValueExp[] {</code>
* <i>q(value2)</i>, <i>q(value3)</i><code>}</code>))
*
* <tr><td><i>value</i> <b>LIKE</b> <i>stringLiteral</i>
* <td>{@link Query#match Query.match}(<i>q(value)</i>,
* <i><a href="#translateWildcards">translateWildcards</a>(q(stringLiteral))</i>)
*
* <tr><td><i>value</i> <b>NOT LIKE</b> <i>stringLiteral</i>
* <td>{@link Query#not Query.not}({@link Query#match Query.match}(<i>q(value)</i>,
* <i><a href="#translateWildcards">translateWildcards</a>(q(stringLiteral))</i>))
*
* <tr><td><i>value1</i> <b>+</b> <i>value2</i>
* <td>{@link Query#plus Query.plus}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>-</b> <i>value2</i>
* <td>{@link Query#minus Query.minus}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>*</b> <i>value2</i>
* <td>{@link Query#times Query.times}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>value1</i> <b>/</b> <i>value2</i>
* <td>{@link Query#div Query.div}(<i>q(value1)</i>, <i>q(value2)</i>)
*
* <tr><td><i>name</i>
* <td>{@link Query#attr(String) Query.attr}(<i>q(name)</i>)
*
* <tr><td><i>name1<b>#</b>name2</i>
* <td>{@link Query#attr(String,String) Query.attr}(<i>q(name1)</i>,
* <i>q(name2)</i>)
*
* <tr><td><b>FALSE</b>
* <td>{@link Query#value(boolean) Query.value}(false)
*
* <tr><td><b>TRUE</b>
* <td>{@link Query#value(boolean) Query.value}(true)
*
* <tr><td><i>decimalLiteral</i>
* <td>{@link Query#value(long) Query.value}(<!--
* -->{@link Long#valueOf(String) Long.valueOf}(<i>decimalLiteral</i>))
*
* <tr><td><i>floatingPointLiteral</i>
* <td>{@link Query#value(double) Query.value}(<!--
* -->{@link Double#valueOf(String) Double.valueOf}(<!--
* --><i>floatingPointLiteral</i>))
* </table>
*
* <p id="translateWildcards">Here, <i>translateWildcards</i> 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
* <b>LIKE</b> 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}.</p>
*
* @since 1.5
*/
public class Query extends Object {
@ -277,16 +604,12 @@ package javax.management;
}
/**
* <p>Returns a new attribute expression.</p>
*
* <p>Evaluating this expression for a given
* <code>objectName</code> includes performing {@link
* MBeanServer#getAttribute MBeanServer.getAttribute(objectName,
* name)}.</p>
* <p>Returns a new attribute expression. See {@link AttributeValueExp}
* for a detailed description of the semantics of the expression.</p>
*
* @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);
@ -627,6 +950,63 @@ package javax.management;
return new InstanceOfQueryExp(classNameValue);
}
/**
* <p>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}.</p>
*
* <p>(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.)</p>
*
* <p>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.</p>
*
* @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();
}
/**
* <p>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}.</p>
*
* @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.

View File

@ -38,7 +38,7 @@ import javax.management.MBeanServer;
*
* @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;

View File

@ -30,9 +30,9 @@ import java.io.Serializable;
/**
* <p>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.</p>
* <p>Represents relational constraints similar to database query "where
* clauses". Instances of QueryExp are returned by the static methods of the
* {@link Query} class.</p>
*
* <p>It is possible, but not
* recommended, to create custom queries by implementing this
@ -40,6 +40,7 @@ import java.io.Serializable;
* 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 {

View File

@ -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;
/**
* <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.isEmpty())
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();
String s = sve.getValue();
q = Query.match(alhs, patternValueExp(s));
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);
}
// 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);
}
}

View File

@ -73,7 +73,7 @@ public class StringValueExp implements ValueExp {
* Returns the string representing the object.
*/
public String toString() {
return "'" + val + "'";
return "'" + val.replace("'", "''") + "'";
}

View File

@ -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();
}
}

View File

@ -27,13 +27,8 @@ package javax.management.monitor;
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.NotificationBroadcasterSupport;
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 @@ public abstract class Monitor
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) {

View File

@ -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 <T> AttrWithDot(Object impl, Class<T> 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<ObjectName> 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);
}
}

View File

@ -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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
(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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
}
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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 @@ public class QueryExpStringTest {
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);
}

View File

@ -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<ObjectName> 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);
}
}