6701498: Change JMX query language to use * and ? as wildcards rather than % and _

Reviewed-by: dfuchs
This commit is contained in:
Eamonn McManus 2008-06-05 13:40:09 +02:00
parent 0ad6d3770b
commit e01cfb7fa8
7 changed files with 35 additions and 134 deletions

View File

@ -109,36 +109,7 @@ class MatchQueryExp extends QueryEval implements QueryExp {
* Returns the string representing the object
*/
public String toString() {
return exp + " like " + new StringValueExp(likeTranslate(pattern));
}
private static String likeTranslate(String s) {
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();
return exp + " like " + new StringValueExp(pattern);
}
/*

View File

@ -1781,7 +1781,7 @@ public class ObjectName implements Comparable<ObjectName>, QueryExp {
}
String toQueryString() {
return "LIKE " + Query.value(toString());
return "like " + Query.value(toString());
}
/**

View File

@ -108,13 +108,13 @@ package javax.management;
* <dd>Selects MBeans that have a {@code Status} attribute whose value
* is one of those three strings.
*
* <dt>{@code Message like 'OK: %'}
* <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,
* wildcard characters are not the ones that SQL uses.</b> In SQL,
* {@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.
* means "any single character". Here, as in the rest of the JMX API,
* those are represented by {@code *} and {@code ?} respectively.
*
* <dt>{@code instanceof 'javax.management.NotificationBroadcaster'}
* <dd>Selects MBeans that are instances of
@ -319,11 +319,11 @@ package javax.management;
*
* <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>)
* <i>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>))
* <i>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>)
@ -360,13 +360,6 @@ package javax.management;
* --><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 {

View File

@ -43,12 +43,6 @@ import java.util.Set;
* on both the client and the server in the remote case, so using this class
* instead is recommended where possible.</p>
*
* <!-- <p>Because this class was introduced in version 2.0 of the JMX API,
* it may not be present on a remote JMX agent that is running an earlier
* version. The method {@link JMX#addListenerWithFilter JMX.addListenerWithFilter}
* can be used when you cannot be sure whether this class is present in the
* agent you are connecting to.</p> -->
*
* <p>This class uses the {@linkplain Query Query API} to specify the
* filtering logic. For example, to select only notifications where the
* {@linkplain Notification#getType() type} is {@code "com.example.mytype"},

View File

@ -490,8 +490,7 @@ class QueryParser {
}
AttributeValueExp alhs = (AttributeValueExp) lhs;
StringValueExp sve = stringvalue();
String s = sve.getValue();
q = Query.match(alhs, patternValueExp(s));
q = Query.match(alhs, sve);
break;
}
@ -624,40 +623,4 @@ class QueryParser {
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

@ -121,14 +121,14 @@ 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 '\\*%'",
initialPercent, "attr like '\\%%'",
any, "attr like '%simpleString%'",
anyStar, "attr like '%\\*%'",
anyPercent, "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))",
@ -207,7 +207,6 @@ public class QueryExpStringTest {
exp + " like " + pat);
}
StringValueExp spat = (StringValueExp) pat;
spat = Query.value(translateMatch(spat.getValue()));
return Query.match((AttributeValueExp) exp, spat);
}
@ -226,28 +225,6 @@ 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

View File

@ -347,30 +347,30 @@ public class QueryParseTest {
// LIKE
"A like 'b%m'",
"A like 'b*m'",
expectTrue("blim"), expectTrue("bm"),
expectFalse(""), expectFalse("blimmo"), expectFalse("mmm"),
"A not like 'b%m'",
"A not like 'b*m'",
expectFalse("blim"), expectFalse("bm"),
expectTrue(""), expectTrue("blimmo"), expectTrue("mmm"),
"A like 'b_m'",
"A like 'b?m'",
expectTrue("bim"), expectFalse("blim"),
"A like '%can''t%'",
"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("*asdf"), expectFalse("asdf*"),
"A LIKE '*%?_'",
expectTrue("*blim?!"), expectTrue("*?_"),
expectFalse("blim"), expectFalse("blim?"),
expectFalse("?*"), expectFalse("??"), expectFalse(""), expectFalse("?"),
"A LIKE '%*_?'",
expectTrue("%blim_?"), expectTrue("%_?"), expectTrue("%blim_!"),
expectFalse("blim"), expectFalse("blim_"),
expectFalse("_%"), expectFalse("??"), expectFalse(""), expectFalse("?"),
Query.toString(
Query.initialSubString(Query.attr("A"), Query.value("*?%_"))),
@ -483,7 +483,7 @@ public class QueryParseTest {
// note the little {} at the end which means this is a subclass
// and therefore QualifiedAttributeValue should return false.
MBeanServerDelegate.class.getName() + "#SpecificationName LIKE '%'",
MBeanServerDelegate.class.getName() + "#SpecificationName LIKE '*'",
new Wrapped(new MBeanServerDelegate(), true),
new Tester(new String[] {"SpecificationName"}, new Object[] {"JMX"}, false),
@ -497,7 +497,7 @@ public class QueryParseTest {
"A.class.name = 'java.lang.String'",
expectTrue("blim"), expectFalse(95), expectFalse((Object) null),
"A.canonicalName like 'JMImpl%:%'",
"A.canonicalName like 'JMImpl*:*'",
expectTrue(MBeanServerDelegate.DELEGATE_NAME),
expectFalse(ObjectName.WILDCARD),
@ -544,12 +544,15 @@ public class QueryParseTest {
"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 '%'",
"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'",
// "a like 'oops\\'", "a like '[oops'",
// We don't check the above because Query.match doesn't. If LIKE
// rejected bad patterns then there would be some QueryExp values
// that could not be converted to a string and back.
// 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