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