jdk/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java
changeset 736 98441e673be6
parent 735 372aa565a221
parent 725 3d6b58de3f1c
child 737 bee4731164a0
equal deleted inserted replaced
735:372aa565a221 736:98441e673be6
     1 /*
       
     2  * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package com.sun.jmx.mbeanserver;
       
    27 
       
    28 import static com.sun.jmx.mbeanserver.Util.*;
       
    29 
       
    30 import static javax.management.openmbean.SimpleType.*;
       
    31 
       
    32 import com.sun.jmx.remote.util.EnvHelp;
       
    33 
       
    34 import java.beans.ConstructorProperties;
       
    35 import java.io.InvalidObjectException;
       
    36 import java.lang.annotation.ElementType;
       
    37 import java.lang.ref.WeakReference;
       
    38 import java.lang.reflect.Array;
       
    39 import java.lang.reflect.Constructor;
       
    40 import java.lang.reflect.Field;
       
    41 import java.lang.reflect.GenericArrayType;
       
    42 import java.lang.reflect.Method;
       
    43 import java.lang.reflect.Modifier;
       
    44 import java.lang.reflect.ParameterizedType;
       
    45 import java.lang.reflect.Proxy;
       
    46 import java.lang.reflect.Type;
       
    47 import java.util.ArrayList;
       
    48 import java.util.Arrays;
       
    49 import java.util.BitSet;
       
    50 import java.util.Collection;
       
    51 import java.util.Comparator;
       
    52 import java.util.HashSet;
       
    53 import java.util.List;
       
    54 import java.util.Map;
       
    55 import java.util.Set;
       
    56 import java.util.SortedMap;
       
    57 import java.util.SortedSet;
       
    58 import java.util.TreeSet;
       
    59 import java.util.WeakHashMap;
       
    60 
       
    61 import javax.management.JMX;
       
    62 import javax.management.ObjectName;
       
    63 import javax.management.openmbean.ArrayType;
       
    64 import javax.management.openmbean.CompositeData;
       
    65 import javax.management.openmbean.CompositeDataInvocationHandler;
       
    66 import javax.management.openmbean.CompositeDataSupport;
       
    67 import javax.management.openmbean.CompositeDataView;
       
    68 import javax.management.openmbean.CompositeType;
       
    69 import javax.management.openmbean.OpenDataException;
       
    70 import javax.management.openmbean.OpenType;
       
    71 import javax.management.openmbean.SimpleType;
       
    72 import javax.management.openmbean.TabularData;
       
    73 import javax.management.openmbean.TabularDataSupport;
       
    74 import javax.management.openmbean.TabularType;
       
    75 
       
    76 /**
       
    77    <p>A converter between Java types and the limited set of classes
       
    78    defined by Open MBeans.</p>
       
    79 
       
    80    <p>A Java type is an instance of java.lang.reflect.Type.  For our
       
    81    purposes, it is either a Class, such as String.class or int.class;
       
    82    or a ParameterizedType, such as List<String> or Map<Integer,
       
    83    String[]>.  On J2SE 1.4 and earlier, it can only be a Class.</p>
       
    84 
       
    85    <p>Each Type is associated with an OpenConverter.  The
       
    86    OpenConverter defines an OpenType corresponding to the Type, plus a
       
    87    Java class corresponding to the OpenType.  For example:</p>
       
    88 
       
    89    <pre>
       
    90    Type                     Open class     OpenType
       
    91    ----                     ----------     --------
       
    92    Integer                  Integer        SimpleType.INTEGER
       
    93    int                      int            SimpleType.INTEGER
       
    94    Integer[]                Integer[]      ArrayType(1, SimpleType.INTEGER)
       
    95    int[]                    Integer[]      ArrayType(SimpleType.INTEGER, true)
       
    96    String[][]               String[][]     ArrayType(2, SimpleType.STRING)
       
    97    List<String>             String[]       ArrayType(1, SimpleType.STRING)
       
    98    ThreadState (an Enum)    String         SimpleType.STRING
       
    99    Map<Integer, String[]>   TabularData    TabularType(
       
   100                                              CompositeType(
       
   101                                                {"key", SimpleType.INTEGER},
       
   102                                                {"value",
       
   103                                                  ArrayType(1,
       
   104                                                   SimpleType.STRING)}),
       
   105                                              indexNames={"key"})
       
   106    </pre>
       
   107 
       
   108    <p>Apart from simple types, arrays, and collections, Java types are
       
   109    converted through introspection into CompositeType.  The Java type
       
   110    must have at least one getter (method such as "int getSize()" or
       
   111    "boolean isBig()"), and we must be able to deduce how to
       
   112    reconstruct an instance of the Java class from the values of the
       
   113    getters using one of various heuristics.</p>
       
   114 
       
   115    @since 1.6
       
   116  */
       
   117 public abstract class OpenConverter {
       
   118     private OpenConverter(Type targetType, OpenType openType,
       
   119                           Class openClass) {
       
   120         this.targetType = targetType;
       
   121         this.openType = openType;
       
   122         this.openClass = openClass;
       
   123     }
       
   124 
       
   125     /** <p>Convert an instance of openClass into an instance of targetType. */
       
   126     public final Object fromOpenValue(MXBeanLookup lookup, Object value)
       
   127             throws InvalidObjectException {
       
   128         if (value == null)
       
   129             return null;
       
   130         else
       
   131             return fromNonNullOpenValue(lookup, value);
       
   132     }
       
   133 
       
   134     abstract Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   135             throws InvalidObjectException;
       
   136 
       
   137     /** <p>Throw an appropriate InvalidObjectException if we will not be able
       
   138         to convert back from the open data to the original Java object.</p> */
       
   139     void checkReconstructible() throws InvalidObjectException {
       
   140         // subclasses override if action necessary
       
   141     }
       
   142 
       
   143     /** <p>Convert an instance of targetType into an instance of openClass. */
       
   144     final Object toOpenValue(MXBeanLookup lookup, Object value)
       
   145             throws OpenDataException {
       
   146         if (value == null)
       
   147             return null;
       
   148         else
       
   149             return toNonNullOpenValue(lookup, value);
       
   150     }
       
   151 
       
   152     abstract Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   153             throws OpenDataException;
       
   154 
       
   155     /** <p>True if and only if this OpenConverter's toOpenValue and fromOpenValue
       
   156         methods are the identity function.</p> */
       
   157     boolean isIdentity() {
       
   158         return false;
       
   159     }
       
   160 
       
   161     /** <p>True if and only if isIdentity() and even an array of the underlying type
       
   162        is transformed as the identity.  This is true for Integer and
       
   163        ObjectName, for instance, but not for int.</p> */
       
   164     final Type getTargetType() {
       
   165         return targetType;
       
   166     }
       
   167 
       
   168     final OpenType getOpenType() {
       
   169         return openType;
       
   170     }
       
   171 
       
   172     /* The Java class corresponding to getOpenType().  This is the class
       
   173        named by getOpenType().getClassName(), except that it may be a
       
   174        primitive type or an array of primitive type.  */
       
   175     final Class getOpenClass() {
       
   176         return openClass;
       
   177     }
       
   178 
       
   179     private final Type targetType;
       
   180     private final OpenType openType;
       
   181     private final Class openClass;
       
   182 
       
   183     private static final class ConverterMap
       
   184         extends WeakHashMap<Type, WeakReference<OpenConverter>> {}
       
   185 
       
   186     private static final ConverterMap converterMap = new ConverterMap();
       
   187 
       
   188     /** Following List simply serves to keep a reference to predefined
       
   189         OpenConverters so they don't get garbage collected. */
       
   190     private static final List<OpenConverter> permanentConverters = newList();
       
   191 
       
   192     private static synchronized OpenConverter getConverter(Type type) {
       
   193         WeakReference<OpenConverter> wr = converterMap.get(type);
       
   194         return (wr == null) ? null : wr.get();
       
   195     }
       
   196 
       
   197     private static synchronized void putConverter(Type type,
       
   198                                                   OpenConverter conv) {
       
   199         WeakReference<OpenConverter> wr =
       
   200             new WeakReference<OpenConverter>(conv);
       
   201         converterMap.put(type, wr);
       
   202     }
       
   203 
       
   204     private static synchronized void putPermanentConverter(Type type,
       
   205                                                            OpenConverter conv) {
       
   206         putConverter(type, conv);
       
   207         permanentConverters.add(conv);
       
   208     }
       
   209 
       
   210     static {
       
   211         /* Set up the mappings for Java types that map to SimpleType.  */
       
   212 
       
   213         final OpenType[] simpleTypes = {
       
   214             BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
       
   215             DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
       
   216             VOID,
       
   217         };
       
   218 
       
   219         for (int i = 0; i < simpleTypes.length; i++) {
       
   220             final OpenType t = simpleTypes[i];
       
   221             Class c;
       
   222             try {
       
   223                 c = Class.forName(t.getClassName(), false,
       
   224                                   ObjectName.class.getClassLoader());
       
   225             } catch (ClassNotFoundException e) {
       
   226                 // the classes that these predefined types declare must exist!
       
   227                 throw new Error(e);
       
   228             }
       
   229             final OpenConverter conv = new IdentityConverter(c, t, c);
       
   230             putPermanentConverter(c, conv);
       
   231 
       
   232             if (c.getName().startsWith("java.lang.")) {
       
   233                 try {
       
   234                     final Field typeField = c.getField("TYPE");
       
   235                     final Class primitiveType = (Class) typeField.get(null);
       
   236                     final OpenConverter primitiveConv =
       
   237                         new IdentityConverter(primitiveType, t, primitiveType);
       
   238                     putPermanentConverter(primitiveType,
       
   239                                           primitiveConv);
       
   240                     if (primitiveType != void.class) {
       
   241                         final Class<?> primitiveArrayType =
       
   242                             Array.newInstance(primitiveType, 0).getClass();
       
   243                         final OpenType primitiveArrayOpenType =
       
   244                             ArrayType.getPrimitiveArrayType(primitiveArrayType);
       
   245                         final OpenConverter primitiveArrayConv =
       
   246                             new IdentityConverter(primitiveArrayType,
       
   247                                                   primitiveArrayOpenType,
       
   248                                                   primitiveArrayType);
       
   249                         putPermanentConverter(primitiveArrayType,
       
   250                                               primitiveArrayConv);
       
   251                     }
       
   252                 } catch (NoSuchFieldException e) {
       
   253                     // OK: must not be a primitive wrapper
       
   254                 } catch (IllegalAccessException e) {
       
   255                     // Should not reach here
       
   256                     assert(false);
       
   257                 }
       
   258             }
       
   259         }
       
   260     }
       
   261 
       
   262     /** Get the converter for the given Java type, creating it if necessary. */
       
   263     public static synchronized OpenConverter toConverter(Type objType)
       
   264             throws OpenDataException {
       
   265 
       
   266         if (inProgress.containsKey(objType))
       
   267             throw new OpenDataException("Recursive data structure");
       
   268 
       
   269         OpenConverter conv;
       
   270 
       
   271         conv = getConverter(objType);
       
   272         if (conv != null)
       
   273             return conv;
       
   274 
       
   275         inProgress.put(objType, objType);
       
   276         try {
       
   277             conv = makeConverter(objType);
       
   278         } finally {
       
   279             inProgress.remove(objType);
       
   280         }
       
   281 
       
   282         putConverter(objType, conv);
       
   283         return conv;
       
   284     }
       
   285 
       
   286     private static OpenConverter makeConverter(Type objType)
       
   287             throws OpenDataException {
       
   288 
       
   289         /* It's not yet worth formalizing these tests by having for example
       
   290            an array of factory classes, each of which says whether it
       
   291            recognizes the Type (Chain of Responsibility pattern).  */
       
   292         if (objType instanceof GenericArrayType) {
       
   293             Type componentType =
       
   294                 ((GenericArrayType) objType).getGenericComponentType();
       
   295             return makeArrayOrCollectionConverter(objType, componentType);
       
   296         } else if (objType instanceof Class) {
       
   297             Class<?> objClass = (Class<?>) objType;
       
   298             if (objClass.isEnum()) {
       
   299                 // Huge hack to avoid compiler warnings here.  The ElementType
       
   300                 // parameter is ignored but allows us to obtain a type variable
       
   301                 // T that matches <T extends Enum<T>>.
       
   302                 return makeEnumConverter(objClass, ElementType.class);
       
   303             } else if (objClass.isArray()) {
       
   304                 Type componentType = objClass.getComponentType();
       
   305                 return makeArrayOrCollectionConverter(objClass, componentType);
       
   306             } else if (JMX.isMXBeanInterface(objClass)) {
       
   307                 return makeMXBeanConverter(objClass);
       
   308             } else {
       
   309                 return makeCompositeConverter(objClass);
       
   310             }
       
   311         } else if (objType instanceof ParameterizedType) {
       
   312             return makeParameterizedConverter((ParameterizedType) objType);
       
   313         } else
       
   314             throw new OpenDataException("Cannot map type: " + objType);
       
   315     }
       
   316 
       
   317     private static <T extends Enum<T>> OpenConverter
       
   318             makeEnumConverter(Class<?> enumClass, Class<T> fake) {
       
   319         Class<T> enumClassT = Util.cast(enumClass);
       
   320         return new EnumConverter<T>(enumClassT);
       
   321     }
       
   322 
       
   323     /* Make the converter for an array type, or a collection such as
       
   324      * List<String> or Set<Integer>.  We never see one-dimensional
       
   325      * primitive arrays (e.g. int[]) here because they use the identity
       
   326      * converter and are registered as such in the static initializer.
       
   327      */
       
   328     private static OpenConverter
       
   329         makeArrayOrCollectionConverter(Type collectionType, Type elementType)
       
   330             throws OpenDataException {
       
   331 
       
   332         final OpenConverter elementConverter = toConverter(elementType);
       
   333         final OpenType<?> elementOpenType = elementConverter.getOpenType();
       
   334         final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
       
   335         final Class<?> elementOpenClass = elementConverter.getOpenClass();
       
   336 
       
   337         final Class<?> openArrayClass;
       
   338         final String openArrayClassName;
       
   339         if (elementOpenClass.isArray())
       
   340             openArrayClassName = "[" + elementOpenClass.getName();
       
   341         else
       
   342             openArrayClassName = "[L" + elementOpenClass.getName() + ";";
       
   343         try {
       
   344             openArrayClass = Class.forName(openArrayClassName);
       
   345         } catch (ClassNotFoundException e) {
       
   346             throw openDataException("Cannot obtain array class", e);
       
   347         }
       
   348 
       
   349         if (collectionType instanceof ParameterizedType) {
       
   350             return new CollectionConverter(collectionType,
       
   351                                            openType, openArrayClass,
       
   352                                            elementConverter);
       
   353         } else {
       
   354             if (elementConverter.isIdentity()) {
       
   355                 return new IdentityConverter(collectionType,
       
   356                                              openType,
       
   357                                              openArrayClass);
       
   358             } else {
       
   359                 return new ArrayConverter(collectionType,
       
   360                                           openType,
       
   361                                           openArrayClass,
       
   362                                           elementConverter);
       
   363             }
       
   364         }
       
   365     }
       
   366 
       
   367     private static final String[] keyArray = {"key"};
       
   368     private static final String[] keyValueArray = {"key", "value"};
       
   369 
       
   370     private static OpenConverter
       
   371         makeTabularConverter(Type objType, boolean sortedMap,
       
   372                              Type keyType, Type valueType)
       
   373             throws OpenDataException {
       
   374 
       
   375         final String objTypeName = objType.toString();
       
   376         final OpenConverter keyConverter = toConverter(keyType);
       
   377         final OpenConverter valueConverter = toConverter(valueType);
       
   378         final OpenType keyOpenType = keyConverter.getOpenType();
       
   379         final OpenType valueOpenType = valueConverter.getOpenType();
       
   380         final CompositeType rowType =
       
   381             new CompositeType(objTypeName,
       
   382                               objTypeName,
       
   383                               keyValueArray,
       
   384                               keyValueArray,
       
   385                               new OpenType[] {keyOpenType, valueOpenType});
       
   386         final TabularType tabularType =
       
   387             new TabularType(objTypeName, objTypeName, rowType, keyArray);
       
   388         return new TabularConverter(objType, sortedMap, tabularType,
       
   389                                     keyConverter, valueConverter);
       
   390     }
       
   391 
       
   392     /* We know how to translate List<E>, Set<E>, SortedSet<E>,
       
   393        Map<K,V>, SortedMap<K,V>, and that's it.  We don't accept
       
   394        subtypes of those because we wouldn't know how to deserialize
       
   395        them.  We don't accept Queue<E> because it is unlikely people
       
   396        would use that as a parameter or return type in an MBean.  */
       
   397     private static OpenConverter
       
   398         makeParameterizedConverter(ParameterizedType objType) throws OpenDataException {
       
   399 
       
   400         final Type rawType = objType.getRawType();
       
   401 
       
   402         if (rawType instanceof Class) {
       
   403             Class c = (Class<?>) rawType;
       
   404             if (c == List.class || c == Set.class || c == SortedSet.class) {
       
   405                 Type[] actuals = objType.getActualTypeArguments();
       
   406                 assert(actuals.length == 1);
       
   407                 if (c == SortedSet.class)
       
   408                     mustBeComparable(c, actuals[0]);
       
   409                 return makeArrayOrCollectionConverter(objType, actuals[0]);
       
   410             } else {
       
   411                 boolean sortedMap = (c == SortedMap.class);
       
   412                 if (c == Map.class || sortedMap) {
       
   413                     Type[] actuals = objType.getActualTypeArguments();
       
   414                     assert(actuals.length == 2);
       
   415                     if (sortedMap)
       
   416                         mustBeComparable(c, actuals[0]);
       
   417                     return makeTabularConverter(objType, sortedMap,
       
   418                             actuals[0], actuals[1]);
       
   419                 }
       
   420             }
       
   421         }
       
   422         throw new OpenDataException("Cannot convert type: " + objType);
       
   423     }
       
   424 
       
   425     private static OpenConverter makeMXBeanConverter(Type t)
       
   426             throws OpenDataException {
       
   427         return new MXBeanConverter(t);
       
   428     }
       
   429 
       
   430     private static OpenConverter makeCompositeConverter(Class c)
       
   431             throws OpenDataException {
       
   432 
       
   433         // For historical reasons GcInfo implements CompositeData but we
       
   434         // shouldn't count its CompositeData.getCompositeType() field as
       
   435         // an item in the computed CompositeType.
       
   436         final boolean gcInfoHack =
       
   437             (c.getName().equals("com.sun.management.GcInfo") &&
       
   438                 c.getClassLoader() == null);
       
   439 
       
   440         final List<Method> methods =
       
   441                 MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
       
   442         final SortedMap<String,Method> getterMap = newSortedMap();
       
   443 
       
   444         /* Select public methods that look like "T getX()" or "boolean
       
   445            isX()", where T is not void and X is not the empty
       
   446            string.  Exclude "Class getClass()" inherited from Object.  */
       
   447         for (Method method : methods) {
       
   448             final String propertyName = propertyName(method);
       
   449 
       
   450             if (propertyName == null)
       
   451                 continue;
       
   452             if (gcInfoHack && propertyName.equals("CompositeType"))
       
   453                 continue;
       
   454 
       
   455             Method old =
       
   456                 getterMap.put(decapitalize(propertyName),
       
   457                             method);
       
   458             if (old != null) {
       
   459                 final String msg =
       
   460                     "Class " + c.getName() + " has method name clash: " +
       
   461                     old.getName() + ", " + method.getName();
       
   462                 throw new OpenDataException(msg);
       
   463             }
       
   464         }
       
   465 
       
   466         final int nitems = getterMap.size();
       
   467 
       
   468         if (nitems == 0) {
       
   469             throw new OpenDataException("Can't map " + c.getName() +
       
   470                                         " to an open data type");
       
   471         }
       
   472 
       
   473         final Method[] getters = new Method[nitems];
       
   474         final String[] itemNames = new String[nitems];
       
   475         final OpenType[] openTypes = new OpenType[nitems];
       
   476         int i = 0;
       
   477         for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
       
   478             itemNames[i] = entry.getKey();
       
   479             final Method getter = entry.getValue();
       
   480             getters[i] = getter;
       
   481             final Type retType = getter.getGenericReturnType();
       
   482             openTypes[i] = toConverter(retType).getOpenType();
       
   483             i++;
       
   484         }
       
   485 
       
   486         CompositeType compositeType =
       
   487             new CompositeType(c.getName(),
       
   488                               c.getName(),
       
   489                               itemNames, // field names
       
   490                               itemNames, // field descriptions
       
   491                               openTypes);
       
   492 
       
   493         return new CompositeConverter(c,
       
   494                                       compositeType,
       
   495                                       itemNames,
       
   496                                       getters);
       
   497     }
       
   498 
       
   499     /* Converter for classes where the open data is identical to the
       
   500        original data.  This is true for any of the SimpleType types,
       
   501        and for an any-dimension array of those.  It is also true for
       
   502        primitive types as of JMX 1.3, since an int[] needs to
       
   503        can be directly represented by an ArrayType, and an int needs no mapping
       
   504        because reflection takes care of it.  */
       
   505     private static final class IdentityConverter extends OpenConverter {
       
   506         IdentityConverter(Type targetType, OpenType openType,
       
   507                           Class openClass) {
       
   508             super(targetType, openType, openClass);
       
   509         }
       
   510 
       
   511         boolean isIdentity() {
       
   512             return true;
       
   513         }
       
   514 
       
   515         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
       
   516             return value;
       
   517         }
       
   518 
       
   519         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) {
       
   520             return value;
       
   521         }
       
   522     }
       
   523 
       
   524     private static final class EnumConverter<T extends Enum<T>>
       
   525             extends OpenConverter {
       
   526 
       
   527         EnumConverter(Class<T> enumClass) {
       
   528             super(enumClass, SimpleType.STRING, String.class);
       
   529             this.enumClass = enumClass;
       
   530         }
       
   531 
       
   532         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
       
   533             return ((Enum) value).name();
       
   534         }
       
   535 
       
   536         // return type could be T, but after erasure that would be
       
   537         // java.lang.Enum, which doesn't exist on J2SE 1.4
       
   538         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   539                 throws InvalidObjectException {
       
   540             try {
       
   541                 return Enum.valueOf(enumClass, (String) value);
       
   542             } catch (Exception e) {
       
   543                 throw invalidObjectException("Cannot convert to enum: " +
       
   544                                              value, e);
       
   545             }
       
   546         }
       
   547 
       
   548         private final Class<T> enumClass;
       
   549     }
       
   550 
       
   551     private static final class ArrayConverter extends OpenConverter {
       
   552         ArrayConverter(Type targetType,
       
   553                        ArrayType openArrayType, Class openArrayClass,
       
   554                        OpenConverter elementConverter) {
       
   555             super(targetType, openArrayType, openArrayClass);
       
   556             this.elementConverter = elementConverter;
       
   557         }
       
   558 
       
   559         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   560                 throws OpenDataException {
       
   561             Object[] valueArray = (Object[]) value;
       
   562             final int len = valueArray.length;
       
   563             final Object[] openArray = (Object[])
       
   564                 Array.newInstance(getOpenClass().getComponentType(), len);
       
   565             for (int i = 0; i < len; i++) {
       
   566                 openArray[i] =
       
   567                     elementConverter.toOpenValue(lookup, valueArray[i]);
       
   568             }
       
   569             return openArray;
       
   570         }
       
   571 
       
   572         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
       
   573                 throws InvalidObjectException {
       
   574             final Object[] openArray = (Object[]) openValue;
       
   575             final Type targetType = getTargetType();
       
   576             final Object[] valueArray;
       
   577             final Type componentType;
       
   578             if (targetType instanceof GenericArrayType) {
       
   579                 componentType =
       
   580                     ((GenericArrayType) targetType).getGenericComponentType();
       
   581             } else if (targetType instanceof Class &&
       
   582                        ((Class<?>) targetType).isArray()) {
       
   583                 componentType = ((Class<?>) targetType).getComponentType();
       
   584             } else {
       
   585                 throw new IllegalArgumentException("Not an array: " +
       
   586                                                    targetType);
       
   587             }
       
   588             valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
       
   589                                                       openArray.length);
       
   590             for (int i = 0; i < openArray.length; i++) {
       
   591                 valueArray[i] =
       
   592                     elementConverter.fromOpenValue(lookup, openArray[i]);
       
   593             }
       
   594             return valueArray;
       
   595         }
       
   596 
       
   597         void checkReconstructible() throws InvalidObjectException {
       
   598             elementConverter.checkReconstructible();
       
   599         }
       
   600 
       
   601         /** OpenConverter for the elements of this array.  If this is an
       
   602             array of arrays, the converter converts the second-level arrays,
       
   603             not the deepest elements.  */
       
   604         private final OpenConverter elementConverter;
       
   605     }
       
   606 
       
   607     private static final class CollectionConverter extends OpenConverter {
       
   608         CollectionConverter(Type targetType,
       
   609                             ArrayType openArrayType,
       
   610                             Class openArrayClass,
       
   611                             OpenConverter elementConverter) {
       
   612             super(targetType, openArrayType, openArrayClass);
       
   613             this.elementConverter = elementConverter;
       
   614 
       
   615             /* Determine the concrete class to be used when converting
       
   616                back to this Java type.  We convert all Lists to ArrayList
       
   617                and all Sets to TreeSet.  (TreeSet because it is a SortedSet,
       
   618                so works for both Set and SortedSet.)  */
       
   619             Type raw = ((ParameterizedType) targetType).getRawType();
       
   620             Class c = (Class<?>) raw;
       
   621             if (c == List.class)
       
   622                 collectionClass = ArrayList.class;
       
   623             else if (c == Set.class)
       
   624                 collectionClass = HashSet.class;
       
   625             else if (c == SortedSet.class)
       
   626                 collectionClass = TreeSet.class;
       
   627             else { // can't happen
       
   628                 assert(false);
       
   629                 collectionClass = null;
       
   630             }
       
   631         }
       
   632 
       
   633         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   634                 throws OpenDataException {
       
   635             final Collection valueCollection = (Collection) value;
       
   636             if (valueCollection instanceof SortedSet) {
       
   637                 Comparator comparator =
       
   638                     ((SortedSet) valueCollection).comparator();
       
   639                 if (comparator != null) {
       
   640                     final String msg =
       
   641                         "Cannot convert SortedSet with non-null comparator: " +
       
   642                         comparator;
       
   643                     throw new OpenDataException(msg);
       
   644                 }
       
   645             }
       
   646             final Object[] openArray = (Object[])
       
   647                 Array.newInstance(getOpenClass().getComponentType(),
       
   648                                   valueCollection.size());
       
   649             int i = 0;
       
   650             for (Object o : valueCollection)
       
   651                 openArray[i++] = elementConverter.toOpenValue(lookup, o);
       
   652             return openArray;
       
   653         }
       
   654 
       
   655         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
       
   656                 throws InvalidObjectException {
       
   657             final Object[] openArray = (Object[]) openValue;
       
   658             final Collection<Object> valueCollection;
       
   659             try {
       
   660                 valueCollection = Util.cast(collectionClass.newInstance());
       
   661             } catch (Exception e) {
       
   662                 throw invalidObjectException("Cannot create collection", e);
       
   663             }
       
   664             for (Object o : openArray) {
       
   665                 Object value = elementConverter.fromOpenValue(lookup, o);
       
   666                 if (!valueCollection.add(value)) {
       
   667                     final String msg =
       
   668                         "Could not add " + o + " to " +
       
   669                         collectionClass.getName() +
       
   670                         " (duplicate set element?)";
       
   671                     throw new InvalidObjectException(msg);
       
   672                 }
       
   673             }
       
   674             return valueCollection;
       
   675         }
       
   676 
       
   677         void checkReconstructible() throws InvalidObjectException {
       
   678             elementConverter.checkReconstructible();
       
   679         }
       
   680 
       
   681         private final Class<? extends Collection> collectionClass;
       
   682         private final OpenConverter elementConverter;
       
   683     }
       
   684 
       
   685     private static final class MXBeanConverter extends OpenConverter {
       
   686         MXBeanConverter(Type intf) {
       
   687             super(intf, SimpleType.OBJECTNAME, ObjectName.class);
       
   688         }
       
   689 
       
   690         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   691                 throws OpenDataException {
       
   692             lookupNotNull(lookup, OpenDataException.class);
       
   693             ObjectName name = lookup.mxbeanToObjectName(value);
       
   694             if (name == null)
       
   695                 throw new OpenDataException("No name for object: " + value);
       
   696             return name;
       
   697         }
       
   698 
       
   699         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   700                 throws InvalidObjectException {
       
   701             lookupNotNull(lookup, InvalidObjectException.class);
       
   702             ObjectName name = (ObjectName) value;
       
   703             Object mxbean =
       
   704                 lookup.objectNameToMXBean(name, (Class<?>) getTargetType());
       
   705             if (mxbean == null) {
       
   706                 final String msg =
       
   707                     "No MXBean for name: " + name;
       
   708                 throw new InvalidObjectException(msg);
       
   709             }
       
   710             return mxbean;
       
   711         }
       
   712 
       
   713         private <T extends Exception> void
       
   714             lookupNotNull(MXBeanLookup lookup, Class<T> excClass)
       
   715                 throws T {
       
   716             if (lookup == null) {
       
   717                 final String msg =
       
   718                     "Cannot convert MXBean interface in this context";
       
   719                 T exc;
       
   720                 try {
       
   721                     Constructor<T> con = excClass.getConstructor(String.class);
       
   722                     exc = con.newInstance(msg);
       
   723                 } catch (Exception e) {
       
   724                     throw new RuntimeException(e);
       
   725                 }
       
   726                 throw exc;
       
   727             }
       
   728         }
       
   729     }
       
   730 
       
   731     private static final class TabularConverter extends OpenConverter {
       
   732         TabularConverter(Type targetType,
       
   733                          boolean sortedMap,
       
   734                          TabularType tabularType,
       
   735                          OpenConverter keyConverter,
       
   736                          OpenConverter valueConverter) {
       
   737             super(targetType, tabularType, TabularData.class);
       
   738             this.sortedMap = sortedMap;
       
   739             this.keyConverter = keyConverter;
       
   740             this.valueConverter = valueConverter;
       
   741         }
       
   742 
       
   743         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   744                 throws OpenDataException {
       
   745             final Map<Object, Object> valueMap = Util.cast(value);
       
   746             if (valueMap instanceof SortedMap) {
       
   747                 Comparator comparator = ((SortedMap) valueMap).comparator();
       
   748                 if (comparator != null) {
       
   749                     final String msg =
       
   750                         "Cannot convert SortedMap with non-null comparator: " +
       
   751                         comparator;
       
   752                     throw new OpenDataException(msg);
       
   753                 }
       
   754             }
       
   755             final TabularType tabularType = (TabularType) getOpenType();
       
   756             final TabularData table = new TabularDataSupport(tabularType);
       
   757             final CompositeType rowType = tabularType.getRowType();
       
   758             for (Map.Entry entry : valueMap.entrySet()) {
       
   759                 final Object openKey =
       
   760                     keyConverter.toOpenValue(lookup, entry.getKey());
       
   761                 final Object openValue =
       
   762                     valueConverter.toOpenValue(lookup, entry.getValue());
       
   763                 final CompositeData row;
       
   764                 row =
       
   765                     new CompositeDataSupport(rowType, keyValueArray,
       
   766                                              new Object[] {openKey,
       
   767                                                            openValue});
       
   768                 table.put(row);
       
   769             }
       
   770             return table;
       
   771         }
       
   772 
       
   773         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
       
   774                 throws InvalidObjectException {
       
   775             final TabularData table = (TabularData) openValue;
       
   776             final Collection<CompositeData> rows = Util.cast(table.values());
       
   777             final Map<Object, Object> valueMap =
       
   778                 sortedMap ? newSortedMap() : newMap();
       
   779             for (CompositeData row : rows) {
       
   780                 final Object key =
       
   781                     keyConverter.fromOpenValue(lookup, row.get("key"));
       
   782                 final Object value =
       
   783                     valueConverter.fromOpenValue(lookup, row.get("value"));
       
   784                 if (valueMap.put(key, value) != null) {
       
   785                     final String msg =
       
   786                         "Duplicate entry in TabularData: key=" + key;
       
   787                     throw new InvalidObjectException(msg);
       
   788                 }
       
   789             }
       
   790             return valueMap;
       
   791         }
       
   792 
       
   793         void checkReconstructible() throws InvalidObjectException {
       
   794             keyConverter.checkReconstructible();
       
   795             valueConverter.checkReconstructible();
       
   796         }
       
   797 
       
   798         private final boolean sortedMap;
       
   799         private final OpenConverter keyConverter;
       
   800         private final OpenConverter valueConverter;
       
   801     }
       
   802 
       
   803     private static final class CompositeConverter extends OpenConverter {
       
   804         CompositeConverter(Class targetClass,
       
   805                            CompositeType compositeType,
       
   806                            String[] itemNames,
       
   807                            Method[] getters) throws OpenDataException {
       
   808             super(targetClass, compositeType, CompositeData.class);
       
   809 
       
   810             assert(itemNames.length == getters.length);
       
   811 
       
   812             this.itemNames = itemNames;
       
   813             this.getters = getters;
       
   814             this.getterConverters = new OpenConverter[getters.length];
       
   815             for (int i = 0; i < getters.length; i++) {
       
   816                 Type retType = getters[i].getGenericReturnType();
       
   817                 getterConverters[i] = OpenConverter.toConverter(retType);
       
   818             }
       
   819         }
       
   820 
       
   821         final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   822                 throws OpenDataException {
       
   823             CompositeType ct = (CompositeType) getOpenType();
       
   824             if (value instanceof CompositeDataView)
       
   825                 return ((CompositeDataView) value).toCompositeData(ct);
       
   826             if (value == null)
       
   827                 return null;
       
   828 
       
   829             Object[] values = new Object[getters.length];
       
   830             for (int i = 0; i < getters.length; i++) {
       
   831                 try {
       
   832                     Object got = getters[i].invoke(value, (Object[]) null);
       
   833                     values[i] = getterConverters[i].toOpenValue(lookup, got);
       
   834                 } catch (Exception e) {
       
   835                     throw openDataException("Error calling getter for " +
       
   836                                             itemNames[i] + ": " + e, e);
       
   837                 }
       
   838             }
       
   839             return new CompositeDataSupport(ct, itemNames, values);
       
   840         }
       
   841 
       
   842         /** Determine how to convert back from the CompositeData into
       
   843             the original Java type.  For a type that is not reconstructible,
       
   844             this method will fail every time, and will throw the right
       
   845             exception. */
       
   846         private synchronized void makeCompositeBuilder()
       
   847                 throws InvalidObjectException {
       
   848             if (compositeBuilder != null)
       
   849                 return;
       
   850 
       
   851             Class targetClass = (Class<?>) getTargetType();
       
   852             /* In this 2D array, each subarray is a set of builders where
       
   853                there is no point in consulting the ones after the first if
       
   854                the first refuses.  */
       
   855             CompositeBuilder[][] builders = {
       
   856                 {
       
   857                     new CompositeBuilderViaFrom(targetClass, itemNames),
       
   858                 },
       
   859                 {
       
   860                     new CompositeBuilderViaConstructor(targetClass, itemNames),
       
   861                 },
       
   862                 {
       
   863                     new CompositeBuilderCheckGetters(targetClass, itemNames,
       
   864                                                      getterConverters),
       
   865                     new CompositeBuilderViaSetters(targetClass, itemNames),
       
   866                     new CompositeBuilderViaProxy(targetClass, itemNames),
       
   867                 },
       
   868             };
       
   869             CompositeBuilder foundBuilder = null;
       
   870             /* We try to make a meaningful exception message by
       
   871                concatenating each Builder's explanation of why it
       
   872                isn't applicable.  */
       
   873             final StringBuilder whyNots = new StringBuilder();
       
   874         find:
       
   875             for (CompositeBuilder[] relatedBuilders : builders) {
       
   876                 for (int i = 0; i < relatedBuilders.length; i++) {
       
   877                     CompositeBuilder builder = relatedBuilders[i];
       
   878                     String whyNot = builder.applicable(getters);
       
   879                     if (whyNot == null) {
       
   880                         foundBuilder = builder;
       
   881                         break find;
       
   882                     }
       
   883                     if (whyNot.length() > 0) {
       
   884                         if (whyNots.length() > 0)
       
   885                             whyNots.append("; ");
       
   886                         whyNots.append(whyNot);
       
   887                         if (i == 0)
       
   888                            break; // skip other builders in this group
       
   889                     }
       
   890                 }
       
   891             }
       
   892             if (foundBuilder == null) {
       
   893                 final String msg =
       
   894                     "Do not know how to make a " + targetClass.getName() +
       
   895                     " from a CompositeData: " + whyNots;
       
   896                 throw new InvalidObjectException(msg);
       
   897             }
       
   898             compositeBuilder = foundBuilder;
       
   899         }
       
   900 
       
   901         void checkReconstructible() throws InvalidObjectException {
       
   902             makeCompositeBuilder();
       
   903         }
       
   904 
       
   905         public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
       
   906                 throws InvalidObjectException {
       
   907             makeCompositeBuilder();
       
   908             return compositeBuilder.fromCompositeData(lookup,
       
   909                                                       (CompositeData) value,
       
   910                                                       itemNames,
       
   911                                                       getterConverters);
       
   912         }
       
   913 
       
   914         private final String[] itemNames;
       
   915         private final Method[] getters;
       
   916         private final OpenConverter[] getterConverters;
       
   917         private CompositeBuilder compositeBuilder;
       
   918     }
       
   919 
       
   920     /** Converts from a CompositeData to an instance of the targetClass.  */
       
   921     private static abstract class CompositeBuilder {
       
   922         CompositeBuilder(Class targetClass, String[] itemNames) {
       
   923             this.targetClass = targetClass;
       
   924             this.itemNames = itemNames;
       
   925         }
       
   926 
       
   927         Class<?> getTargetClass() {
       
   928             return targetClass;
       
   929         }
       
   930 
       
   931         String[] getItemNames() {
       
   932             return itemNames;
       
   933         }
       
   934 
       
   935         /** If the subclass is appropriate for targetClass, then the
       
   936             method returns null.  If the subclass is not appropriate,
       
   937             then the method returns an explanation of why not.  If the
       
   938             subclass should be appropriate but there is a problem,
       
   939             then the method throws InvalidObjectException.  */
       
   940         abstract String applicable(Method[] getters)
       
   941                 throws InvalidObjectException;
       
   942 
       
   943         abstract Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
   944                                           String[] itemNames,
       
   945                                           OpenConverter[] converters)
       
   946                 throws InvalidObjectException;
       
   947 
       
   948         private final Class<?> targetClass;
       
   949         private final String[] itemNames;
       
   950     }
       
   951 
       
   952     /** Builder for when the target class has a method "public static
       
   953         from(CompositeData)".  */
       
   954     private static final class CompositeBuilderViaFrom
       
   955             extends CompositeBuilder {
       
   956 
       
   957         CompositeBuilderViaFrom(Class targetClass, String[] itemNames) {
       
   958             super(targetClass, itemNames);
       
   959         }
       
   960 
       
   961         String applicable(Method[] getters) throws InvalidObjectException {
       
   962             // See if it has a method "T from(CompositeData)"
       
   963             // as is conventional for a CompositeDataView
       
   964             Class<?> targetClass = getTargetClass();
       
   965             try {
       
   966                 Method fromMethod =
       
   967                     targetClass.getMethod("from",
       
   968                                           new Class[] {CompositeData.class});
       
   969 
       
   970                 if (!Modifier.isStatic(fromMethod.getModifiers())) {
       
   971                     final String msg =
       
   972                         "Method from(CompositeData) is not static";
       
   973                     throw new InvalidObjectException(msg);
       
   974                 }
       
   975 
       
   976                 if (fromMethod.getReturnType() != getTargetClass()) {
       
   977                     final String msg =
       
   978                         "Method from(CompositeData) returns " +
       
   979                         fromMethod.getReturnType().getName() +
       
   980                         " not " + targetClass.getName();
       
   981                     throw new InvalidObjectException(msg);
       
   982                 }
       
   983 
       
   984                 this.fromMethod = fromMethod;
       
   985                 return null; // success!
       
   986             } catch (InvalidObjectException e) {
       
   987                 throw e;
       
   988             } catch (Exception e) {
       
   989                 // OK: it doesn't have the method
       
   990                 return "no method from(CompositeData)";
       
   991             }
       
   992         }
       
   993 
       
   994         final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
   995                                  String[] itemNames,
       
   996                                  OpenConverter[] converters)
       
   997                 throws InvalidObjectException {
       
   998             try {
       
   999                 return fromMethod.invoke(null, cd);
       
  1000             } catch (Exception e) {
       
  1001                 final String msg = "Failed to invoke from(CompositeData)";
       
  1002                 throw invalidObjectException(msg, e);
       
  1003             }
       
  1004         }
       
  1005 
       
  1006         private Method fromMethod;
       
  1007     }
       
  1008 
       
  1009     /** This builder never actually returns success.  It simply serves
       
  1010         to check whether the other builders in the same group have any
       
  1011         chance of success.  If any getter in the targetClass returns
       
  1012         a type that we don't know how to reconstruct, then we will
       
  1013         not be able to make a builder, and there is no point in repeating
       
  1014         the error about the problematic getter as many times as there are
       
  1015         candidate builders.  Instead, the "applicable" method will return
       
  1016         an explanatory string, and the other builders will be skipped.
       
  1017         If all the getters are OK, then the "applicable" method will return
       
  1018         an empty string and the other builders will be tried.  */
       
  1019     private static class CompositeBuilderCheckGetters extends CompositeBuilder {
       
  1020         CompositeBuilderCheckGetters(Class targetClass, String[] itemNames,
       
  1021                                      OpenConverter[] getterConverters) {
       
  1022             super(targetClass, itemNames);
       
  1023             this.getterConverters = getterConverters;
       
  1024         }
       
  1025 
       
  1026         String applicable(Method[] getters) {
       
  1027             for (int i = 0; i < getters.length; i++) {
       
  1028                 try {
       
  1029                     getterConverters[i].checkReconstructible();
       
  1030                 } catch (InvalidObjectException e) {
       
  1031                     return "method " + getters[i].getName() + " returns type " +
       
  1032                         "that cannot be mapped back from OpenData";
       
  1033                 }
       
  1034             }
       
  1035             return "";
       
  1036         }
       
  1037 
       
  1038         final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
  1039                                        String[] itemNames,
       
  1040                                        OpenConverter[] converters) {
       
  1041             throw new Error();
       
  1042         }
       
  1043 
       
  1044         private final OpenConverter[] getterConverters;
       
  1045     }
       
  1046 
       
  1047     /** Builder for when the target class has a setter for every getter. */
       
  1048     private static class CompositeBuilderViaSetters extends CompositeBuilder {
       
  1049 
       
  1050         CompositeBuilderViaSetters(Class targetClass, String[] itemNames) {
       
  1051             super(targetClass, itemNames);
       
  1052         }
       
  1053 
       
  1054         String applicable(Method[] getters) {
       
  1055             try {
       
  1056                 Constructor<?> c = getTargetClass().getConstructor((Class[]) null);
       
  1057             } catch (Exception e) {
       
  1058                 return "does not have a public no-arg constructor";
       
  1059             }
       
  1060 
       
  1061             Method[] setters = new Method[getters.length];
       
  1062             for (int i = 0; i < getters.length; i++) {
       
  1063                 Method getter = getters[i];
       
  1064                 Class returnType = getter.getReturnType();
       
  1065                 String name = propertyName(getter);
       
  1066                 String setterName = "set" + name;
       
  1067                 Method setter;
       
  1068                 try {
       
  1069                     setter = getTargetClass().getMethod(setterName, returnType);
       
  1070                     if (setter.getReturnType() != void.class)
       
  1071                         throw new Exception();
       
  1072                 } catch (Exception e) {
       
  1073                     return "not all getters have corresponding setters " +
       
  1074                            "(" + getter + ")";
       
  1075                 }
       
  1076                 setters[i] = setter;
       
  1077             }
       
  1078             this.setters = setters;
       
  1079             return null;
       
  1080         }
       
  1081 
       
  1082         Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
  1083                                  String[] itemNames,
       
  1084                                  OpenConverter[] converters)
       
  1085                 throws InvalidObjectException {
       
  1086             Object o;
       
  1087             try {
       
  1088                 o = getTargetClass().newInstance();
       
  1089                 for (int i = 0; i < itemNames.length; i++) {
       
  1090                     if (cd.containsKey(itemNames[i])) {
       
  1091                         Object openItem = cd.get(itemNames[i]);
       
  1092                         Object javaItem =
       
  1093                             converters[i].fromOpenValue(lookup, openItem);
       
  1094                         setters[i].invoke(o, javaItem);
       
  1095                     }
       
  1096                 }
       
  1097             } catch (Exception e) {
       
  1098                 throw invalidObjectException(e);
       
  1099             }
       
  1100             return o;
       
  1101         }
       
  1102 
       
  1103         private Method[] setters;
       
  1104     }
       
  1105 
       
  1106     /** Builder for when the target class has a constructor that is
       
  1107         annotated with @ConstructorProperties so we can see the correspondence
       
  1108         to getters.  */
       
  1109     private static final class CompositeBuilderViaConstructor
       
  1110             extends CompositeBuilder {
       
  1111 
       
  1112         CompositeBuilderViaConstructor(Class targetClass, String[] itemNames) {
       
  1113             super(targetClass, itemNames);
       
  1114         }
       
  1115 
       
  1116         String applicable(Method[] getters) throws InvalidObjectException {
       
  1117 
       
  1118             final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
       
  1119 
       
  1120             Class targetClass = getTargetClass();
       
  1121             Constructor<?>[] constrs = targetClass.getConstructors();
       
  1122 
       
  1123             // Applicable if and only if there are any annotated constructors
       
  1124             List<Constructor<?>> annotatedConstrList = newList();
       
  1125             for (Constructor<?> constr : constrs) {
       
  1126                 if (Modifier.isPublic(constr.getModifiers())
       
  1127                         && constr.getAnnotation(propertyNamesClass) != null)
       
  1128                     annotatedConstrList.add(constr);
       
  1129             }
       
  1130 
       
  1131             if (annotatedConstrList.isEmpty())
       
  1132                 return "no constructor has @ConstructorProperties annotation";
       
  1133 
       
  1134             annotatedConstructors = newList();
       
  1135 
       
  1136             // Now check that all the annotated constructors are valid
       
  1137             // and throw an exception if not.
       
  1138 
       
  1139             // First link the itemNames to their getter indexes.
       
  1140             Map<String, Integer> getterMap = newMap();
       
  1141             String[] itemNames = getItemNames();
       
  1142             for (int i = 0; i < itemNames.length; i++)
       
  1143                 getterMap.put(itemNames[i], i);
       
  1144 
       
  1145             // Run through the constructors making the checks in the spec.
       
  1146             // For each constructor, remember the correspondence between its
       
  1147             // parameters and the items.  The int[] for a constructor says
       
  1148             // what parameter index should get what item.  For example,
       
  1149             // if element 0 is 2 then that means that item 0 in the
       
  1150             // CompositeData goes to parameter 2 of the constructor.  If an
       
  1151             // element is -1, that item isn't given to the constructor.
       
  1152             // Also remember the set of properties in that constructor
       
  1153             // so we can test unambiguity.
       
  1154             Set<BitSet> getterIndexSets = newSet();
       
  1155             for (Constructor<?> constr : annotatedConstrList) {
       
  1156                 String[] propertyNames =
       
  1157                     constr.getAnnotation(propertyNamesClass).value();
       
  1158 
       
  1159                 Type[] paramTypes = constr.getGenericParameterTypes();
       
  1160                 if (paramTypes.length != propertyNames.length) {
       
  1161                     final String msg =
       
  1162                         "Number of constructor params does not match " +
       
  1163                         "@ConstructorProperties annotation: " + constr;
       
  1164                     throw new InvalidObjectException(msg);
       
  1165                 }
       
  1166 
       
  1167                 int[] paramIndexes = new int[getters.length];
       
  1168                 for (int i = 0; i < getters.length; i++)
       
  1169                     paramIndexes[i] = -1;
       
  1170                 BitSet present = new BitSet();
       
  1171 
       
  1172                 for (int i = 0; i < propertyNames.length; i++) {
       
  1173                     String propertyName = propertyNames[i];
       
  1174                     if (!getterMap.containsKey(propertyName)) {
       
  1175                         final String msg =
       
  1176                             "@ConstructorProperties includes name " + propertyName +
       
  1177                             " which does not correspond to a property: " +
       
  1178                             constr;
       
  1179                         throw new InvalidObjectException(msg);
       
  1180                     }
       
  1181                     int getterIndex = getterMap.get(propertyName);
       
  1182                     paramIndexes[getterIndex] = i;
       
  1183                     if (present.get(getterIndex)) {
       
  1184                         final String msg =
       
  1185                             "@ConstructorProperties contains property " +
       
  1186                             propertyName + " more than once: " + constr;
       
  1187                         throw new InvalidObjectException(msg);
       
  1188                     }
       
  1189                     present.set(getterIndex);
       
  1190                     Method getter = getters[getterIndex];
       
  1191                     Type propertyType = getter.getGenericReturnType();
       
  1192                     if (!propertyType.equals(paramTypes[i])) {
       
  1193                         final String msg =
       
  1194                             "@ConstructorProperties gives property " + propertyName +
       
  1195                             " of type " + propertyType + " for parameter " +
       
  1196                             " of type " + paramTypes[i] + ": " + constr;
       
  1197                         throw new InvalidObjectException(msg);
       
  1198                     }
       
  1199                 }
       
  1200 
       
  1201                 if (!getterIndexSets.add(present)) {
       
  1202                     final String msg =
       
  1203                         "More than one constructor has a @ConstructorProperties " +
       
  1204                         "annotation with this set of names: " +
       
  1205                         Arrays.toString(propertyNames);
       
  1206                     throw new InvalidObjectException(msg);
       
  1207                 }
       
  1208 
       
  1209                 Constr c = new Constr(constr, paramIndexes, present);
       
  1210                 annotatedConstructors.add(c);
       
  1211             }
       
  1212 
       
  1213             /* Check that no possible set of items could lead to an ambiguous
       
  1214              * choice of constructor (spec requires this check).  For any
       
  1215              * pair of constructors, their union would be the minimal
       
  1216              * ambiguous set.  If this set itself corresponds to a constructor,
       
  1217              * there is no ambiguity for that pair.  In the usual case, one
       
  1218              * of the constructors is a superset of the other so the union is
       
  1219              * just the bigger constuctor.
       
  1220              *
       
  1221              * The algorithm here is quadratic in the number of constructors
       
  1222              * with a @ConstructorProperties annotation.  Typically this corresponds
       
  1223              * to the number of versions of the class there have been.  Ten
       
  1224              * would already be a large number, so although it's probably
       
  1225              * possible to have an O(n lg n) algorithm it wouldn't be
       
  1226              * worth the complexity.
       
  1227              */
       
  1228             for (BitSet a : getterIndexSets) {
       
  1229                 boolean seen = false;
       
  1230                 for (BitSet b : getterIndexSets) {
       
  1231                     if (a == b)
       
  1232                         seen = true;
       
  1233                     else if (seen) {
       
  1234                         BitSet u = new BitSet();
       
  1235                         u.or(a); u.or(b);
       
  1236                         if (!getterIndexSets.contains(u)) {
       
  1237                             Set<String> names = new TreeSet<String>();
       
  1238                             for (int i = u.nextSetBit(0); i >= 0;
       
  1239                                  i = u.nextSetBit(i+1))
       
  1240                                 names.add(itemNames[i]);
       
  1241                             final String msg =
       
  1242                                 "Constructors with @ConstructorProperties annotation " +
       
  1243                                 " would be ambiguous for these items: " +
       
  1244                                 names;
       
  1245                             throw new InvalidObjectException(msg);
       
  1246                         }
       
  1247                     }
       
  1248                 }
       
  1249             }
       
  1250 
       
  1251             return null; // success!
       
  1252         }
       
  1253 
       
  1254         Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
  1255                                  String[] itemNames,
       
  1256                                  OpenConverter[] converters)
       
  1257                 throws InvalidObjectException {
       
  1258             // The CompositeData might come from an earlier version where
       
  1259             // not all the items were present.  We look for a constructor
       
  1260             // that accepts just the items that are present.  Because of
       
  1261             // the ambiguity check in applicable(), we know there must be
       
  1262             // at most one maximally applicable constructor.
       
  1263             CompositeType ct = cd.getCompositeType();
       
  1264             BitSet present = new BitSet();
       
  1265             for (int i = 0; i < itemNames.length; i++) {
       
  1266                 if (ct.getType(itemNames[i]) != null)
       
  1267                     present.set(i);
       
  1268             }
       
  1269 
       
  1270             Constr max = null;
       
  1271             for (Constr constr : annotatedConstructors) {
       
  1272                 if (subset(constr.presentParams, present) &&
       
  1273                         (max == null ||
       
  1274                          subset(max.presentParams, constr.presentParams)))
       
  1275                     max = constr;
       
  1276             }
       
  1277 
       
  1278             if (max == null) {
       
  1279                 final String msg =
       
  1280                     "No constructor has a @ConstructorProperties for this set of " +
       
  1281                     "items: " + ct.keySet();
       
  1282                 throw new InvalidObjectException(msg);
       
  1283             }
       
  1284 
       
  1285             Object[] params = new Object[max.presentParams.cardinality()];
       
  1286             for (int i = 0; i < itemNames.length; i++) {
       
  1287                 if (!max.presentParams.get(i))
       
  1288                     continue;
       
  1289                 Object openItem = cd.get(itemNames[i]);
       
  1290                 Object javaItem = converters[i].fromOpenValue(lookup, openItem);
       
  1291                 int index = max.paramIndexes[i];
       
  1292                 if (index >= 0)
       
  1293                     params[index] = javaItem;
       
  1294             }
       
  1295 
       
  1296             try {
       
  1297                 return max.constructor.newInstance(params);
       
  1298             } catch (Exception e) {
       
  1299                 final String msg =
       
  1300                     "Exception constructing " + getTargetClass().getName();
       
  1301                 throw invalidObjectException(msg, e);
       
  1302             }
       
  1303         }
       
  1304 
       
  1305         private static boolean subset(BitSet sub, BitSet sup) {
       
  1306             BitSet subcopy = (BitSet) sub.clone();
       
  1307             subcopy.andNot(sup);
       
  1308             return subcopy.isEmpty();
       
  1309         }
       
  1310 
       
  1311         private static class Constr {
       
  1312             final Constructor<?> constructor;
       
  1313             final int[] paramIndexes;
       
  1314             final BitSet presentParams;
       
  1315             Constr(Constructor<?> constructor, int[] paramIndexes,
       
  1316                    BitSet presentParams) {
       
  1317                 this.constructor = constructor;
       
  1318                 this.paramIndexes = paramIndexes;
       
  1319                 this.presentParams = presentParams;
       
  1320             }
       
  1321         }
       
  1322 
       
  1323         private List<Constr> annotatedConstructors;
       
  1324     }
       
  1325 
       
  1326     /** Builder for when the target class is an interface and contains
       
  1327         no methods other than getters.  Then we can make an instance
       
  1328         using a dynamic proxy that forwards the getters to the source
       
  1329         CompositeData.  */
       
  1330     private static final class CompositeBuilderViaProxy
       
  1331             extends CompositeBuilder {
       
  1332 
       
  1333         CompositeBuilderViaProxy(Class targetClass, String[] itemNames) {
       
  1334             super(targetClass, itemNames);
       
  1335         }
       
  1336 
       
  1337         String applicable(Method[] getters) {
       
  1338             Class targetClass = getTargetClass();
       
  1339             if (!targetClass.isInterface())
       
  1340                 return "not an interface";
       
  1341             Set<Method> methods =
       
  1342                 newSet(Arrays.asList(targetClass.getMethods()));
       
  1343             methods.removeAll(Arrays.asList(getters));
       
  1344             /* If the interface has any methods left over, they better be
       
  1345              * public methods that are already present in java.lang.Object.
       
  1346              */
       
  1347             String bad = null;
       
  1348             for (Method m : methods) {
       
  1349                 String mname = m.getName();
       
  1350                 Class[] mparams = m.getParameterTypes();
       
  1351                 try {
       
  1352                     Method om = Object.class.getMethod(mname, mparams);
       
  1353                     if (!Modifier.isPublic(om.getModifiers()))
       
  1354                         bad = mname;
       
  1355                 } catch (NoSuchMethodException e) {
       
  1356                     bad = mname;
       
  1357                 }
       
  1358                 /* We don't catch SecurityException since it shouldn't
       
  1359                  * happen for a method in Object and if it does we would
       
  1360                  * like to know about it rather than mysteriously complaining.
       
  1361                  */
       
  1362             }
       
  1363             if (bad != null)
       
  1364                 return "contains methods other than getters (" + bad + ")";
       
  1365             return null; // success!
       
  1366         }
       
  1367 
       
  1368         final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
       
  1369                                  String[] itemNames,
       
  1370                                  OpenConverter[] converters) {
       
  1371             final Class targetClass = getTargetClass();
       
  1372             return
       
  1373                 Proxy.newProxyInstance(targetClass.getClassLoader(),
       
  1374                                        new Class[] {targetClass},
       
  1375                                        new CompositeDataInvocationHandler(cd));
       
  1376         }
       
  1377     }
       
  1378 
       
  1379     static InvalidObjectException invalidObjectException(String msg,
       
  1380                                                          Throwable cause) {
       
  1381         return EnvHelp.initCause(new InvalidObjectException(msg), cause);
       
  1382     }
       
  1383 
       
  1384     static InvalidObjectException invalidObjectException(Throwable cause) {
       
  1385         return invalidObjectException(cause.getMessage(), cause);
       
  1386     }
       
  1387 
       
  1388     static OpenDataException openDataException(String msg, Throwable cause) {
       
  1389         return EnvHelp.initCause(new OpenDataException(msg), cause);
       
  1390     }
       
  1391 
       
  1392     static OpenDataException openDataException(Throwable cause) {
       
  1393         return openDataException(cause.getMessage(), cause);
       
  1394     }
       
  1395 
       
  1396     static void mustBeComparable(Class collection, Type element)
       
  1397             throws OpenDataException {
       
  1398         if (!(element instanceof Class)
       
  1399             || !Comparable.class.isAssignableFrom((Class<?>) element)) {
       
  1400             final String msg =
       
  1401                 "Parameter class " + element + " of " +
       
  1402                 collection.getName() + " does not implement " +
       
  1403                 Comparable.class.getName();
       
  1404             throw new OpenDataException(msg);
       
  1405         }
       
  1406     }
       
  1407 
       
  1408     /**
       
  1409      * Utility method to take a string and convert it to normal Java variable
       
  1410      * name capitalization.  This normally means converting the first
       
  1411      * character from upper case to lower case, but in the (unusual) special
       
  1412      * case when there is more than one character and both the first and
       
  1413      * second characters are upper case, we leave it alone.
       
  1414      * <p>
       
  1415      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
       
  1416      * as "URL".
       
  1417      *
       
  1418      * @param  name The string to be decapitalized.
       
  1419      * @return  The decapitalized version of the string.
       
  1420      */
       
  1421     public static String decapitalize(String name) {
       
  1422         if (name == null || name.length() == 0) {
       
  1423             return name;
       
  1424         }
       
  1425         int offset1 = Character.offsetByCodePoints(name, 0, 1);
       
  1426         // Should be name.offsetByCodePoints but 6242664 makes this fail
       
  1427         if (offset1 < name.length() &&
       
  1428                 Character.isUpperCase(name.codePointAt(offset1)))
       
  1429             return name;
       
  1430         return name.substring(0, offset1).toLowerCase() +
       
  1431                name.substring(offset1);
       
  1432     }
       
  1433 
       
  1434     /**
       
  1435      * Reverse operation for java.beans.Introspector.decapitalize.  For any s,
       
  1436      * capitalize(decapitalize(s)).equals(s).  The reverse is not true:
       
  1437      * e.g. capitalize("uRL") produces "URL" which is unchanged by
       
  1438      * decapitalize.
       
  1439      */
       
  1440     static String capitalize(String name) {
       
  1441         if (name == null || name.length() == 0)
       
  1442             return name;
       
  1443         int offset1 = name.offsetByCodePoints(0, 1);
       
  1444         return name.substring(0, offset1).toUpperCase() +
       
  1445                name.substring(offset1);
       
  1446     }
       
  1447 
       
  1448     public static String propertyName(Method m) {
       
  1449         String rest = null;
       
  1450         String name = m.getName();
       
  1451         if (name.startsWith("get"))
       
  1452             rest = name.substring(3);
       
  1453         else if (name.startsWith("is") && m.getReturnType() == boolean.class)
       
  1454             rest = name.substring(2);
       
  1455         if (rest == null || rest.length() == 0
       
  1456             || m.getParameterTypes().length > 0
       
  1457             || m.getReturnType() == void.class
       
  1458             || name.equals("getClass"))
       
  1459             return null;
       
  1460         return rest;
       
  1461     }
       
  1462 
       
  1463     private final static Map<Type, Type> inProgress = newIdentityHashMap();
       
  1464     // really an IdentityHashSet but that doesn't exist
       
  1465 }