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