6701498: Change JMX query language to use * and ? as wildcards rather than % and _
Reviewed-by: dfuchs
This commit is contained in:
parent
0ad6d3770b
commit
e01cfb7fa8
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1781,7 +1781,7 @@ public class ObjectName implements Comparable<ObjectName>, QueryExp {
|
||||
}
|
||||
|
||||
String toQueryString() {
|
||||
return "LIKE " + Query.value(toString());
|
||||
return "like " + Query.value(toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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"},
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user