|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package javax.management; |
|
|
|
import com.sun.jmx.mbeanserver.Util; |
|
import java.io.InvalidObjectException; |
|
import java.lang.reflect.Array; |
|
import java.util.Arrays; |
|
import java.util.Comparator; |
|
import java.util.Map; |
|
import java.util.SortedMap; |
|
import java.util.TreeMap; |
|
|
|
|
|
|
|
|
|
*/ |
|
public class ImmutableDescriptor implements Descriptor { |
|
private static final long serialVersionUID = 8853308591080540165L; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private final String[] names; |
|
|
|
|
|
|
|
|
|
*/ |
|
private final Object[] values; |
|
|
|
private transient int hashCode = -1; |
|
|
|
|
|
|
|
*/ |
|
public static final ImmutableDescriptor EMPTY_DESCRIPTOR = |
|
new ImmutableDescriptor(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ImmutableDescriptor(String[] fieldNames, Object[] fieldValues) { |
|
this(makeMap(fieldNames, fieldValues)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ImmutableDescriptor(String... fields) { |
|
this(makeMap(fields)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ImmutableDescriptor(Map<String, ?> fields) { |
|
if (fields == null) |
|
throw new IllegalArgumentException("Null Map"); |
|
SortedMap<String, Object> map = |
|
new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); |
|
for (Map.Entry<String, ?> entry : fields.entrySet()) { |
|
String name = entry.getKey(); |
|
if (name == null || name.equals("")) |
|
throw new IllegalArgumentException("Empty or null field name"); |
|
if (map.containsKey(name)) |
|
throw new IllegalArgumentException("Duplicate name: " + name); |
|
map.put(name, entry.getValue()); |
|
} |
|
int size = map.size(); |
|
this.names = map.keySet().toArray(new String[size]); |
|
this.values = map.values().toArray(new Object[size]); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Object readResolve() throws InvalidObjectException { |
|
|
|
boolean bad = false; |
|
if (names == null || values == null || names.length != values.length) |
|
bad = true; |
|
if (!bad) { |
|
if (names.length == 0 && getClass() == ImmutableDescriptor.class) |
|
return EMPTY_DESCRIPTOR; |
|
final Comparator<String> compare = String.CASE_INSENSITIVE_ORDER; |
|
String lastName = ""; |
|
for (int i = 0; i < names.length; i++) { |
|
if (names[i] == null || |
|
compare.compare(lastName, names[i]) >= 0) { |
|
bad = true; |
|
break; |
|
} |
|
lastName = names[i]; |
|
} |
|
} |
|
if (bad) |
|
throw new InvalidObjectException("Bad names or values"); |
|
|
|
return this; |
|
} |
|
|
|
private static SortedMap<String, ?> makeMap(String[] fieldNames, |
|
Object[] fieldValues) { |
|
if (fieldNames == null || fieldValues == null) |
|
throw new IllegalArgumentException("Null array parameter"); |
|
if (fieldNames.length != fieldValues.length) |
|
throw new IllegalArgumentException("Different size arrays"); |
|
SortedMap<String, Object> map = |
|
new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); |
|
for (int i = 0; i < fieldNames.length; i++) { |
|
String name = fieldNames[i]; |
|
if (name == null || name.equals("")) |
|
throw new IllegalArgumentException("Empty or null field name"); |
|
Object old = map.put(name, fieldValues[i]); |
|
if (old != null) { |
|
throw new IllegalArgumentException("Duplicate field name: " + |
|
name); |
|
} |
|
} |
|
return map; |
|
} |
|
|
|
private static SortedMap<String, ?> makeMap(String[] fields) { |
|
if (fields == null) |
|
throw new IllegalArgumentException("Null fields parameter"); |
|
String[] fieldNames = new String[fields.length]; |
|
String[] fieldValues = new String[fields.length]; |
|
for (int i = 0; i < fields.length; i++) { |
|
String field = fields[i]; |
|
int eq = field.indexOf('='); |
|
if (eq < 0) { |
|
throw new IllegalArgumentException("Missing = character: " + |
|
field); |
|
} |
|
fieldNames[i] = field.substring(0, eq); |
|
|
|
fieldValues[i] = field.substring(eq + 1); |
|
} |
|
return makeMap(fieldNames, fieldValues); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static ImmutableDescriptor union(Descriptor... descriptors) { |
|
// Optimize the case where exactly one Descriptor is non-Empty |
|
|
|
int index = findNonEmpty(descriptors, 0); |
|
if (index < 0) |
|
return EMPTY_DESCRIPTOR; |
|
if (descriptors[index] instanceof ImmutableDescriptor |
|
&& findNonEmpty(descriptors, index + 1) < 0) |
|
return (ImmutableDescriptor) descriptors[index]; |
|
|
|
Map<String, Object> map = |
|
new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); |
|
ImmutableDescriptor biggestImmutable = EMPTY_DESCRIPTOR; |
|
for (Descriptor d : descriptors) { |
|
if (d != null) { |
|
String[] names; |
|
if (d instanceof ImmutableDescriptor) { |
|
ImmutableDescriptor id = (ImmutableDescriptor) d; |
|
names = id.names; |
|
if (id.getClass() == ImmutableDescriptor.class |
|
&& names.length > biggestImmutable.names.length) |
|
biggestImmutable = id; |
|
} else |
|
names = d.getFieldNames(); |
|
for (String n : names) { |
|
Object v = d.getFieldValue(n); |
|
Object old = map.put(n, v); |
|
if (old != null) { |
|
boolean equal; |
|
if (old.getClass().isArray()) { |
|
equal = Arrays.deepEquals(new Object[] {old}, |
|
new Object[] {v}); |
|
} else |
|
equal = old.equals(v); |
|
if (!equal) { |
|
final String msg = |
|
"Inconsistent values for descriptor field " + |
|
n + ": " + old + " :: " + v; |
|
throw new IllegalArgumentException(msg); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if (biggestImmutable.names.length == map.size()) |
|
return biggestImmutable; |
|
return new ImmutableDescriptor(map); |
|
} |
|
|
|
private static boolean isEmpty(Descriptor d) { |
|
if (d == null) |
|
return true; |
|
else if (d instanceof ImmutableDescriptor) |
|
return ((ImmutableDescriptor) d).names.length == 0; |
|
else |
|
return (d.getFieldNames().length == 0); |
|
} |
|
|
|
private static int findNonEmpty(Descriptor[] ds, int start) { |
|
for (int i = start; i < ds.length; i++) { |
|
if (!isEmpty(ds[i])) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
private int fieldIndex(String name) { |
|
return Arrays.binarySearch(names, name, String.CASE_INSENSITIVE_ORDER); |
|
} |
|
|
|
public final Object getFieldValue(String fieldName) { |
|
checkIllegalFieldName(fieldName); |
|
int i = fieldIndex(fieldName); |
|
if (i < 0) |
|
return null; |
|
Object v = values[i]; |
|
if (v == null || !v.getClass().isArray()) |
|
return v; |
|
if (v instanceof Object[]) |
|
return ((Object[]) v).clone(); |
|
|
|
int len = Array.getLength(v); |
|
Object a = Array.newInstance(v.getClass().getComponentType(), len); |
|
System.arraycopy(v, 0, a, 0, len); |
|
return a; |
|
} |
|
|
|
public final String[] getFields() { |
|
String[] result = new String[names.length]; |
|
for (int i = 0; i < result.length; i++) { |
|
Object value = values[i]; |
|
if (value == null) |
|
value = ""; |
|
else if (!(value instanceof String)) |
|
value = "(" + value + ")"; |
|
result[i] = names[i] + "=" + value; |
|
} |
|
return result; |
|
} |
|
|
|
public final Object[] getFieldValues(String... fieldNames) { |
|
if (fieldNames == null) |
|
return values.clone(); |
|
Object[] result = new Object[fieldNames.length]; |
|
for (int i = 0; i < fieldNames.length; i++) { |
|
String name = fieldNames[i]; |
|
if (name != null && !name.equals("")) |
|
result[i] = getFieldValue(name); |
|
} |
|
return result; |
|
} |
|
|
|
public final String[] getFieldNames() { |
|
return names.clone(); |
|
} |
|
|
|
/** |
|
* Compares this descriptor to the given object. The objects are equal if |
|
* the given object is also a Descriptor, and if the two Descriptors have |
|
* the same field names (possibly differing in case) and the same |
|
* associated values. The respective values for a field in the two |
|
* Descriptors are equal if the following conditions hold: |
|
* |
|
* <ul> |
|
* <li>If one value is null then the other must be too.</li> |
|
* <li>If one value is a primitive array then the other must be a primitive |
|
* array of the same type with the same elements.</li> |
|
* <li>If one value is an object array then the other must be too and |
|
* {@link Arrays#deepEquals(Object[],Object[])} must return true.</li> |
|
* <li>Otherwise {@link Object#equals(Object)} must return true.</li> |
|
* </ul> |
|
* |
|
* @param o the object to compare with. |
|
* |
|
* @return {@code true} if the objects are the same; {@code false} |
|
* otherwise. |
|
* |
|
*/ |
|
// Note: this Javadoc is copied from javax.management.Descriptor |
|
|
|
@Override |
|
public boolean equals(Object o) { |
|
if (o == this) |
|
return true; |
|
if (!(o instanceof Descriptor)) |
|
return false; |
|
String[] onames; |
|
if (o instanceof ImmutableDescriptor) { |
|
onames = ((ImmutableDescriptor) o).names; |
|
} else { |
|
onames = ((Descriptor) o).getFieldNames(); |
|
Arrays.sort(onames, String.CASE_INSENSITIVE_ORDER); |
|
} |
|
if (names.length != onames.length) |
|
return false; |
|
for (int i = 0; i < names.length; i++) { |
|
if (!names[i].equalsIgnoreCase(onames[i])) |
|
return false; |
|
} |
|
Object[] ovalues; |
|
if (o instanceof ImmutableDescriptor) |
|
ovalues = ((ImmutableDescriptor) o).values; |
|
else |
|
ovalues = ((Descriptor) o).getFieldValues(onames); |
|
return Arrays.deepEquals(values, ovalues); |
|
} |
|
|
|
/** |
|
* <p>Returns the hash code value for this descriptor. The hash |
|
* code is computed as the sum of the hash codes for each field in |
|
* the descriptor. The hash code of a field with name {@code n} |
|
* and value {@code v} is {@code n.toLowerCase().hashCode() ^ h}. |
|
* Here {@code h} is the hash code of {@code v}, computed as |
|
* follows:</p> |
|
* |
|
* <ul> |
|
* <li>If {@code v} is null then {@code h} is 0.</li> |
|
* <li>If {@code v} is a primitive array then {@code h} is computed using |
|
* the appropriate overloading of {@code java.util.Arrays.hashCode}.</li> |
|
* <li>If {@code v} is an object array then {@code h} is computed using |
|
* {@link Arrays#deepHashCode(Object[])}.</li> |
|
* <li>Otherwise {@code h} is {@code v.hashCode()}.</li> |
|
* </ul> |
|
* |
|
* @return A hash code value for this object. |
|
* |
|
*/ |
|
// Note: this Javadoc is copied from javax.management.Descriptor |
|
|
|
@Override |
|
public int hashCode() { |
|
if (hashCode == -1) { |
|
hashCode = Util.hashCode(names, values); |
|
} |
|
return hashCode; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
StringBuilder sb = new StringBuilder("{"); |
|
for (int i = 0; i < names.length; i++) { |
|
if (i > 0) |
|
sb.append(", "); |
|
sb.append(names[i]).append("="); |
|
Object v = values[i]; |
|
if (v != null && v.getClass().isArray()) { |
|
String s = Arrays.deepToString(new Object[] {v}); |
|
s = s.substring(1, s.length() - 1); |
|
v = s; |
|
} |
|
sb.append(String.valueOf(v)); |
|
} |
|
return sb.append("}").toString(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isValid() { |
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Descriptor clone() { |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final void setFields(String[] fieldNames, Object[] fieldValues) |
|
throws RuntimeOperationsException { |
|
if (fieldNames == null || fieldValues == null) |
|
illegal("Null argument"); |
|
if (fieldNames.length != fieldValues.length) |
|
illegal("Different array sizes"); |
|
for (int i = 0; i < fieldNames.length; i++) |
|
checkIllegalFieldName(fieldNames[i]); |
|
for (int i = 0; i < fieldNames.length; i++) |
|
setField(fieldNames[i], fieldValues[i]); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final void setField(String fieldName, Object fieldValue) |
|
throws RuntimeOperationsException { |
|
checkIllegalFieldName(fieldName); |
|
int i = fieldIndex(fieldName); |
|
if (i < 0) |
|
unsupported(); |
|
Object value = values[i]; |
|
if ((value == null) ? |
|
(fieldValue != null) : |
|
!value.equals(fieldValue)) |
|
unsupported(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final void removeField(String fieldName) { |
|
if (fieldName != null && fieldIndex(fieldName) >= 0) |
|
unsupported(); |
|
} |
|
|
|
static Descriptor nonNullDescriptor(Descriptor d) { |
|
if (d == null) |
|
return EMPTY_DESCRIPTOR; |
|
else |
|
return d; |
|
} |
|
|
|
private static void checkIllegalFieldName(String name) { |
|
if (name == null || name.equals("")) |
|
illegal("Null or empty field name"); |
|
} |
|
|
|
private static void unsupported() { |
|
UnsupportedOperationException uoe = |
|
new UnsupportedOperationException("Descriptor is read-only"); |
|
throw new RuntimeOperationsException(uoe); |
|
} |
|
|
|
private static void illegal(String message) { |
|
IllegalArgumentException iae = new IllegalArgumentException(message); |
|
throw new RuntimeOperationsException(iae); |
|
} |
|
} |