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