/*
* Copyright (c) 2005, 2006, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jmx.mbeanserver;
import static com.sun.jmx.mbeanserver.Util.*;
import static javax.management.openmbean.SimpleType.*;
import com.sun.jmx.remote.util.EnvHelp;
import java.beans.ConstructorProperties;
import java.io.InvalidObjectException;
import java.lang.annotation.ElementType;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
import javax.management.JMX;
import javax.management.ObjectName;
import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataInvocationHandler;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeDataView;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
A converter between Java types and the limited set of classes
defined by Open MBeans.
A Java type is an instance of java.lang.reflect.Type. For our
purposes, it is either a Class, such as String.class or int.class;
or a ParameterizedType, such as List or Map. On J2SE 1.4 and earlier, it can only be a Class.
Each Type is associated with an OpenConverter. The
OpenConverter defines an OpenType corresponding to the Type, plus a
Java class corresponding to the OpenType. For example:
Type Open class OpenType
---- ---------- --------
Integer Integer SimpleType.INTEGER
int int SimpleType.INTEGER
Integer[] Integer[] ArrayType(1, SimpleType.INTEGER)
int[] Integer[] ArrayType(SimpleType.INTEGER, true)
String[][] String[][] ArrayType(2, SimpleType.STRING)
List String[] ArrayType(1, SimpleType.STRING)
ThreadState (an Enum) String SimpleType.STRING
Map TabularData TabularType(
CompositeType(
{"key", SimpleType.INTEGER},
{"value",
ArrayType(1,
SimpleType.STRING)}),
indexNames={"key"})
Apart from simple types, arrays, and collections, Java types are
converted through introspection into CompositeType. The Java type
must have at least one getter (method such as "int getSize()" or
"boolean isBig()"), and we must be able to deduce how to
reconstruct an instance of the Java class from the values of the
getters using one of various heuristics.
Since: 1.6
/**
<p>A converter between Java types and the limited set of classes
defined by Open MBeans.</p>
<p>A Java type is an instance of java.lang.reflect.Type. For our
purposes, it is either a Class, such as String.class or int.class;
or a ParameterizedType, such as List<String> or Map<Integer,
String[]>. On J2SE 1.4 and earlier, it can only be a Class.</p>
<p>Each Type is associated with an OpenConverter. The
OpenConverter defines an OpenType corresponding to the Type, plus a
Java class corresponding to the OpenType. For example:</p>
<pre>
Type Open class OpenType
---- ---------- --------
Integer Integer SimpleType.INTEGER
int int SimpleType.INTEGER
Integer[] Integer[] ArrayType(1, SimpleType.INTEGER)
int[] Integer[] ArrayType(SimpleType.INTEGER, true)
String[][] String[][] ArrayType(2, SimpleType.STRING)
List<String> String[] ArrayType(1, SimpleType.STRING)
ThreadState (an Enum) String SimpleType.STRING
Map<Integer, String[]> TabularData TabularType(
CompositeType(
{"key", SimpleType.INTEGER},
{"value",
ArrayType(1,
SimpleType.STRING)}),
indexNames={"key"})
</pre>
<p>Apart from simple types, arrays, and collections, Java types are
converted through introspection into CompositeType. The Java type
must have at least one getter (method such as "int getSize()" or
"boolean isBig()"), and we must be able to deduce how to
reconstruct an instance of the Java class from the values of the
getters using one of various heuristics.</p>
@since 1.6
*/
public abstract class OpenConverter {
private OpenConverter(Type targetType, OpenType openType,
Class openClass) {
this.targetType = targetType;
this.openType = openType;
this.openClass = openClass;
}
Convert an instance of openClass into an instance of targetType.
/** <p>Convert an instance of openClass into an instance of targetType. */
public final Object fromOpenValue(MXBeanLookup lookup, Object value)
throws InvalidObjectException {
if (value == null)
return null;
else
return fromNonNullOpenValue(lookup, value);
}
abstract Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
throws InvalidObjectException;
Throw an appropriate InvalidObjectException if we will not be able
to convert back from the open data to the original Java object.
/** <p>Throw an appropriate InvalidObjectException if we will not be able
to convert back from the open data to the original Java object.</p> */
void checkReconstructible() throws InvalidObjectException {
// subclasses override if action necessary
}
Convert an instance of targetType into an instance of openClass.
/** <p>Convert an instance of targetType into an instance of openClass. */
final Object toOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
if (value == null)
return null;
else
return toNonNullOpenValue(lookup, value);
}
abstract Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException;
True if and only if this OpenConverter's toOpenValue and fromOpenValue
methods are the identity function.
/** <p>True if and only if this OpenConverter's toOpenValue and fromOpenValue
methods are the identity function.</p> */
boolean isIdentity() {
return false;
}
True if and only if isIdentity() and even an array of the underlying type
is transformed as the identity. This is true for Integer and
ObjectName, for instance, but not for int.
/** <p>True if and only if isIdentity() and even an array of the underlying type
is transformed as the identity. This is true for Integer and
ObjectName, for instance, but not for int.</p> */
final Type getTargetType() {
return targetType;
}
final OpenType getOpenType() {
return openType;
}
/* The Java class corresponding to getOpenType(). This is the class
named by getOpenType().getClassName(), except that it may be a
primitive type or an array of primitive type. */
final Class getOpenClass() {
return openClass;
}
private final Type targetType;
private final OpenType openType;
private final Class openClass;
private static final class ConverterMap
extends WeakHashMap<Type, WeakReference<OpenConverter>> {}
private static final ConverterMap converterMap = new ConverterMap();
Following List simply serves to keep a reference to predefined
OpenConverters so they don't get garbage collected. /** Following List simply serves to keep a reference to predefined
OpenConverters so they don't get garbage collected. */
private static final List<OpenConverter> permanentConverters = newList();
private static synchronized OpenConverter getConverter(Type type) {
WeakReference<OpenConverter> wr = converterMap.get(type);
return (wr == null) ? null : wr.get();
}
private static synchronized void putConverter(Type type,
OpenConverter conv) {
WeakReference<OpenConverter> wr =
new WeakReference<OpenConverter>(conv);
converterMap.put(type, wr);
}
private static synchronized void putPermanentConverter(Type type,
OpenConverter conv) {
putConverter(type, conv);
permanentConverters.add(conv);
}
static {
/* Set up the mappings for Java types that map to SimpleType. */
final OpenType[] simpleTypes = {
BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
VOID,
};
for (int i = 0; i < simpleTypes.length; i++) {
final OpenType t = simpleTypes[i];
Class c;
try {
c = Class.forName(t.getClassName(), false,
ObjectName.class.getClassLoader());
} catch (ClassNotFoundException e) {
// the classes that these predefined types declare must exist!
throw new Error(e);
}
final OpenConverter conv = new IdentityConverter(c, t, c);
putPermanentConverter(c, conv);
if (c.getName().startsWith("java.lang.")) {
try {
final Field typeField = c.getField("TYPE");
final Class primitiveType = (Class) typeField.get(null);
final OpenConverter primitiveConv =
new IdentityConverter(primitiveType, t, primitiveType);
putPermanentConverter(primitiveType,
primitiveConv);
if (primitiveType != void.class) {
final Class<?> primitiveArrayType =
Array.newInstance(primitiveType, 0).getClass();
final OpenType primitiveArrayOpenType =
ArrayType.getPrimitiveArrayType(primitiveArrayType);
final OpenConverter primitiveArrayConv =
new IdentityConverter(primitiveArrayType,
primitiveArrayOpenType,
primitiveArrayType);
putPermanentConverter(primitiveArrayType,
primitiveArrayConv);
}
} catch (NoSuchFieldException e) {
// OK: must not be a primitive wrapper
} catch (IllegalAccessException e) {
// Should not reach here
assert(false);
}
}
}
}
Get the converter for the given Java type, creating it if necessary. /** Get the converter for the given Java type, creating it if necessary. */
public static synchronized OpenConverter toConverter(Type objType)
throws OpenDataException {
if (inProgress.containsKey(objType))
throw new OpenDataException("Recursive data structure");
OpenConverter conv;
conv = getConverter(objType);
if (conv != null)
return conv;
inProgress.put(objType, objType);
try {
conv = makeConverter(objType);
} finally {
inProgress.remove(objType);
}
putConverter(objType, conv);
return conv;
}
private static OpenConverter makeConverter(Type objType)
throws OpenDataException {
/* It's not yet worth formalizing these tests by having for example
an array of factory classes, each of which says whether it
recognizes the Type (Chain of Responsibility pattern). */
if (objType instanceof GenericArrayType) {
Type componentType =
((GenericArrayType) objType).getGenericComponentType();
return makeArrayOrCollectionConverter(objType, componentType);
} else if (objType instanceof Class) {
Class<?> objClass = (Class<?>) objType;
if (objClass.isEnum()) {
// Huge hack to avoid compiler warnings here. The ElementType
// parameter is ignored but allows us to obtain a type variable
// T that matches <T extends Enum<T>>.
return makeEnumConverter(objClass, ElementType.class);
} else if (objClass.isArray()) {
Type componentType = objClass.getComponentType();
return makeArrayOrCollectionConverter(objClass, componentType);
} else if (JMX.isMXBeanInterface(objClass)) {
return makeMXBeanConverter(objClass);
} else {
return makeCompositeConverter(objClass);
}
} else if (objType instanceof ParameterizedType) {
return makeParameterizedConverter((ParameterizedType) objType);
} else
throw new OpenDataException("Cannot map type: " + objType);
}
private static <T extends Enum<T>> OpenConverter
makeEnumConverter(Class<?> enumClass, Class<T> fake) {
Class<T> enumClassT = Util.cast(enumClass);
return new EnumConverter<T>(enumClassT);
}
/* Make the converter for an array type, or a collection such as
* List<String> or Set<Integer>. We never see one-dimensional
* primitive arrays (e.g. int[]) here because they use the identity
* converter and are registered as such in the static initializer.
*/
private static OpenConverter
makeArrayOrCollectionConverter(Type collectionType, Type elementType)
throws OpenDataException {
final OpenConverter elementConverter = toConverter(elementType);
final OpenType<?> elementOpenType = elementConverter.getOpenType();
final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
final Class<?> elementOpenClass = elementConverter.getOpenClass();
final Class<?> openArrayClass;
final String openArrayClassName;
if (elementOpenClass.isArray())
openArrayClassName = "[" + elementOpenClass.getName();
else
openArrayClassName = "[L" + elementOpenClass.getName() + ";";
try {
openArrayClass = Class.forName(openArrayClassName);
} catch (ClassNotFoundException e) {
throw openDataException("Cannot obtain array class", e);
}
if (collectionType instanceof ParameterizedType) {
return new CollectionConverter(collectionType,
openType, openArrayClass,
elementConverter);
} else {
if (elementConverter.isIdentity()) {
return new IdentityConverter(collectionType,
openType,
openArrayClass);
} else {
return new ArrayConverter(collectionType,
openType,
openArrayClass,
elementConverter);
}
}
}
private static final String[] keyArray = {"key"};
private static final String[] keyValueArray = {"key", "value"};
private static OpenConverter
makeTabularConverter(Type objType, boolean sortedMap,
Type keyType, Type valueType)
throws OpenDataException {
final String objTypeName = objType.toString();
final OpenConverter keyConverter = toConverter(keyType);
final OpenConverter valueConverter = toConverter(valueType);
final OpenType keyOpenType = keyConverter.getOpenType();
final OpenType valueOpenType = valueConverter.getOpenType();
final CompositeType rowType =
new CompositeType(objTypeName,
objTypeName,
keyValueArray,
keyValueArray,
new OpenType[] {keyOpenType, valueOpenType});
final TabularType tabularType =
new TabularType(objTypeName, objTypeName, rowType, keyArray);
return new TabularConverter(objType, sortedMap, tabularType,
keyConverter, valueConverter);
}
/* We know how to translate List<E>, Set<E>, SortedSet<E>,
Map<K,V>, SortedMap<K,V>, and that's it. We don't accept
subtypes of those because we wouldn't know how to deserialize
them. We don't accept Queue<E> because it is unlikely people
would use that as a parameter or return type in an MBean. */
private static OpenConverter
makeParameterizedConverter(ParameterizedType objType) throws OpenDataException {
final Type rawType = objType.getRawType();
if (rawType instanceof Class) {
Class c = (Class<?>) rawType;
if (c == List.class || c == Set.class || c == SortedSet.class) {
Type[] actuals = objType.getActualTypeArguments();
assert(actuals.length == 1);
if (c == SortedSet.class)
mustBeComparable(c, actuals[0]);
return makeArrayOrCollectionConverter(objType, actuals[0]);
} else {
boolean sortedMap = (c == SortedMap.class);
if (c == Map.class || sortedMap) {
Type[] actuals = objType.getActualTypeArguments();
assert(actuals.length == 2);
if (sortedMap)
mustBeComparable(c, actuals[0]);
return makeTabularConverter(objType, sortedMap,
actuals[0], actuals[1]);
}
}
}
throw new OpenDataException("Cannot convert type: " + objType);
}
private static OpenConverter makeMXBeanConverter(Type t)
throws OpenDataException {
return new MXBeanConverter(t);
}
private static OpenConverter makeCompositeConverter(Class c)
throws OpenDataException {
// For historical reasons GcInfo implements CompositeData but we
// shouldn't count its CompositeData.getCompositeType() field as
// an item in the computed CompositeType.
final boolean gcInfoHack =
(c.getName().equals("com.sun.management.GcInfo") &&
c.getClassLoader() == null);
final List<Method> methods =
MBeanAnalyzer.eliminateCovariantMethods(c.getMethods());
final SortedMap<String,Method> getterMap = newSortedMap();
/* Select public methods that look like "T getX()" or "boolean
isX()", where T is not void and X is not the empty
string. Exclude "Class getClass()" inherited from Object. */
for (Method method : methods) {
final String propertyName = propertyName(method);
if (propertyName == null)
continue;
if (gcInfoHack && propertyName.equals("CompositeType"))
continue;
Method old =
getterMap.put(decapitalize(propertyName),
method);
if (old != null) {
final String msg =
"Class " + c.getName() + " has method name clash: " +
old.getName() + ", " + method.getName();
throw new OpenDataException(msg);
}
}
final int nitems = getterMap.size();
if (nitems == 0) {
throw new OpenDataException("Can't map " + c.getName() +
" to an open data type");
}
final Method[] getters = new Method[nitems];
final String[] itemNames = new String[nitems];
final OpenType[] openTypes = new OpenType[nitems];
int i = 0;
for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
itemNames[i] = entry.getKey();
final Method getter = entry.getValue();
getters[i] = getter;
final Type retType = getter.getGenericReturnType();
openTypes[i] = toConverter(retType).getOpenType();
i++;
}
CompositeType compositeType =
new CompositeType(c.getName(),
c.getName(),
itemNames, // field names
itemNames, // field descriptions
openTypes);
return new CompositeConverter(c,
compositeType,
itemNames,
getters);
}
/* Converter for classes where the open data is identical to the
original data. This is true for any of the SimpleType types,
and for an any-dimension array of those. It is also true for
primitive types as of JMX 1.3, since an int[] needs to
can be directly represented by an ArrayType, and an int needs no mapping
because reflection takes care of it. */
private static final class IdentityConverter extends OpenConverter {
IdentityConverter(Type targetType, OpenType openType,
Class openClass) {
super(targetType, openType, openClass);
}
boolean isIdentity() {
return true;
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
return value;
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) {
return value;
}
}
private static final class EnumConverter<T extends Enum<T>>
extends OpenConverter {
EnumConverter(Class<T> enumClass) {
super(enumClass, SimpleType.STRING, String.class);
this.enumClass = enumClass;
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
return ((Enum) value).name();
}
// return type could be T, but after erasure that would be
// java.lang.Enum, which doesn't exist on J2SE 1.4
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
throws InvalidObjectException {
try {
return Enum.valueOf(enumClass, (String) value);
} catch (Exception e) {
throw invalidObjectException("Cannot convert to enum: " +
value, e);
}
}
private final Class<T> enumClass;
}
private static final class ArrayConverter extends OpenConverter {
ArrayConverter(Type targetType,
ArrayType openArrayType, Class openArrayClass,
OpenConverter elementConverter) {
super(targetType, openArrayType, openArrayClass);
this.elementConverter = elementConverter;
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
Object[] valueArray = (Object[]) value;
final int len = valueArray.length;
final Object[] openArray = (Object[])
Array.newInstance(getOpenClass().getComponentType(), len);
for (int i = 0; i < len; i++) {
openArray[i] =
elementConverter.toOpenValue(lookup, valueArray[i]);
}
return openArray;
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
throws InvalidObjectException {
final Object[] openArray = (Object[]) openValue;
final Type targetType = getTargetType();
final Object[] valueArray;
final Type componentType;
if (targetType instanceof GenericArrayType) {
componentType =
((GenericArrayType) targetType).getGenericComponentType();
} else if (targetType instanceof Class &&
((Class<?>) targetType).isArray()) {
componentType = ((Class<?>) targetType).getComponentType();
} else {
throw new IllegalArgumentException("Not an array: " +
targetType);
}
valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
openArray.length);
for (int i = 0; i < openArray.length; i++) {
valueArray[i] =
elementConverter.fromOpenValue(lookup, openArray[i]);
}
return valueArray;
}
void checkReconstructible() throws InvalidObjectException {
elementConverter.checkReconstructible();
}
OpenConverter for the elements of this array. If this is an
array of arrays, the converter converts the second-level arrays,
not the deepest elements. /** OpenConverter for the elements of this array. If this is an
array of arrays, the converter converts the second-level arrays,
not the deepest elements. */
private final OpenConverter elementConverter;
}
private static final class CollectionConverter extends OpenConverter {
CollectionConverter(Type targetType,
ArrayType openArrayType,
Class openArrayClass,
OpenConverter elementConverter) {
super(targetType, openArrayType, openArrayClass);
this.elementConverter = elementConverter;
/* Determine the concrete class to be used when converting
back to this Java type. We convert all Lists to ArrayList
and all Sets to TreeSet. (TreeSet because it is a SortedSet,
so works for both Set and SortedSet.) */
Type raw = ((ParameterizedType) targetType).getRawType();
Class c = (Class<?>) raw;
if (c == List.class)
collectionClass = ArrayList.class;
else if (c == Set.class)
collectionClass = HashSet.class;
else if (c == SortedSet.class)
collectionClass = TreeSet.class;
else { // can't happen
assert(false);
collectionClass = null;
}
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
final Collection valueCollection = (Collection) value;
if (valueCollection instanceof SortedSet) {
Comparator comparator =
((SortedSet) valueCollection).comparator();
if (comparator != null) {
final String msg =
"Cannot convert SortedSet with non-null comparator: " +
comparator;
throw new OpenDataException(msg);
}
}
final Object[] openArray = (Object[])
Array.newInstance(getOpenClass().getComponentType(),
valueCollection.size());
int i = 0;
for (Object o : valueCollection)
openArray[i++] = elementConverter.toOpenValue(lookup, o);
return openArray;
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
throws InvalidObjectException {
final Object[] openArray = (Object[]) openValue;
final Collection<Object> valueCollection;
try {
valueCollection = Util.cast(collectionClass.newInstance());
} catch (Exception e) {
throw invalidObjectException("Cannot create collection", e);
}
for (Object o : openArray) {
Object value = elementConverter.fromOpenValue(lookup, o);
if (!valueCollection.add(value)) {
final String msg =
"Could not add " + o + " to " +
collectionClass.getName() +
" (duplicate set element?)";
throw new InvalidObjectException(msg);
}
}
return valueCollection;
}
void checkReconstructible() throws InvalidObjectException {
elementConverter.checkReconstructible();
}
private final Class<? extends Collection> collectionClass;
private final OpenConverter elementConverter;
}
private static final class MXBeanConverter extends OpenConverter {
MXBeanConverter(Type intf) {
super(intf, SimpleType.OBJECTNAME, ObjectName.class);
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
lookupNotNull(lookup, OpenDataException.class);
ObjectName name = lookup.mxbeanToObjectName(value);
if (name == null)
throw new OpenDataException("No name for object: " + value);
return name;
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
throws InvalidObjectException {
lookupNotNull(lookup, InvalidObjectException.class);
ObjectName name = (ObjectName) value;
Object mxbean =
lookup.objectNameToMXBean(name, (Class<?>) getTargetType());
if (mxbean == null) {
final String msg =
"No MXBean for name: " + name;
throw new InvalidObjectException(msg);
}
return mxbean;
}
private <T extends Exception> void
lookupNotNull(MXBeanLookup lookup, Class<T> excClass)
throws T {
if (lookup == null) {
final String msg =
"Cannot convert MXBean interface in this context";
T exc;
try {
Constructor<T> con = excClass.getConstructor(String.class);
exc = con.newInstance(msg);
} catch (Exception e) {
throw new RuntimeException(e);
}
throw exc;
}
}
}
private static final class TabularConverter extends OpenConverter {
TabularConverter(Type targetType,
boolean sortedMap,
TabularType tabularType,
OpenConverter keyConverter,
OpenConverter valueConverter) {
super(targetType, tabularType, TabularData.class);
this.sortedMap = sortedMap;
this.keyConverter = keyConverter;
this.valueConverter = valueConverter;
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
final Map<Object, Object> valueMap = Util.cast(value);
if (valueMap instanceof SortedMap) {
Comparator comparator = ((SortedMap) valueMap).comparator();
if (comparator != null) {
final String msg =
"Cannot convert SortedMap with non-null comparator: " +
comparator;
throw new OpenDataException(msg);
}
}
final TabularType tabularType = (TabularType) getOpenType();
final TabularData table = new TabularDataSupport(tabularType);
final CompositeType rowType = tabularType.getRowType();
for (Map.Entry entry : valueMap.entrySet()) {
final Object openKey =
keyConverter.toOpenValue(lookup, entry.getKey());
final Object openValue =
valueConverter.toOpenValue(lookup, entry.getValue());
final CompositeData row;
row =
new CompositeDataSupport(rowType, keyValueArray,
new Object[] {openKey,
openValue});
table.put(row);
}
return table;
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
throws InvalidObjectException {
final TabularData table = (TabularData) openValue;
final Collection<CompositeData> rows = Util.cast(table.values());
final Map<Object, Object> valueMap =
sortedMap ? newSortedMap() : newMap();
for (CompositeData row : rows) {
final Object key =
keyConverter.fromOpenValue(lookup, row.get("key"));
final Object value =
valueConverter.fromOpenValue(lookup, row.get("value"));
if (valueMap.put(key, value) != null) {
final String msg =
"Duplicate entry in TabularData: key=" + key;
throw new InvalidObjectException(msg);
}
}
return valueMap;
}
void checkReconstructible() throws InvalidObjectException {
keyConverter.checkReconstructible();
valueConverter.checkReconstructible();
}
private final boolean sortedMap;
private final OpenConverter keyConverter;
private final OpenConverter valueConverter;
}
private static final class CompositeConverter extends OpenConverter {
CompositeConverter(Class targetClass,
CompositeType compositeType,
String[] itemNames,
Method[] getters) throws OpenDataException {
super(targetClass, compositeType, CompositeData.class);
assert(itemNames.length == getters.length);
this.itemNames = itemNames;
this.getters = getters;
this.getterConverters = new OpenConverter[getters.length];
for (int i = 0; i < getters.length; i++) {
Type retType = getters[i].getGenericReturnType();
getterConverters[i] = OpenConverter.toConverter(retType);
}
}
final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
throws OpenDataException {
CompositeType ct = (CompositeType) getOpenType();
if (value instanceof CompositeDataView)
return ((CompositeDataView) value).toCompositeData(ct);
if (value == null)
return null;
Object[] values = new Object[getters.length];
for (int i = 0; i < getters.length; i++) {
try {
Object got = getters[i].invoke(value, (Object[]) null);
values[i] = getterConverters[i].toOpenValue(lookup, got);
} catch (Exception e) {
throw openDataException("Error calling getter for " +
itemNames[i] + ": " + e, e);
}
}
return new CompositeDataSupport(ct, itemNames, values);
}
Determine how to convert back from the CompositeData into
the original Java type. For a type that is not reconstructible,
this method will fail every time, and will throw the right
exception. /** Determine how to convert back from the CompositeData into
the original Java type. For a type that is not reconstructible,
this method will fail every time, and will throw the right
exception. */
private synchronized void makeCompositeBuilder()
throws InvalidObjectException {
if (compositeBuilder != null)
return;
Class targetClass = (Class<?>) getTargetType();
/* In this 2D array, each subarray is a set of builders where
there is no point in consulting the ones after the first if
the first refuses. */
CompositeBuilder[][] builders = {
{
new CompositeBuilderViaFrom(targetClass, itemNames),
},
{
new CompositeBuilderViaConstructor(targetClass, itemNames),
},
{
new CompositeBuilderCheckGetters(targetClass, itemNames,
getterConverters),
new CompositeBuilderViaSetters(targetClass, itemNames),
new CompositeBuilderViaProxy(targetClass, itemNames),
},
};
CompositeBuilder foundBuilder = null;
/* We try to make a meaningful exception message by
concatenating each Builder's explanation of why it
isn't applicable. */
final StringBuilder whyNots = new StringBuilder();
find:
for (CompositeBuilder[] relatedBuilders : builders) {
for (int i = 0; i < relatedBuilders.length; i++) {
CompositeBuilder builder = relatedBuilders[i];
String whyNot = builder.applicable(getters);
if (whyNot == null) {
foundBuilder = builder;
break find;
}
if (whyNot.length() > 0) {
if (whyNots.length() > 0)
whyNots.append("; ");
whyNots.append(whyNot);
if (i == 0)
break; // skip other builders in this group
}
}
}
if (foundBuilder == null) {
final String msg =
"Do not know how to make a " + targetClass.getName() +
" from a CompositeData: " + whyNots;
throw new InvalidObjectException(msg);
}
compositeBuilder = foundBuilder;
}
void checkReconstructible() throws InvalidObjectException {
makeCompositeBuilder();
}
public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
throws InvalidObjectException {
makeCompositeBuilder();
return compositeBuilder.fromCompositeData(lookup,
(CompositeData) value,
itemNames,
getterConverters);
}
private final String[] itemNames;
private final Method[] getters;
private final OpenConverter[] getterConverters;
private CompositeBuilder compositeBuilder;
}
Converts from a CompositeData to an instance of the targetClass. /** Converts from a CompositeData to an instance of the targetClass. */
private static abstract class CompositeBuilder {
CompositeBuilder(Class targetClass, String[] itemNames) {
this.targetClass = targetClass;
this.itemNames = itemNames;
}
Class<?> getTargetClass() {
return targetClass;
}
String[] getItemNames() {
return itemNames;
}
If the subclass is appropriate for targetClass, then the
method returns null. If the subclass is not appropriate,
then the method returns an explanation of why not. If the
subclass should be appropriate but there is a problem,
then the method throws InvalidObjectException. /** If the subclass is appropriate for targetClass, then the
method returns null. If the subclass is not appropriate,
then the method returns an explanation of why not. If the
subclass should be appropriate but there is a problem,
then the method throws InvalidObjectException. */
abstract String applicable(Method[] getters)
throws InvalidObjectException;
abstract Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters)
throws InvalidObjectException;
private final Class<?> targetClass;
private final String[] itemNames;
}
Builder for when the target class has a method "public static
from(CompositeData)". /** Builder for when the target class has a method "public static
from(CompositeData)". */
private static final class CompositeBuilderViaFrom
extends CompositeBuilder {
CompositeBuilderViaFrom(Class targetClass, String[] itemNames) {
super(targetClass, itemNames);
}
String applicable(Method[] getters) throws InvalidObjectException {
// See if it has a method "T from(CompositeData)"
// as is conventional for a CompositeDataView
Class<?> targetClass = getTargetClass();
try {
Method fromMethod =
targetClass.getMethod("from",
new Class[] {CompositeData.class});
if (!Modifier.isStatic(fromMethod.getModifiers())) {
final String msg =
"Method from(CompositeData) is not static";
throw new InvalidObjectException(msg);
}
if (fromMethod.getReturnType() != getTargetClass()) {
final String msg =
"Method from(CompositeData) returns " +
fromMethod.getReturnType().getName() +
" not " + targetClass.getName();
throw new InvalidObjectException(msg);
}
this.fromMethod = fromMethod;
return null; // success!
} catch (InvalidObjectException e) {
throw e;
} catch (Exception e) {
// OK: it doesn't have the method
return "no method from(CompositeData)";
}
}
final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters)
throws InvalidObjectException {
try {
return fromMethod.invoke(null, cd);
} catch (Exception e) {
final String msg = "Failed to invoke from(CompositeData)";
throw invalidObjectException(msg, e);
}
}
private Method fromMethod;
}
This builder never actually returns success. It simply serves
to check whether the other builders in the same group have any
chance of success. If any getter in the targetClass returns
a type that we don't know how to reconstruct, then we will
not be able to make a builder, and there is no point in repeating
the error about the problematic getter as many times as there are
candidate builders. Instead, the "applicable" method will return
an explanatory string, and the other builders will be skipped.
If all the getters are OK, then the "applicable" method will return
an empty string and the other builders will be tried. /** This builder never actually returns success. It simply serves
to check whether the other builders in the same group have any
chance of success. If any getter in the targetClass returns
a type that we don't know how to reconstruct, then we will
not be able to make a builder, and there is no point in repeating
the error about the problematic getter as many times as there are
candidate builders. Instead, the "applicable" method will return
an explanatory string, and the other builders will be skipped.
If all the getters are OK, then the "applicable" method will return
an empty string and the other builders will be tried. */
private static class CompositeBuilderCheckGetters extends CompositeBuilder {
CompositeBuilderCheckGetters(Class targetClass, String[] itemNames,
OpenConverter[] getterConverters) {
super(targetClass, itemNames);
this.getterConverters = getterConverters;
}
String applicable(Method[] getters) {
for (int i = 0; i < getters.length; i++) {
try {
getterConverters[i].checkReconstructible();
} catch (InvalidObjectException e) {
return "method " + getters[i].getName() + " returns type " +
"that cannot be mapped back from OpenData";
}
}
return "";
}
final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters) {
throw new Error();
}
private final OpenConverter[] getterConverters;
}
Builder for when the target class has a setter for every getter. /** Builder for when the target class has a setter for every getter. */
private static class CompositeBuilderViaSetters extends CompositeBuilder {
CompositeBuilderViaSetters(Class targetClass, String[] itemNames) {
super(targetClass, itemNames);
}
String applicable(Method[] getters) {
try {
Constructor<?> c = getTargetClass().getConstructor((Class[]) null);
} catch (Exception e) {
return "does not have a public no-arg constructor";
}
Method[] setters = new Method[getters.length];
for (int i = 0; i < getters.length; i++) {
Method getter = getters[i];
Class returnType = getter.getReturnType();
String name = propertyName(getter);
String setterName = "set" + name;
Method setter;
try {
setter = getTargetClass().getMethod(setterName, returnType);
if (setter.getReturnType() != void.class)
throw new Exception();
} catch (Exception e) {
return "not all getters have corresponding setters " +
"(" + getter + ")";
}
setters[i] = setter;
}
this.setters = setters;
return null;
}
Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters)
throws InvalidObjectException {
Object o;
try {
o = getTargetClass().newInstance();
for (int i = 0; i < itemNames.length; i++) {
if (cd.containsKey(itemNames[i])) {
Object openItem = cd.get(itemNames[i]);
Object javaItem =
converters[i].fromOpenValue(lookup, openItem);
setters[i].invoke(o, javaItem);
}
}
} catch (Exception e) {
throw invalidObjectException(e);
}
return o;
}
private Method[] setters;
}
Builder for when the target class has a constructor that is
annotated with @ConstructorProperties so we can see the correspondence
to getters. /** Builder for when the target class has a constructor that is
annotated with @ConstructorProperties so we can see the correspondence
to getters. */
private static final class CompositeBuilderViaConstructor
extends CompositeBuilder {
CompositeBuilderViaConstructor(Class targetClass, String[] itemNames) {
super(targetClass, itemNames);
}
String applicable(Method[] getters) throws InvalidObjectException {
final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
Class targetClass = getTargetClass();
Constructor<?>[] constrs = targetClass.getConstructors();
// Applicable if and only if there are any annotated constructors
List<Constructor<?>> annotatedConstrList = newList();
for (Constructor<?> constr : constrs) {
if (Modifier.isPublic(constr.getModifiers())
&& constr.getAnnotation(propertyNamesClass) != null)
annotatedConstrList.add(constr);
}
if (annotatedConstrList.isEmpty())
return "no constructor has @ConstructorProperties annotation";
annotatedConstructors = newList();
// Now check that all the annotated constructors are valid
// and throw an exception if not.
// First link the itemNames to their getter indexes.
Map<String, Integer> getterMap = newMap();
String[] itemNames = getItemNames();
for (int i = 0; i < itemNames.length; i++)
getterMap.put(itemNames[i], i);
// Run through the constructors making the checks in the spec.
// For each constructor, remember the correspondence between its
// parameters and the items. The int[] for a constructor says
// what parameter index should get what item. For example,
// if element 0 is 2 then that means that item 0 in the
// CompositeData goes to parameter 2 of the constructor. If an
// element is -1, that item isn't given to the constructor.
// Also remember the set of properties in that constructor
// so we can test unambiguity.
Set<BitSet> getterIndexSets = newSet();
for (Constructor<?> constr : annotatedConstrList) {
String[] propertyNames =
constr.getAnnotation(propertyNamesClass).value();
Type[] paramTypes = constr.getGenericParameterTypes();
if (paramTypes.length != propertyNames.length) {
final String msg =
"Number of constructor params does not match " +
"@ConstructorProperties annotation: " + constr;
throw new InvalidObjectException(msg);
}
int[] paramIndexes = new int[getters.length];
for (int i = 0; i < getters.length; i++)
paramIndexes[i] = -1;
BitSet present = new BitSet();
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
if (!getterMap.containsKey(propertyName)) {
final String msg =
"@ConstructorProperties includes name " + propertyName +
" which does not correspond to a property: " +
constr;
throw new InvalidObjectException(msg);
}
int getterIndex = getterMap.get(propertyName);
paramIndexes[getterIndex] = i;
if (present.get(getterIndex)) {
final String msg =
"@ConstructorProperties contains property " +
propertyName + " more than once: " + constr;
throw new InvalidObjectException(msg);
}
present.set(getterIndex);
Method getter = getters[getterIndex];
Type propertyType = getter.getGenericReturnType();
if (!propertyType.equals(paramTypes[i])) {
final String msg =
"@ConstructorProperties gives property " + propertyName +
" of type " + propertyType + " for parameter " +
" of type " + paramTypes[i] + ": " + constr;
throw new InvalidObjectException(msg);
}
}
if (!getterIndexSets.add(present)) {
final String msg =
"More than one constructor has a @ConstructorProperties " +
"annotation with this set of names: " +
Arrays.toString(propertyNames);
throw new InvalidObjectException(msg);
}
Constr c = new Constr(constr, paramIndexes, present);
annotatedConstructors.add(c);
}
/* Check that no possible set of items could lead to an ambiguous
* choice of constructor (spec requires this check). For any
* pair of constructors, their union would be the minimal
* ambiguous set. If this set itself corresponds to a constructor,
* there is no ambiguity for that pair. In the usual case, one
* of the constructors is a superset of the other so the union is
* just the bigger constuctor.
*
* The algorithm here is quadratic in the number of constructors
* with a @ConstructorProperties annotation. Typically this corresponds
* to the number of versions of the class there have been. Ten
* would already be a large number, so although it's probably
* possible to have an O(n lg n) algorithm it wouldn't be
* worth the complexity.
*/
for (BitSet a : getterIndexSets) {
boolean seen = false;
for (BitSet b : getterIndexSets) {
if (a == b)
seen = true;
else if (seen) {
BitSet u = new BitSet();
u.or(a); u.or(b);
if (!getterIndexSets.contains(u)) {
Set<String> names = new TreeSet<String>();
for (int i = u.nextSetBit(0); i >= 0;
i = u.nextSetBit(i+1))
names.add(itemNames[i]);
final String msg =
"Constructors with @ConstructorProperties annotation " +
" would be ambiguous for these items: " +
names;
throw new InvalidObjectException(msg);
}
}
}
}
return null; // success!
}
Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters)
throws InvalidObjectException {
// The CompositeData might come from an earlier version where
// not all the items were present. We look for a constructor
// that accepts just the items that are present. Because of
// the ambiguity check in applicable(), we know there must be
// at most one maximally applicable constructor.
CompositeType ct = cd.getCompositeType();
BitSet present = new BitSet();
for (int i = 0; i < itemNames.length; i++) {
if (ct.getType(itemNames[i]) != null)
present.set(i);
}
Constr max = null;
for (Constr constr : annotatedConstructors) {
if (subset(constr.presentParams, present) &&
(max == null ||
subset(max.presentParams, constr.presentParams)))
max = constr;
}
if (max == null) {
final String msg =
"No constructor has a @ConstructorProperties for this set of " +
"items: " + ct.keySet();
throw new InvalidObjectException(msg);
}
Object[] params = new Object[max.presentParams.cardinality()];
for (int i = 0; i < itemNames.length; i++) {
if (!max.presentParams.get(i))
continue;
Object openItem = cd.get(itemNames[i]);
Object javaItem = converters[i].fromOpenValue(lookup, openItem);
int index = max.paramIndexes[i];
if (index >= 0)
params[index] = javaItem;
}
try {
return max.constructor.newInstance(params);
} catch (Exception e) {
final String msg =
"Exception constructing " + getTargetClass().getName();
throw invalidObjectException(msg, e);
}
}
private static boolean subset(BitSet sub, BitSet sup) {
BitSet subcopy = (BitSet) sub.clone();
subcopy.andNot(sup);
return subcopy.isEmpty();
}
private static class Constr {
final Constructor<?> constructor;
final int[] paramIndexes;
final BitSet presentParams;
Constr(Constructor<?> constructor, int[] paramIndexes,
BitSet presentParams) {
this.constructor = constructor;
this.paramIndexes = paramIndexes;
this.presentParams = presentParams;
}
}
private List<Constr> annotatedConstructors;
}
Builder for when the target class is an interface and contains
no methods other than getters. Then we can make an instance
using a dynamic proxy that forwards the getters to the source
CompositeData. /** Builder for when the target class is an interface and contains
no methods other than getters. Then we can make an instance
using a dynamic proxy that forwards the getters to the source
CompositeData. */
private static final class CompositeBuilderViaProxy
extends CompositeBuilder {
CompositeBuilderViaProxy(Class targetClass, String[] itemNames) {
super(targetClass, itemNames);
}
String applicable(Method[] getters) {
Class targetClass = getTargetClass();
if (!targetClass.isInterface())
return "not an interface";
Set<Method> methods =
newSet(Arrays.asList(targetClass.getMethods()));
methods.removeAll(Arrays.asList(getters));
/* If the interface has any methods left over, they better be
* public methods that are already present in java.lang.Object.
*/
String bad = null;
for (Method m : methods) {
String mname = m.getName();
Class[] mparams = m.getParameterTypes();
try {
Method om = Object.class.getMethod(mname, mparams);
if (!Modifier.isPublic(om.getModifiers()))
bad = mname;
} catch (NoSuchMethodException e) {
bad = mname;
}
/* We don't catch SecurityException since it shouldn't
* happen for a method in Object and if it does we would
* like to know about it rather than mysteriously complaining.
*/
}
if (bad != null)
return "contains methods other than getters (" + bad + ")";
return null; // success!
}
final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
String[] itemNames,
OpenConverter[] converters) {
final Class targetClass = getTargetClass();
return
Proxy.newProxyInstance(targetClass.getClassLoader(),
new Class[] {targetClass},
new CompositeDataInvocationHandler(cd));
}
}
static InvalidObjectException invalidObjectException(String msg,
Throwable cause) {
return EnvHelp.initCause(new InvalidObjectException(msg), cause);
}
static InvalidObjectException invalidObjectException(Throwable cause) {
return invalidObjectException(cause.getMessage(), cause);
}
static OpenDataException openDataException(String msg, Throwable cause) {
return EnvHelp.initCause(new OpenDataException(msg), cause);
}
static OpenDataException openDataException(Throwable cause) {
return openDataException(cause.getMessage(), cause);
}
static void mustBeComparable(Class collection, Type element)
throws OpenDataException {
if (!(element instanceof Class)
|| !Comparable.class.isAssignableFrom((Class<?>) element)) {
final String msg =
"Parameter class " + element + " of " +
collection.getName() + " does not implement " +
Comparable.class.getName();
throw new OpenDataException(msg);
}
}
Utility method to take a string and convert it to normal Java variable
name capitalization. This normally means converting the first
character from upper case to lower case, but in the (unusual) special
case when there is more than one character and both the first and
second characters are upper case, we leave it alone.
Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
as "URL".
Params: - name – The string to be decapitalized.
Returns: The decapitalized version of the string.
/**
* Utility method to take a string and convert it to normal Java variable
* name capitalization. This normally means converting the first
* character from upper case to lower case, but in the (unusual) special
* case when there is more than one character and both the first and
* second characters are upper case, we leave it alone.
* <p>
* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
* as "URL".
*
* @param name The string to be decapitalized.
* @return The decapitalized version of the string.
*/
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
int offset1 = Character.offsetByCodePoints(name, 0, 1);
// Should be name.offsetByCodePoints but 6242664 makes this fail
if (offset1 < name.length() &&
Character.isUpperCase(name.codePointAt(offset1)))
return name;
return name.substring(0, offset1).toLowerCase() +
name.substring(offset1);
}
Reverse operation for java.beans.Introspector.decapitalize. For any s,
capitalize(decapitalize(s)).equals(s). The reverse is not true:
e.g. capitalize("uRL") produces "URL" which is unchanged by
decapitalize.
/**
* Reverse operation for java.beans.Introspector.decapitalize. For any s,
* capitalize(decapitalize(s)).equals(s). The reverse is not true:
* e.g. capitalize("uRL") produces "URL" which is unchanged by
* decapitalize.
*/
static String capitalize(String name) {
if (name == null || name.length() == 0)
return name;
int offset1 = name.offsetByCodePoints(0, 1);
return name.substring(0, offset1).toUpperCase() +
name.substring(offset1);
}
public static String propertyName(Method m) {
String rest = null;
String name = m.getName();
if (name.startsWith("get"))
rest = name.substring(3);
else if (name.startsWith("is") && m.getReturnType() == boolean.class)
rest = name.substring(2);
if (rest == null || rest.length() == 0
|| m.getParameterTypes().length > 0
|| m.getReturnType() == void.class
|| name.equals("getClass"))
return null;
return rest;
}
private final static Map<Type, Type> inProgress = newIdentityHashMap();
// really an IdentityHashSet but that doesn't exist
}