6616825: JMX query returns no value in 1.0 compatibility mode - deserialization bug in readObject()
Reviewed-by: emcmanus
/*
* Copyright 1999-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 com.sun.jmx.mbeanserver.GetPropertyAction;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* <p>Represents the object name of an MBean, or a pattern that can
* match the names of several MBeans. Instances of this class are
* immutable.</p>
*
* <p>An instance of this class can be used to represent:</p>
* <ul>
* <li>An object name</li>
* <li>An object name pattern, within the context of a query</li>
* </ul>
*
* <p>An object name consists of two parts, the domain and the key
* properties.</p>
*
* <p>The <em>domain</em> is a string of characters not including
* the character colon (<code>:</code>). It is recommended that the domain
* should not contain the string "{@code //}", which is reserved for future use.
*
* <p>If the domain includes at least one occurrence of the wildcard
* characters asterisk (<code>*</code>) or question mark
* (<code>?</code>), then the object name is a pattern. The asterisk
* matches any sequence of zero or more characters, while the question
* mark matches any single character.</p>
*
* <p>If the domain is empty, it will be replaced in certain contexts
* by the <em>default domain</em> of the MBean server in which the
* ObjectName is used.</p>
*
* <p>The <em>key properties</em> are an unordered set of keys and
* associated values.</p>
*
* <p>Each <em>key</em> is a nonempty string of characters which may
* not contain any of the characters comma (<code>,</code>), equals
* (<code>=</code>), colon, asterisk, or question mark. The same key
* may not occur twice in a given ObjectName.</p>
*
* <p>Each <em>value</em> associated with a key is a string of
* characters that is either unquoted or quoted.</p>
*
* <p>An <em>unquoted value</em> is a possibly empty string of
* characters which may not contain any of the characters comma,
* equals, colon, or quote.</p>
*
* <p>If the <em>unquoted value</em> contains at least one occurrence
* of the wildcard characters asterisk or question mark, then the object
* name is a <em>property value pattern</em>. The asterisk matches any
* sequence of zero or more characters, while the question mark matches
* any single character.</p>
*
* <p>A <em>quoted value</em> consists of a quote (<code>"</code>),
* followed by a possibly empty string of characters, followed by
* another quote. Within the string of characters, the backslash
* (<code>\</code>) has a special meaning. It must be followed by
* one of the following characters:</p>
*
* <ul>
* <li>Another backslash. The second backslash has no special
* meaning and the two characters represent a single backslash.</li>
*
* <li>The character 'n'. The two characters represent a newline
* ('\n' in Java).</li>
*
* <li>A quote. The two characters represent a quote, and that quote
* is not considered to terminate the quoted value. An ending closing
* quote must be present for the quoted value to be valid.</li>
*
* <li>A question mark (?) or asterisk (*). The two characters represent
* a question mark or asterisk respectively.</li>
* </ul>
*
* <p>A quote may not appear inside a quoted value except immediately
* after an odd number of consecutive backslashes.</p>
*
* <p>The quotes surrounding a quoted value, and any backslashes
* within that value, are considered to be part of the value.</p>
*
* <p>If the <em>quoted value</em> contains at least one occurrence of
* the characters asterisk or question mark and they are not preceded
* by a backslash, then they are considered as wildcard characters and
* the object name is a <em>property value pattern</em>. The asterisk
* matches any sequence of zero or more characters, while the question
* mark matches any single character.</p>
*
* <p>An ObjectName may be a <em>property list pattern</em>. In this
* case it may have zero or more keys and associated values. It matches
* a nonpattern ObjectName whose domain matches and that contains the
* same keys and associated values, as well as possibly other keys and
* values.</p>
*
* <p>An ObjectName is a <em>property value pattern</em> when at least
* one of its <em>quoted</em> or <em>unquoted</em> key property values
* contains the wildcard characters asterisk or question mark as described
* above. In this case it has one or more keys and associated values, with
* at least one of the values containing wildcard characters. It matches a
* nonpattern ObjectName whose domain matches and that contains the same
* keys whose values match; if the property value pattern is also a
* property list pattern then the nonpattern ObjectName can contain
* other keys and values.</p>
*
* <p>An ObjectName is a <em>property pattern</em> if it is either a
* <em>property list pattern</em> or a <em>property value pattern</em>
* or both.</p>
*
* <p>An ObjectName is a pattern if its domain contains a wildcard or
* if the ObjectName is a property pattern.</p>
*
* <p>If an ObjectName is not a pattern, it must contain at least one
* key with its associated value.</p>
*
* <p>Examples of ObjectName patterns are:</p>
*
* <ul>
* <li>{@code *:type=Foo,name=Bar} to match names in any domain whose
* exact set of keys is {@code type=Foo,name=Bar}.</li>
* <li>{@code d:type=Foo,name=Bar,*} to match names in the domain
* {@code d} that have the keys {@code type=Foo,name=Bar} plus
* zero or more other keys.</li>
* <li>{@code *:type=Foo,name=Bar,*} to match names in any domain
* that has the keys {@code type=Foo,name=Bar} plus zero or
* more other keys.</li>
* <li>{@code d:type=F?o,name=Bar} will match e.g.
* {@code d:type=Foo,name=Bar} and {@code d:type=Fro,name=Bar}.</li>
* <li>{@code d:type=F*o,name=Bar} will match e.g.
* {@code d:type=Fo,name=Bar} and {@code d:type=Frodo,name=Bar}.</li>
* <li>{@code d:type=Foo,name="B*"} will match e.g.
* {@code d:type=Foo,name="Bling"}. Wildcards are recognized even
* inside quotes, and like other special characters can be escaped
* with {@code \}.</li>
* </ul>
*
* <p>An ObjectName can be written as a String with the following
* elements in order:</p>
*
* <ul>
* <li>The domain.
* <li>A colon (<code>:</code>).
* <li>A key property list as defined below.
* </ul>
*
* <p>A key property list written as a String is a comma-separated
* list of elements. Each element is either an asterisk or a key
* property. A key property consists of a key, an equals
* (<code>=</code>), and the associated value.</p>
*
* <p>At most one element of a key property list may be an asterisk.
* If the key property list contains an asterisk element, the
* ObjectName is a property list pattern.</p>
*
* <p>Spaces have no special significance in a String representing an
* ObjectName. For example, the String:
* <pre>
* domain: key1 = value1 , key2 = value2
* </pre>
* represents an ObjectName with two keys. The name of each key
* contains six characters, of which the first and last are spaces.
* The value associated with the key <code>" key1 "</code>
* also begins and ends with a space.</p>
*
* <p>In addition to the restrictions on characters spelt out above,
* no part of an ObjectName may contain a newline character
* (<code>'\n'</code>), whether the domain, a key, or a value, whether
* quoted or unquoted. The newline character can be represented in a
* quoted value with the sequence <code>\n</code>.
*
* <p>The rules on special characters and quoting apply regardless of
* which constructor is used to make an ObjectName.</p>
*
* <p>To avoid collisions between MBeans supplied by different
* vendors, a useful convention is to begin the domain name with the
* reverse DNS name of the organization that specifies the MBeans,
* followed by a period and a string whose interpretation is
* determined by that organization. For example, MBeans specified by
* Sun Microsystems Inc., DNS name <code>sun.com</code>, would have
* domains such as <code>com.sun.MyDomain</code>. This is essentially
* the same convention as for Java-language package names.</p>
*
* <p>The <b>serialVersionUID</b> of this class is <code>1081892073854801359L</code>.
*
* @since 1.5
*/
@SuppressWarnings("serial") // don't complain serialVersionUID not constant
public class ObjectName implements Comparable<ObjectName>, QueryExp {
/**
* A structure recording property structure and
* proposing minimal services
*/
private static class Property {
int _key_index;
int _key_length;
int _value_length;
/**
* Constructor.
*/
Property(int key_index, int key_length, int value_length) {
_key_index = key_index;
_key_length = key_length;
_value_length = value_length;
}
/**
* Assigns the key index of property
*/
void setKeyIndex(int key_index) {
_key_index = key_index;
}
/**
* Returns a key string for receiver key
*/
String getKeyString(String name) {
return name.substring(_key_index, _key_index + _key_length);
}
/**
* Returns a value string for receiver key
*/
String getValueString(String name) {
int in_begin = _key_index + _key_length + 1;
int out_end = in_begin + _value_length;
return name.substring(in_begin, out_end);
}
}
/**
* Marker class for value pattern property.
*/
private static class PatternProperty extends Property {
/**
* Constructor.
*/
PatternProperty(int key_index, int key_length, int value_length) {
super(key_index, key_length, value_length);
}
}
// Inner classes <========================================
// Private fields ---------------------------------------->
// Serialization compatibility stuff -------------------->
// Two serial forms are supported in this class. The selected form depends
// on system property "jmx.serial.form":
// - "1.0" for JMX 1.0
// - any other value for JMX 1.1 and higher
//
// Serial version for old serial form
private static final long oldSerialVersionUID = -5467795090068647408L;
//
// Serial version for new serial form
private static final long newSerialVersionUID = 1081892073854801359L;
//
// Serializable fields in old serial form
private static final ObjectStreamField[] oldSerialPersistentFields =
{
new ObjectStreamField("domain", String.class),
new ObjectStreamField("propertyList", Hashtable.class),
new ObjectStreamField("propertyListString", String.class),
new ObjectStreamField("canonicalName", String.class),
new ObjectStreamField("pattern", Boolean.TYPE),
new ObjectStreamField("propertyPattern", Boolean.TYPE)
};
//
// Serializable fields in new serial form
private static final ObjectStreamField[] newSerialPersistentFields = { };
//
// Actual serial version and serial form
private static final long serialVersionUID;
private static final ObjectStreamField[] serialPersistentFields;
private static boolean compat = false;
static {
try {
GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
String form = AccessController.doPrivileged(act);
compat = (form != null && form.equals("1.0"));
} catch (Exception e) {
// OK: exception means no compat with 1.0, too bad
}
if (compat) {
serialPersistentFields = oldSerialPersistentFields;
serialVersionUID = oldSerialVersionUID;
} else {
serialPersistentFields = newSerialPersistentFields;
serialVersionUID = newSerialVersionUID;
}
}
//
// Serialization compatibility stuff <==============================
// Class private fields ----------------------------------->
/**
* a shared empty array for empty property lists
*/
static final private Property[] _Empty_property_array = new Property[0];
// Class private fields <==============================
// Instance private fields ----------------------------------->
/**
* a String containing the canonical name
*/
private transient String _canonicalName;
/**
* An array of properties in the same seq order as time creation
*/
private transient Property[] _kp_array;
/**
* An array of properties in the same seq order as canonical order
*/
private transient Property[] _ca_array;
/**
* The length of the domain part of built objectname
*/
private transient int _domain_length = 0;
/**
* The propertyList of built object name. Initialized lazily.
* Table that contains all the pairs (key,value) for this ObjectName.
*/
private transient Map<String,String> _propertyList;
/**
* boolean that declares if this ObjectName domain part is a pattern
*/
private transient boolean _domain_pattern = false;
/**
* boolean that declares if this ObjectName contains a pattern on the
* key property list
*/
private transient boolean _property_list_pattern = false;
/**
* boolean that declares if this ObjectName contains a pattern on the
* value of at least one key property
*/
private transient boolean _property_value_pattern = false;
// Instance private fields <=======================================
// Private fields <========================================
// Private methods ---------------------------------------->
// Category : Instance construction ------------------------->
/**
* Initializes this {@link ObjectName} from the given string
* representation.
*
* @param name A string representation of the {@link ObjectName}
*
* @exception MalformedObjectNameException The string passed as a
* parameter does not have the right format.
* @exception NullPointerException The <code>name</code> parameter
* is null.
*/
private void construct(String name)
throws MalformedObjectNameException, NullPointerException {
// The name cannot be null
if (name == null)
throw new NullPointerException("name cannot be null");
// Test if the name is empty
if (name.length() == 0) {
// this is equivalent to the whole word query object name.
_canonicalName = "*:*";
_kp_array = _Empty_property_array;
_ca_array = _Empty_property_array;
_domain_length = 1;
_propertyList = null;
_domain_pattern = true;
_property_list_pattern = true;
_property_value_pattern = false;
return;
}
// initialize parsing of the string
char[] name_chars = name.toCharArray();
int len = name_chars.length;
char[] canonical_chars = new char[len]; // canonical form will be same
// length at most
int cname_index = 0;
int index = 0;
char c, c1;
// parses domain part
domain_parsing:
while (index < len) {
switch (name_chars[index]) {
case ':' :
_domain_length = index++;
break domain_parsing;
case '=' :
// ":" omission check.
//
// Although "=" is a valid character in the domain part
// it is true that it is rarely used in the real world.
// So check straight away if the ":" has been omitted
// from the ObjectName. This allows us to provide a more
// accurate exception message.
int i = ++index;
while ((i < len) && (name_chars[i++] != ':'))
if (i == len)
throw new MalformedObjectNameException(
"Domain part must be specified");
break;
case '\n' :
throw new MalformedObjectNameException(
"Invalid character '\\n' in domain name");
case '*' :
case '?' :
_domain_pattern = true;
index++;
break;
default :
index++;
break;
}
}
// check for non-empty properties
if (index == len)
throw new MalformedObjectNameException(
"Key properties cannot be empty");
// we have got the domain part, begins building of _canonicalName
System.arraycopy(name_chars, 0, canonical_chars, 0, _domain_length);
canonical_chars[_domain_length] = ':';
cname_index = _domain_length + 1;
// parses property list
Property prop;
Map<String,Property> keys_map = new HashMap<String,Property>();
String[] keys;
String key_name;
boolean quoted_value;
int property_index = 0;
int in_index;
int key_index, key_length, value_index, value_length;
keys = new String[10];
_kp_array = new Property[10];
_property_list_pattern = false;
_property_value_pattern = false;
while (index < len) {
c = name_chars[index];
// case of pattern properties
if (c == '*') {
if (_property_list_pattern)
throw new MalformedObjectNameException(
"Cannot have several '*' characters in pattern " +
"property list");
else {
_property_list_pattern = true;
if ((++index < len ) && (name_chars[index] != ','))
throw new MalformedObjectNameException(
"Invalid character found after '*': end of " +
"name or ',' expected");
else if (index == len) {
if (property_index == 0) {
// empty properties case
_kp_array = _Empty_property_array;
_ca_array = _Empty_property_array;
_propertyList = Collections.emptyMap();
}
break;
} else {
// correct pattern spec in props, continue
index++;
continue;
}
}
}
// standard property case, key part
in_index = index;
key_index = in_index;
if (name_chars[in_index] == '=')
throw new MalformedObjectNameException("Invalid key (empty)");
while ((in_index < len) && ((c1 = name_chars[in_index++]) != '='))
switch (c1) {
// '=' considered to introduce value part
case '*' :
case '?' :
case ',' :
case ':' :
case '\n' :
final String ichar = ((c1=='\n')?"\\n":""+c1);
throw new MalformedObjectNameException(
"Invalid character '" + ichar +
"' in key part of property");
}
if (name_chars[in_index - 1] != '=')
throw new MalformedObjectNameException(
"Unterminated key property part");
value_index = in_index; // in_index pointing after '=' char
key_length = value_index - key_index - 1; // found end of key
// standard property case, value part
boolean value_pattern = false;
if (in_index < len && name_chars[in_index] == '\"') {
quoted_value = true;
// the case of quoted value part
quoted_value_parsing:
while ((++in_index < len) &&
((c1 = name_chars[in_index]) != '\"')) {
// the case of an escaped character
if (c1 == '\\') {
if (++in_index == len)
throw new MalformedObjectNameException(
"Unterminated quoted value");
switch (c1 = name_chars[in_index]) {
case '\\' :
case '\"' :
case '?' :
case '*' :
case 'n' :
break; // valid character
default :
throw new MalformedObjectNameException(
"Invalid escape sequence '\\" +
c1 + "' in quoted value");
}
} else if (c1 == '\n') {
throw new MalformedObjectNameException(
"Newline in quoted value");
} else {
switch (c1) {
case '?' :
case '*' :
value_pattern = true;
break;
}
}
}
if (in_index == len)
throw new MalformedObjectNameException(
"Unterminated quoted value");
else value_length = ++in_index - value_index;
} else {
// the case of standard value part
quoted_value = false;
while ((in_index < len) && ((c1 = name_chars[in_index]) != ','))
switch (c1) {
// ',' considered to be the value separator
case '*' :
case '?' :
value_pattern = true;
in_index++;
break;
case '=' :
case ':' :
case '"' :
case '\n' :
final String ichar = ((c1=='\n')?"\\n":""+c1);
throw new MalformedObjectNameException(
"Invalid character '" + ichar +
"' in value part of property");
default :
in_index++;
break;
}
value_length = in_index - value_index;
}
// Parsed property, checks the end of name
if (in_index == len - 1) {
if (quoted_value)
throw new MalformedObjectNameException(
"Invalid ending character `" +
name_chars[in_index] + "'");
else throw new MalformedObjectNameException(
"Invalid ending comma");
} else in_index++;
// we got the key and value part, prepare a property for this
if (!value_pattern) {
prop = new Property(key_index, key_length, value_length);
} else {
_property_value_pattern = true;
prop = new PatternProperty(key_index, key_length, value_length);
}
key_name = name.substring(key_index, key_index + key_length);
if (property_index == keys.length) {
String[] tmp_string_array = new String[property_index + 10];
System.arraycopy(keys, 0, tmp_string_array, 0, property_index);
keys = tmp_string_array;
}
keys[property_index] = key_name;
addProperty(prop, property_index, keys_map, key_name);
property_index++;
index = in_index;
}
// computes and set canonical name
setCanonicalName(name_chars, canonical_chars, keys,
keys_map, cname_index, property_index);
}
/**
* Construct an ObjectName from a domain and a Hashtable.
*
* @param domain Domain of the ObjectName.
* @param props Map containing couples <i>key</i> -> <i>value</i>.
*
* @exception MalformedObjectNameException The <code>domain</code>
* contains an illegal character, or one of the keys or values in
* <code>table</code> contains an illegal character, or one of the
* values in <code>table</code> does not follow the rules for quoting.
* @exception NullPointerException One of the parameters is null.
*/
private void construct(String domain, Map<String,String> props)
throws MalformedObjectNameException, NullPointerException {
// The domain cannot be null
if (domain == null)
throw new NullPointerException("domain cannot be null");
// The key property list cannot be null
if (props == null)
throw new NullPointerException("key property list cannot be null");
// The key property list cannot be empty
if (props.isEmpty())
throw new MalformedObjectNameException(
"key property list cannot be empty");
// checks domain validity
if (!isDomain(domain))
throw new MalformedObjectNameException("Invalid domain: " + domain);
// init canonicalname
final StringBuilder sb = new StringBuilder();
sb.append(domain).append(':');
_domain_length = domain.length();
// allocates the property array
int nb_props = props.size();
_kp_array = new Property[nb_props];
String[] keys = new String[nb_props];
final Map<String,Property> keys_map = new HashMap<String,Property>();
Property prop;
int key_index;
int i = 0;
for (Map.Entry<String,String> entry : props.entrySet()) {
if (sb.length() > 0)
sb.append(",");
String key = entry.getKey();
String value;
try {
value = entry.getValue();
} catch (ClassCastException e) {
throw new MalformedObjectNameException(e.getMessage());
}
key_index = sb.length();
checkKey(key);
sb.append(key);
keys[i] = key;
sb.append("=");
boolean value_pattern = checkValue(value);
sb.append(value);
if (!value_pattern) {
prop = new Property(key_index,
key.length(),
value.length());
} else {
_property_value_pattern = true;
prop = new PatternProperty(key_index,
key.length(),
value.length());
}
addProperty(prop, i, keys_map, key);
i++;
}
// initialize canonical name and data structure
int len = sb.length();
char[] initial_chars = new char[len];
sb.getChars(0, len, initial_chars, 0);
char[] canonical_chars = new char[len];
System.arraycopy(initial_chars, 0, canonical_chars, 0,
_domain_length + 1);
setCanonicalName(initial_chars, canonical_chars, keys, keys_map,
_domain_length + 1, _kp_array.length);
}
// Category : Instance construction <==============================
// Category : Internal utilities ------------------------------>
/**
* Add passed property to the list at the given index
* for the passed key name
*/
private void addProperty(Property prop, int index,
Map<String,Property> keys_map, String key_name)
throws MalformedObjectNameException {
if (keys_map.containsKey(key_name)) throw new
MalformedObjectNameException("key `" +
key_name +"' already defined");
// if no more space for property arrays, have to increase it
if (index == _kp_array.length) {
Property[] tmp_prop_array = new Property[index + 10];
System.arraycopy(_kp_array, 0, tmp_prop_array, 0, index);
_kp_array = tmp_prop_array;
}
_kp_array[index] = prop;
keys_map.put(key_name, prop);
}
/**
* Sets the canonical name of receiver from input 'specified_chars'
* array, by filling 'canonical_chars' array with found 'nb-props'
* properties starting at position 'prop_index'.
*/
private void setCanonicalName(char[] specified_chars,
char[] canonical_chars,
String[] keys, Map<String,Property> keys_map,
int prop_index, int nb_props) {
// Sort the list of found properties
if (_kp_array != _Empty_property_array) {
String[] tmp_keys = new String[nb_props];
Property[] tmp_props = new Property[nb_props];
System.arraycopy(keys, 0, tmp_keys, 0, nb_props);
Arrays.sort(tmp_keys);
keys = tmp_keys;
System.arraycopy(_kp_array, 0, tmp_props, 0 , nb_props);
_kp_array = tmp_props;
_ca_array = new Property[nb_props];
// now assigns _ca_array to the sorted list of keys
// (there cannot be two identical keys in an objectname.
for (int i = 0; i < nb_props; i++)
_ca_array[i] = keys_map.get(keys[i]);
// now we build the canonical name and set begin indexes of
// properties to reflect canonical form
int last_index = nb_props - 1;
int prop_len;
Property prop;
for (int i = 0; i <= last_index; i++) {
prop = _ca_array[i];
// length of prop including '=' char
prop_len = prop._key_length + prop._value_length + 1;
System.arraycopy(specified_chars, prop._key_index,
canonical_chars, prop_index, prop_len);
prop.setKeyIndex(prop_index);
prop_index += prop_len;
if (i != last_index) {
canonical_chars[prop_index] = ',';
prop_index++;
}
}
}
// terminate canonicalname with '*' in case of pattern
if (_property_list_pattern) {
if (_kp_array != _Empty_property_array)
canonical_chars[prop_index++] = ',';
canonical_chars[prop_index++] = '*';
}
// we now build the canonicalname string
_canonicalName = (new String(canonical_chars, 0, prop_index)).intern();
}
/**
* Parse a key.
* <pre>final int endKey=parseKey(s,startKey);</pre>
* <p>key starts at startKey (included), and ends at endKey (excluded).
* If (startKey == endKey), then the key is empty.
*
* @param s The char array of the original string.
* @param startKey index at which to begin parsing.
* @return The index following the last character of the key.
**/
private static int parseKey(final char[] s, final int startKey)
throws MalformedObjectNameException {
int next = startKey;
int endKey = startKey;
final int len = s.length;
while (next < len) {
final char k = s[next++];
switch (k) {
case '*':
case '?':
case ',':
case ':':
case '\n':
final String ichar = ((k=='\n')?"\\n":""+k);
throw new
MalformedObjectNameException("Invalid character in key: `"
+ ichar + "'");
case '=':
// we got the key.
endKey = next-1;
break;
default:
if (next < len) continue;
else endKey=next;
}
break;
}
return endKey;
}
/**
* Parse a value.
* <pre>final int endVal=parseValue(s,startVal);</pre>
* <p>value starts at startVal (included), and ends at endVal (excluded).
* If (startVal == endVal), then the key is empty.
*
* @param s The char array of the original string.
* @param startValue index at which to begin parsing.
* @return The first element of the int array indicates the index
* following the last character of the value. The second
* element of the int array indicates that the value is
* a pattern when its value equals 1.
**/
private static int[] parseValue(final char[] s, final int startValue)
throws MalformedObjectNameException {
boolean value_pattern = false;
int next = startValue;
int endValue = startValue;
final int len = s.length;
final char q=s[startValue];
if (q == '"') {
// quoted value
if (++next == len) throw new
MalformedObjectNameException("Invalid quote");
while (next < len) {
char last = s[next];
if (last == '\\') {
if (++next == len) throw new
MalformedObjectNameException(
"Invalid unterminated quoted character sequence");
last = s[next];
switch (last) {
case '\\' :
case '?' :
case '*' :
case 'n' :
break;
case '\"' :
// We have an escaped quote. If this escaped
// quote is the last character, it does not
// qualify as a valid termination quote.
//
if (next+1 == len) throw new
MalformedObjectNameException(
"Missing termination quote");
break;
default:
throw new
MalformedObjectNameException(
"Invalid quoted character sequence '\\" +
last + "'");
}
} else if (last == '\n') {
throw new MalformedObjectNameException(
"Newline in quoted value");
} else if (last == '\"') {
next++;
break;
} else {
switch (last) {
case '?' :
case '*' :
value_pattern = true;
break;
}
}
next++;
// Check that last character is a termination quote.
// We have already handled the case were the last
// character is an escaped quote earlier.
//
if ((next >= len) && (last != '\"')) throw new
MalformedObjectNameException("Missing termination quote");
}
endValue = next;
if (next < len) {
if (s[next++] != ',') throw new
MalformedObjectNameException("Invalid quote");
}
} else {
// Non quoted value.
while (next < len) {
final char v=s[next++];
switch(v) {
case '*':
case '?':
value_pattern = true;
if (next < len) continue;
else endValue=next;
break;
case '=':
case ':':
case '\n' :
final String ichar = ((v=='\n')?"\\n":""+v);
throw new
MalformedObjectNameException("Invalid character `" +
ichar + "' in value");
case ',':
endValue = next-1;
break;
default:
if (next < len) continue;
else endValue=next;
}
break;
}
}
return new int[] { endValue, value_pattern ? 1 : 0 };
}
/**
* Check if the supplied value is a valid value.
*
* @return true if the value is a pattern, otherwise false.
*/
private static boolean checkValue(String val)
throws MalformedObjectNameException {
if (val == null) throw new
NullPointerException("Invalid value (null)");
final int len = val.length();
if (len == 0)
return false;
final char[] s = val.toCharArray();
final int[] result = parseValue(s,0);
final int endValue = result[0];
final boolean value_pattern = result[1] == 1;
if (endValue < len) throw new
MalformedObjectNameException("Invalid character in value: `" +
s[endValue] + "'");
return value_pattern;
}
/**
* Check if the supplied key is a valid key.
*/
private static void checkKey(String key)
throws MalformedObjectNameException, NullPointerException {
if (key == null) throw new
NullPointerException("Invalid key (null)");
final int len = key.length();
if (len == 0) throw new
MalformedObjectNameException("Invalid key (empty)");
final char[] k=key.toCharArray();
final int endKey = parseKey(k,0);
if (endKey < len) throw new
MalformedObjectNameException("Invalid character in value: `" +
k[endKey] + "'");
}
/*
* Tests whether string s is matched by pattern p.
* Supports "?", "*" each of which may be escaped with "\";
* Not yet supported: internationalization; "\" inside brackets.<P>
* Wildcard matching routine by Karl Heuer. Public Domain.<P>
*/
private static boolean wildmatch(char[] s, char[] p, int si, int pi) {
char c;
final int slen = s.length;
final int plen = p.length;
while (pi < plen) { // While still string
c = p[pi++];
if (c == '?') {
if (++si > slen) return false;
} else if (c == '*') { // Wildcard
if (pi >= plen) return true;
do {
if (wildmatch(s,p,si,pi)) return true;
} while (++si < slen);
return false;
} else {
if (si >= slen || c != s[si++]) return false;
}
}
return (si == slen);
}
// Category : Internal utilities <==============================
// Category : Internal accessors ------------------------------>
/**
* Check if domain is a valid domain. Set _domain_pattern if appropriate.
*/
private boolean isDomain(String domain) {
if (domain == null) return true;
final char[] d=domain.toCharArray();
final int len = d.length;
int next = 0;
while (next < len) {
final char c = d[next++];
switch (c) {
case ':' :
case '\n' :
return false;
case '*' :
case '?' :
_domain_pattern = true;
break;
}
}
return true;
}
// Category : Internal accessors <==============================
// Category : Serialization ----------------------------------->
/**
* Deserializes an {@link ObjectName} from an {@link ObjectInputStream}.
* @serialData <ul>
* <li>In the current serial form (value of property
* <code>jmx.serial.form</code> differs from
* <code>1.0</code>): the string
* "<domain>:<properties><wild>",
* where: <ul>
* <li><domain> represents the domain part
* of the {@link ObjectName}</li>
* <li><properties> represents the list of
* properties, as returned by
* {@link #getKeyPropertyListString}
* <li><wild> is empty if not
* <code>isPropertyPattern</code>, or
* is the character "<code>*</code>" if
* <code>isPropertyPattern</code>
* and <properties> is empty, or
* is "<code>,*</code>" if
* <code>isPropertyPattern</code> and
* <properties> is not empty.
* </li>
* </ul>
* The intent is that this string could be supplied
* to the {@link #ObjectName(String)} constructor to
* produce an equivalent {@link ObjectName}.
* </li>
* <li>In the old serial form (value of property
* <code>jmx.serial.form</code> is
* <code>1.0</code>): <domain> <propertyList>
* <propertyListString> <canonicalName>
* <pattern> <propertyPattern>,
* where: <ul>
* <li><domain> represents the domain part
* of the {@link ObjectName}</li>
* <li><propertyList> is the
* {@link Hashtable} that contains all the
* pairs (key,value) for this
* {@link ObjectName}</li>
* <li><propertyListString> is the
* {@link String} representation of the
* list of properties in any order (not
* mandatorily a canonical representation)
* </li>
* <li><canonicalName> is the
* {@link String} containing this
* {@link ObjectName}'s canonical name</li>
* <li><pattern> is a boolean which is
* <code>true</code> if this
* {@link ObjectName} contains a pattern</li>
* <li><propertyPattern> is a boolean which
* is <code>true</code> if this
* {@link ObjectName} contains a pattern in
* the list of properties</li>
* </ul>
* </li>
* </ul>
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
String cn;
if (compat) {
// Read an object serialized in the old serial form
//
//in.defaultReadObject();
final ObjectInputStream.GetField fields = in.readFields();
String propListString =
(String)fields.get("propertyListString", "");
// 6616825: take care of property patterns
final boolean propPattern =
fields.get("propertyPattern" , false);
if (propPattern) {
propListString =
(propListString.length()==0?"*":(propListString+",*"));
}
cn = (String)fields.get("domain", "default")+
":"+ propListString;
} else {
// Read an object serialized in the new serial form
//
in.defaultReadObject();
cn = (String)in.readObject();
}
try {
construct(cn);
} catch (NullPointerException e) {
throw new InvalidObjectException(e.toString());
} catch (MalformedObjectNameException e) {
throw new InvalidObjectException(e.toString());
}
}
/**
* Serializes an {@link ObjectName} to an {@link ObjectOutputStream}.
* @serialData <ul>
* <li>In the current serial form (value of property
* <code>jmx.serial.form</code> differs from
* <code>1.0</code>): the string
* "<domain>:<properties><wild>",
* where: <ul>
* <li><domain> represents the domain part
* of the {@link ObjectName}</li>
* <li><properties> represents the list of
* properties, as returned by
* {@link #getKeyPropertyListString}
* <li><wild> is empty if not
* <code>isPropertyPattern</code>, or
* is the character "<code>*</code>" if
* this <code>isPropertyPattern</code>
* and <properties> is empty, or
* is "<code>,*</code>" if
* <code>isPropertyPattern</code> and
* <properties> is not empty.
* </li>
* </ul>
* The intent is that this string could be supplied
* to the {@link #ObjectName(String)} constructor to
* produce an equivalent {@link ObjectName}.
* </li>
* <li>In the old serial form (value of property
* <code>jmx.serial.form</code> is
* <code>1.0</code>): <domain> <propertyList>
* <propertyListString> <canonicalName>
* <pattern> <propertyPattern>,
* where: <ul>
* <li><domain> represents the domain part
* of the {@link ObjectName}</li>
* <li><propertyList> is the
* {@link Hashtable} that contains all the
* pairs (key,value) for this
* {@link ObjectName}</li>
* <li><propertyListString> is the
* {@link String} representation of the
* list of properties in any order (not
* mandatorily a canonical representation)
* </li>
* <li><canonicalName> is the
* {@link String} containing this
* {@link ObjectName}'s canonical name</li>
* <li><pattern> is a boolean which is
* <code>true</code> if this
* {@link ObjectName} contains a pattern</li>
* <li><propertyPattern> is a boolean which
* is <code>true</code> if this
* {@link ObjectName} contains a pattern in
* the list of properties</li>
* </ul>
* </li>
* </ul>
*/
private void writeObject(ObjectOutputStream out)
throws IOException {
if (compat)
{
// Serializes this instance in the old serial form
// Read CR 6441274 before making any changes to this code
ObjectOutputStream.PutField fields = out.putFields();
fields.put("domain", _canonicalName.substring(0, _domain_length));
fields.put("propertyList", getKeyPropertyList());
fields.put("propertyListString", getKeyPropertyListString());
fields.put("canonicalName", _canonicalName);
fields.put("pattern", (_domain_pattern || _property_list_pattern));
fields.put("propertyPattern", _property_list_pattern);
out.writeFields();
}
else
{
// Serializes this instance in the new serial form
//
out.defaultWriteObject();
out.writeObject(getSerializedNameString());
}
}
// Category : Serialization <===================================
// Private methods <========================================
// Public methods ---------------------------------------->
// Category : ObjectName Construction ------------------------------>
/**
* <p>Return an instance of ObjectName that can be used anywhere
* an object obtained with {@link #ObjectName(String) new
* ObjectName(name)} can be used. The returned object may be of
* a subclass of ObjectName. Calling this method twice with the
* same parameters may return the same object or two equal but
* not identical objects.</p>
*
* @param name A string representation of the object name.
*
* @return an ObjectName corresponding to the given String.
*
* @exception MalformedObjectNameException The string passed as a
* parameter does not have the right format.
* @exception NullPointerException The <code>name</code> parameter
* is null.
*
*/
public static ObjectName getInstance(String name)
throws MalformedObjectNameException, NullPointerException {
return new ObjectName(name);
}
/**
* <p>Return an instance of ObjectName that can be used anywhere
* an object obtained with {@link #ObjectName(String, String,
* String) new ObjectName(domain, key, value)} can be used. The
* returned object may be of a subclass of ObjectName. Calling
* this method twice with the same parameters may return the same
* object or two equal but not identical objects.</p>
*
* @param domain The domain part of the object name.
* @param key The attribute in the key property of the object name.
* @param value The value in the key property of the object name.
*
* @return an ObjectName corresponding to the given domain,
* key, and value.
*
* @exception MalformedObjectNameException The
* <code>domain</code>, <code>key</code>, or <code>value</code>
* contains an illegal character, or <code>value</code> does not
* follow the rules for quoting.
* @exception NullPointerException One of the parameters is null.
*
*/
public static ObjectName getInstance(String domain, String key,
String value)
throws MalformedObjectNameException, NullPointerException {
return new ObjectName(domain, key, value);
}
/**
* <p>Return an instance of ObjectName that can be used anywhere
* an object obtained with {@link #ObjectName(String, Hashtable)
* new ObjectName(domain, table)} can be used. The returned
* object may be of a subclass of ObjectName. Calling this method
* twice with the same parameters may return the same object or
* two equal but not identical objects.</p>
*
* @param domain The domain part of the object name.
* @param table A hash table containing one or more key
* properties. The key of each entry in the table is the key of a
* key property in the object name. The associated value in the
* table is the associated value in the object name.
*
* @return an ObjectName corresponding to the given domain and
* key mappings.
*
* @exception MalformedObjectNameException The <code>domain</code>
* contains an illegal character, or one of the keys or values in
* <code>table</code> contains an illegal character, or one of the
* values in <code>table</code> does not follow the rules for
* quoting.
* @exception NullPointerException One of the parameters is null.
*
*/
public static ObjectName getInstance(String domain,
Hashtable<String,String> table)
throws MalformedObjectNameException, NullPointerException {
return new ObjectName(domain, table);
}
/**
* <p>Return an instance of ObjectName that can be used anywhere
* the given object can be used. The returned object may be of a
* subclass of ObjectName. If <code>name</code> is of a subclass
* of ObjectName, it is not guaranteed that the returned object
* will be of the same class.</p>
*
* <p>The returned value may or may not be identical to
* <code>name</code>. Calling this method twice with the same
* parameters may return the same object or two equal but not
* identical objects.</p>
*
* <p>Since ObjectName is immutable, it is not usually useful to
* make a copy of an ObjectName. The principal use of this method
* is to guard against a malicious caller who might pass an
* instance of a subclass with surprising behavior to sensitive
* code. Such code can call this method to obtain an ObjectName
* that is known not to have surprising behavior.</p>
*
* @param name an instance of the ObjectName class or of a subclass
*
* @return an instance of ObjectName or a subclass that is known to
* have the same semantics. If <code>name</code> respects the
* semantics of ObjectName, then the returned object is equal
* (though not necessarily identical) to <code>name</code>.
*
* @exception NullPointerException The <code>name</code> is null.
*
*/
public static ObjectName getInstance(ObjectName name)
throws NullPointerException {
if (name.getClass().equals(ObjectName.class))
return name;
return Util.newObjectName(name.getSerializedNameString());
}
/**
* Construct an object name from the given string.
*
* @param name A string representation of the object name.
*
* @exception MalformedObjectNameException The string passed as a
* parameter does not have the right format.
* @exception NullPointerException The <code>name</code> parameter
* is null.
*/
public ObjectName(String name)
throws MalformedObjectNameException, NullPointerException {
construct(name);
}
/**
* Construct an object name with exactly one key property.
*
* @param domain The domain part of the object name.
* @param key The attribute in the key property of the object name.
* @param value The value in the key property of the object name.
*
* @exception MalformedObjectNameException The
* <code>domain</code>, <code>key</code>, or <code>value</code>
* contains an illegal character, or <code>value</code> does not
* follow the rules for quoting.
* @exception NullPointerException One of the parameters is null.
*/
public ObjectName(String domain, String key, String value)
throws MalformedObjectNameException, NullPointerException {
// If key or value are null a NullPointerException
// will be thrown by the put method in Hashtable.
//
Map<String,String> table = Collections.singletonMap(key, value);
construct(domain, table);
}
/**
* Construct an object name with several key properties from a Hashtable.
*
* @param domain The domain part of the object name.
* @param table A hash table containing one or more key
* properties. The key of each entry in the table is the key of a
* key property in the object name. The associated value in the
* table is the associated value in the object name.
*
* @exception MalformedObjectNameException The <code>domain</code>
* contains an illegal character, or one of the keys or values in
* <code>table</code> contains an illegal character, or one of the
* values in <code>table</code> does not follow the rules for
* quoting.
* @exception NullPointerException One of the parameters is null.
*/
public ObjectName(String domain, Hashtable<String,String> table)
throws MalformedObjectNameException, NullPointerException {
construct(domain, table);
/* The exception for when a key or value in the table is not a
String is now ClassCastException rather than
MalformedObjectNameException. This was not previously
specified. */
}
// Category : ObjectName Construction <==============================
// Category : Getter methods ------------------------------>
/**
* Checks whether the object name is a pattern.
* <p>
* An object name is a pattern if its domain contains a
* wildcard or if the object name is a property pattern.
*
* @return True if the name is a pattern, otherwise false.
*/
public boolean isPattern() {
return (_domain_pattern ||
_property_list_pattern ||
_property_value_pattern);
}
/**
* Checks whether the object name is a pattern on the domain part.
*
* @return True if the name is a domain pattern, otherwise false.
*
*/
public boolean isDomainPattern() {
return _domain_pattern;
}
/**
* Checks whether the object name is a pattern on the key properties.
* <p>
* An object name is a pattern on the key properties if it is a
* pattern on the key property list (e.g. "d:k=v,*") or on the
* property values (e.g. "d:k=*") or on both (e.g. "d:k=*,*").
*
* @return True if the name is a property pattern, otherwise false.
*/
public boolean isPropertyPattern() {
return _property_list_pattern || _property_value_pattern;
}
/**
* Checks whether the object name is a pattern on the key property list.
* <p>
* For example, "d:k=v,*" and "d:k=*,*" are key property list patterns
* whereas "d:k=*" is not.
*
* @return True if the name is a property list pattern, otherwise false.
*
* @since 1.6
*/
public boolean isPropertyListPattern() {
return _property_list_pattern;
}
/**
* Checks whether the object name is a pattern on the value part
* of at least one of the key properties.
* <p>
* For example, "d:k=*" and "d:k=*,*" are property value patterns
* whereas "d:k=v,*" is not.
*
* @return True if the name is a property value pattern, otherwise false.
*
* @since 1.6
*/
public boolean isPropertyValuePattern() {
return _property_value_pattern;
}
/**
* Checks whether the value associated with a key in a key
* property is a pattern.
*
* @param property The property whose value is to be checked.
*
* @return True if the value associated with the given key property
* is a pattern, otherwise false.
*
* @exception NullPointerException If <code>property</code> is null.
* @exception IllegalArgumentException If <code>property</code> is not
* a valid key property for this ObjectName.
*
* @since 1.6
*/
public boolean isPropertyValuePattern(String property)
throws NullPointerException, IllegalArgumentException {
if (property == null)
throw new NullPointerException("key property can't be null");
for (int i = 0; i < _ca_array.length; i++) {
Property prop = _ca_array[i];
String key = prop.getKeyString(_canonicalName);
if (key.equals(property))
return (prop instanceof PatternProperty);
}
throw new IllegalArgumentException("key property not found");
}
/**
* <p>Returns the canonical form of the name; that is, a string
* representation where the properties are sorted in lexical
* order.</p>
*
* <p>More precisely, the canonical form of the name is a String
* consisting of the <em>domain part</em>, a colon
* (<code>:</code>), the <em>canonical key property list</em>, and
* a <em>pattern indication</em>.</p>
*
* <p>The <em>canonical key property list</em> is the same string
* as described for {@link #getCanonicalKeyPropertyListString()}.</p>
*
* <p>The <em>pattern indication</em> is:
* <ul>
* <li>empty for an ObjectName
* that is not a property list pattern;
* <li>an asterisk for an ObjectName
* that is a property list pattern with no keys; or
* <li>a comma and an
* asterisk (<code>,*</code>) for an ObjectName that is a property
* list pattern with at least one key.
* </ul></p>
*
* @return The canonical form of the name.
*/
public String getCanonicalName() {
return _canonicalName;
}
/**
* Returns the domain part.
*
* @return The domain.
*/
public String getDomain() {
return _canonicalName.substring(0, _domain_length);
}
/**
* Obtains the value associated with a key in a key property.
*
* @param property The property whose value is to be obtained.
*
* @return The value of the property, or null if there is no such
* property in this ObjectName.
*
* @exception NullPointerException If <code>property</code> is null.
*/
public String getKeyProperty(String property) throws NullPointerException {
return _getKeyPropertyList().get(property);
}
/**
* <p>Returns the key properties as a Map. The returned
* value is a Map in which each key is a key in the
* ObjectName's key property list and each value is the associated
* value.</p>
*
* <p>The returned value must not be modified.</p>
*
* @return The table of key properties.
*/
private Map<String,String> _getKeyPropertyList() {
synchronized (this) {
if (_propertyList == null) {
// build (lazy eval) the property list from the canonical
// properties array
_propertyList = new HashMap<String,String>();
int len = _ca_array.length;
Property prop;
for (int i = len - 1; i >= 0; i--) {
prop = _ca_array[i];
_propertyList.put(prop.getKeyString(_canonicalName),
prop.getValueString(_canonicalName));
}
}
}
return _propertyList;
}
/**
* <p>Returns the key properties as a Hashtable. The returned
* value is a Hashtable in which each key is a key in the
* ObjectName's key property list and each value is the associated
* value.</p>
*
* <p>The returned value may be unmodifiable. If it is
* modifiable, changing it has no effect on this ObjectName.</p>
*
* @return The table of key properties.
*/
// CR 6441274 depends on the modification property defined above
public Hashtable<String,String> getKeyPropertyList() {
return new Hashtable<String,String>(_getKeyPropertyList());
}
/**
* <p>Returns a string representation of the list of key
* properties specified at creation time. If this ObjectName was
* constructed with the constructor {@link #ObjectName(String)},
* the key properties in the returned String will be in the same
* order as in the argument to the constructor.</p>
*
* @return The key property list string. This string is
* independent of whether the ObjectName is a pattern.
*/
public String getKeyPropertyListString() {
// BEWARE : we rebuild the propertyliststring at each call !!
if (_kp_array.length == 0) return "";
// the size of the string is the canonical one minus domain
// part and pattern part
final int total_size = _canonicalName.length() - _domain_length - 1
- (_property_list_pattern?2:0);
final char[] dest_chars = new char[total_size];
final char[] value = _canonicalName.toCharArray();
writeKeyPropertyListString(value,dest_chars,0);
return new String(dest_chars);
}
/**
* <p>Returns the serialized string of the ObjectName.
* properties specified at creation time. If this ObjectName was
* constructed with the constructor {@link #ObjectName(String)},
* the key properties in the returned String will be in the same
* order as in the argument to the constructor.</p>
*
* @return The key property list string. This string is
* independent of whether the ObjectName is a pattern.
*/
private String getSerializedNameString() {
// the size of the string is the canonical one
final int total_size = _canonicalName.length();
final char[] dest_chars = new char[total_size];
final char[] value = _canonicalName.toCharArray();
final int offset = _domain_length+1;
// copy "domain:" into dest_chars
//
System.arraycopy(value, 0, dest_chars, 0, offset);
// Add property list string
final int end = writeKeyPropertyListString(value,dest_chars,offset);
// Add ",*" if necessary
if (_property_list_pattern) {
if (end == offset) {
// Property list string is empty.
dest_chars[end] = '*';
} else {
// Property list string is not empty.
dest_chars[end] = ',';
dest_chars[end+1] = '*';
}
}
return new String(dest_chars);
}
/**
* <p>Write a string representation of the list of key
* properties specified at creation time in the given array, starting
* at the specified offset. If this ObjectName was
* constructed with the constructor {@link #ObjectName(String)},
* the key properties in the returned String will be in the same
* order as in the argument to the constructor.</p>
*
* @return offset + #of chars written
*/
private int writeKeyPropertyListString(char[] canonicalChars,
char[] data, int offset) {
if (_kp_array.length == 0) return offset;
final char[] dest_chars = data;
final char[] value = _canonicalName.toCharArray();
int index = offset;
final int len = _kp_array.length;
final int last = len - 1;
for (int i = 0; i < len; i++) {
final Property prop = _kp_array[i];
final int prop_len = prop._key_length + prop._value_length + 1;
System.arraycopy(value, prop._key_index, dest_chars, index,
prop_len);
index += prop_len;
if (i < last ) dest_chars[index++] = ',';
}
return index;
}
/**
* Returns a string representation of the list of key properties,
* in which the key properties are sorted in lexical order. This
* is used in lexicographic comparisons performed in order to
* select MBeans based on their key property list. Lexical order
* is the order implied by {@link String#compareTo(String)
* String.compareTo(String)}.
*
* @return The canonical key property list string. This string is
* independent of whether the ObjectName is a pattern.
*/
public String getCanonicalKeyPropertyListString() {
if (_ca_array.length == 0) return "";
int len = _canonicalName.length();
if (_property_list_pattern) len -= 2;
return _canonicalName.substring(_domain_length +1, len);
}
// Category : Getter methods <===================================
// Category : Utilities ---------------------------------------->
/**
* <p>Returns a string representation of the object name. The
* format of this string is not specified, but users can expect
* that two ObjectNames return the same string if and only if they
* are equal.</p>
*
* @return a string representation of this object name.
*/
@Override
public String toString() {
return getSerializedNameString();
}
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
* forms are equal. The canonical form is the string described
* for {@link #getCanonicalName()}.
*
* @param object The object name that the current object name is to be
* compared with.
*
* @return True if <code>object</code> is an ObjectName whose
* canonical form is equal to that of this ObjectName.
*/
@Override
public boolean equals(Object object) {
// same object case
if (this == object) return true;
// object is not an object name case
if (!(object instanceof ObjectName)) return false;
// equality when canonical names are the same
// (because usage of intern())
ObjectName on = (ObjectName) object;
String on_string = on._canonicalName;
if (_canonicalName == on_string) return true;
// Because we are sharing canonical form between object names,
// we have finished the comparison at this stage ==> unequal
return false;
}
/**
* Returns a hash code for this object name.
*
*/
@Override
public int hashCode() {
return _canonicalName.hashCode();
}
/**
* <p>Returns a quoted form of the given String, suitable for
* inclusion in an ObjectName. The returned value can be used as
* the value associated with a key in an ObjectName. The String
* <code>s</code> may contain any character. Appropriate quoting
* ensures that the returned value is legal in an ObjectName.</p>
*
* <p>The returned value consists of a quote ('"'), a sequence of
* characters corresponding to the characters of <code>s</code>,
* and another quote. Characters in <code>s</code> appear
* unchanged within the returned value except:</p>
*
* <ul>
* <li>A quote ('"') is replaced by a backslash (\) followed by a quote.</li>
* <li>An asterisk ('*') is replaced by a backslash (\) followed by an
* asterisk.</li>
* <li>A question mark ('?') is replaced by a backslash (\) followed by
* a question mark.</li>
* <li>A backslash ('\') is replaced by two backslashes.</li>
* <li>A newline character (the character '\n' in Java) is replaced
* by a backslash followed by the character '\n'.</li>
* </ul>
*
* @param s the String to be quoted.
*
* @return the quoted String.
*
* @exception NullPointerException if <code>s</code> is null.
*
*/
public static String quote(String s)
throws NullPointerException {
final StringBuilder buf = new StringBuilder("\"");
final int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
switch (c) {
case '\n':
c = 'n';
buf.append('\\');
break;
case '\\':
case '\"':
case '*':
case '?':
buf.append('\\');
break;
}
buf.append(c);
}
buf.append('"');
return buf.toString();
}
/**
* <p>Returns an unquoted form of the given String. If
* <code>q</code> is a String returned by {@link #quote quote(s)},
* then <code>unquote(q).equals(s)</code>. If there is no String
* <code>s</code> for which <code>quote(s).equals(q)</code>, then
* unquote(q) throws an IllegalArgumentException.</p>
*
* <p>These rules imply that there is a one-to-one mapping between
* quoted and unquoted forms.</p>
*
* @param q the String to be unquoted.
*
* @return the unquoted String.
*
* @exception IllegalArgumentException if <code>q</code> could not
* have been returned by the {@link #quote} method, for instance
* if it does not begin and end with a quote (").
*
* @exception NullPointerException if <code>q</code> is null.
*
*/
public static String unquote(String q)
throws IllegalArgumentException, NullPointerException {
final StringBuilder buf = new StringBuilder();
final int len = q.length();
if (len < 2 || q.charAt(0) != '"' || q.charAt(len - 1) != '"')
throw new IllegalArgumentException("Argument not quoted");
for (int i = 1; i < len - 1; i++) {
char c = q.charAt(i);
if (c == '\\') {
if (i == len - 2)
throw new IllegalArgumentException("Trailing backslash");
c = q.charAt(++i);
switch (c) {
case 'n':
c = '\n';
break;
case '\\':
case '\"':
case '*':
case '?':
break;
default:
throw new IllegalArgumentException(
"Bad character '" + c + "' after backslash");
}
} else {
switch (c) {
case '*' :
case '?' :
case '\"':
case '\n':
throw new IllegalArgumentException(
"Invalid unescaped character '" + c +
"' in the string to unquote");
}
}
buf.append(c);
}
return buf.toString();
}
/**
* Defines the wildcard "*:*" ObjectName.
*
* @since 1.6
*/
public static final ObjectName WILDCARD = Util.newObjectName("*:*");
// Category : Utilities <===================================
// Category : QueryExp Interface ---------------------------------------->
/**
* <p>Test whether this ObjectName, which may be a pattern,
* matches another ObjectName. If <code>name</code> is a pattern,
* the result is false. If this ObjectName is a pattern, the
* result is true if and only if <code>name</code> matches the
* pattern. If neither this ObjectName nor <code>name</code> is
* a pattern, the result is true if and only if the two
* ObjectNames are equal as described for the {@link
* #equals(Object)} method.</p>
*
* @param name The name of the MBean to compare to.
*
* @return True if <code>name</code> matches this ObjectName.
*
* @exception NullPointerException if <code>name</code> is null.
*
*/
public boolean apply(ObjectName name) throws NullPointerException {
if (name == null) throw new NullPointerException();
if (name._domain_pattern ||
name._property_list_pattern ||
name._property_value_pattern)
return false;
// No pattern
if (!_domain_pattern &&
!_property_list_pattern &&
!_property_value_pattern)
return _canonicalName.equals(name._canonicalName);
return matchDomains(name) && matchKeys(name);
}
private final boolean matchDomains(ObjectName name) {
if (_domain_pattern) {
// wildmatch domains
final char[] dom_pattern = getDomain().toCharArray();
final char[] dom_string = name.getDomain().toCharArray();
return wildmatch(dom_string,dom_pattern,0,0);
}
return getDomain().equals(name.getDomain());
}
private final boolean matchKeys(ObjectName name) {
// If key property value pattern but not key property list
// pattern, then the number of key properties must be equal
//
if (_property_value_pattern &&
!_property_list_pattern &&
(name._ca_array.length != _ca_array.length))
return false;
// If key property value pattern or key property list pattern,
// then every property inside pattern should exist in name
//
if (_property_value_pattern || _property_list_pattern) {
final Map<String,String> nameProps = name._getKeyPropertyList();
final Property[] props = _ca_array;
final String cn = _canonicalName;
for (int i = props.length - 1; i >= 0 ; i--) {
// Find value in given object name for key at current
// index in receiver
//
final Property p = props[i];
final String k = p.getKeyString(cn);
final String v = nameProps.get(k);
// Did we find a value for this key ?
//
if (v == null) return false;
// If this property is ok (same key, same value), go to next
//
if (_property_value_pattern && (p instanceof PatternProperty)) {
// wildmatch key property values
final char[] val_pattern =
p.getValueString(cn).toCharArray();
final char[] val_string = v.toCharArray();
if (wildmatch(val_string,val_pattern,0,0))
continue;
else
return false;
}
if (v.equals(p.getValueString(cn))) continue;
return false;
}
return true;
}
// If no pattern, then canonical names must be equal
//
final String p1 = name.getCanonicalKeyPropertyListString();
final String p2 = getCanonicalKeyPropertyListString();
return (p1.equals(p2));
}
/* Method inherited from QueryExp, no implementation needed here
because ObjectName is not relative to an MBeanServer and does
not contain a subquery.
*/
public void setMBeanServer(MBeanServer mbs) { }
// Category : QueryExp Interface <=========================
// Category : Comparable Interface ---------------------------------------->
/**
* <p>Compares two ObjectName instances. The ordering relation between
* ObjectNames is not completely specified but is intended to be such
* that a sorted list of ObjectNames will appear in an order that is
* convenient for a person to read.</p>
*
* <p>In particular, if the two ObjectName instances have different
* domains then their order is the lexicographical order of the domains.
* The ordering of the key property list remains unspecified.</p>
*
* <p>For example, the ObjectName instances below:</p>
* <ul>
* <li>Shapes:type=Square,name=3</li>
* <li>Colors:type=Red,name=2</li>
* <li>Shapes:type=Triangle,side=isosceles,name=2</li>
* <li>Colors:type=Red,name=1</li>
* <li>Shapes:type=Square,name=1</li>
* <li>Colors:type=Blue,name=1</li>
* <li>Shapes:type=Square,name=2</li>
* <li>JMImplementation:type=MBeanServerDelegate</li>
* <li>Shapes:type=Triangle,side=scalene,name=1</li>
* </ul>
* <p>could be ordered as follows:</p>
* <ul>
* <li>Colors:type=Blue,name=1</li>
* <li>Colors:type=Red,name=1</li>
* <li>Colors:type=Red,name=2</li>
* <li>JMImplementation:type=MBeanServerDelegate</li>
* <li>Shapes:type=Square,name=1</li>
* <li>Shapes:type=Square,name=2</li>
* <li>Shapes:type=Square,name=3</li>
* <li>Shapes:type=Triangle,side=scalene,name=1</li>
* <li>Shapes:type=Triangle,side=isosceles,name=2</li>
* </ul>
*
* @param name the ObjectName to be compared.
*
* @return a negative integer, zero, or a positive integer as this
* ObjectName is less than, equal to, or greater than the
* specified ObjectName.
*
* @since 1.6
*/
public int compareTo(ObjectName name) {
// (1) Compare domains
//
int domainValue = this.getDomain().compareTo(name.getDomain());
if (domainValue != 0)
return domainValue;
// (2) Compare "type=" keys
//
// Within a given domain, all names with missing or empty "type="
// come before all names with non-empty type.
//
// When both types are missing or empty, canonical-name ordering
// applies which is a total order.
//
String thisTypeKey = this.getKeyProperty("type");
String anotherTypeKey = name.getKeyProperty("type");
if (thisTypeKey == null)
thisTypeKey = "";
if (anotherTypeKey == null)
anotherTypeKey = "";
int typeKeyValue = thisTypeKey.compareTo(anotherTypeKey);
if (typeKeyValue != 0)
return typeKeyValue;
// (3) Compare canonical names
//
return this.getCanonicalName().compareTo(name.getCanonicalName());
}
// Category : Comparable Interface <=========================
// Public methods <========================================
}