--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java Wed Jul 02 12:55:27 2008 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1465 +0,0 @@
-/*
- * Copyright 2005-2006 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 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;
-
-/**
- <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;
- }
-
- /** <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;
-
- /** <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
- }
-
- /** <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;
-
- /** <p>True if and only if this OpenConverter's toOpenValue and fromOpenValue
- methods are the identity function.</p> */
- boolean isIdentity() {
- return false;
- }
-
- /** <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. */
- 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. */
- 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(Arrays.asList(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. */
- 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. */
- 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. */
- 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. */
- 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)". */
- 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. */
- 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. */
- 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. */
- 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. */
- 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.
- * <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.
- */
- 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
-}