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                 valueCollection = cast(collectionClass.newInstance());
 659             } catch (Exception e) {
 660                 throw invalidObjectException("Cannot create collection", e);
 661             }
 662             for (Object o : openArray) {
 663                 Object value = elementMapping.fromOpenValue(o);
 664                 if (!valueCollection.add(value)) {
 665                     final String msg =
 666                         "Could not add " + o + " to " +
 667                         collectionClass.getName() +
 668                         " (duplicate set element?)";
 669                     throw new InvalidObjectException(msg);
 670                 }
 671             }
 672             return valueCollection;
 673         }
 674 
 675         public void checkReconstructible() throws InvalidObjectException {
 676             elementMapping.checkReconstructible();
 677         }
 678 
 679         private final Class<? extends Collection<?>> collectionClass;
 680         private final MXBeanMapping elementMapping;
 681     }
 682 
 683     private static final class MXBeanRefMapping extends NonNullMXBeanMapping {
 684         MXBeanRefMapping(Type intf) {
 685             super(intf, SimpleType.OBJECTNAME);
 686         }
 687 
 688         @Override
 689         final Object toNonNullOpenValue(Object javaValue)
 690                 throws OpenDataException {
 691             MXBeanLookup lookup = lookupNotNull(OpenDataException.class);
 692             ObjectName name = lookup.mxbeanToObjectName(javaValue);
 693             if (name == null)
 694                 throw new OpenDataException("No name for object: " + javaValue);
 695             return name;
 696         }
 697 
 698         @Override
 699         final Object fromNonNullOpenValue(Object openValue)
 700                 throws InvalidObjectException {
 701             MXBeanLookup lookup = lookupNotNull(InvalidObjectException.class);
 702             ObjectName name = (ObjectName) openValue;
 703             Object mxbean =
 704                 lookup.objectNameToMXBean(name, (Class<?>) getJavaType());
 705             if (mxbean == null) {
 706                 final String msg =
 707                     "No MXBean for name: " + name;
 708                 throw new InvalidObjectException(msg);
 709             }
 710             return mxbean;
 711         }
 712 
 713         private <T extends Exception> MXBeanLookup
 714             lookupNotNull(Class<T> excClass)
 715                 throws T {
 716             MXBeanLookup lookup = MXBeanLookup.getLookup();
 717             if (lookup == null) {
 718                 final String msg =
 719                     "Cannot convert MXBean interface in this context";
 720                 T exc;
 721                 try {
 722                     Constructor<T> con = excClass.getConstructor(String.class);
 723                     exc = con.newInstance(msg);
 724                 } catch (Exception e) {
 725                     throw new RuntimeException(e);
 726                 }
 727                 throw exc;
 728             }
 729             return lookup;
 730         }
 731     }
 732 
 733     private static final class TabularMapping extends NonNullMXBeanMapping {
 734         TabularMapping(Type targetType,
 735                        boolean sortedMap,
 736                        TabularType tabularType,
 737                        MXBeanMapping keyConverter,
 738                        MXBeanMapping valueConverter) {
 739             super(targetType, tabularType);
 740             this.sortedMap = sortedMap;
 741             this.keyMapping = keyConverter;
 742             this.valueMapping = valueConverter;
 743         }
 744 
 745         @Override
 746         final Object toNonNullOpenValue(Object value) throws OpenDataException {
 747             final Map<Object, Object> valueMap = cast(value);
 748             if (valueMap instanceof SortedMap<?,?>) {
 749                 Comparator<?> comparator = ((SortedMap<?,?>) valueMap).comparator();
 750                 if (comparator != null) {
 751                     final String msg =
 752                         "Cannot convert SortedMap with non-null comparator: " +
 753                         comparator;
 754                     throw openDataException(msg, new IllegalArgumentException(msg));
 755                 }
 756             }
 757             final TabularType tabularType = (TabularType) getOpenType();
 758             final TabularData table = new TabularDataSupport(tabularType);
 759             final CompositeType rowType = tabularType.getRowType();
 760             for (Map.Entry<Object, Object> entry : valueMap.entrySet()) {
 761                 final Object openKey = keyMapping.toOpenValue(entry.getKey());
 762                 final Object openValue = valueMapping.toOpenValue(entry.getValue());
 763                 final CompositeData row;
 764                 row =
 765                     new CompositeDataSupport(rowType, keyValueArray,
 766                                              new Object[] {openKey,
 767                                                            openValue});
 768                 table.put(row);
 769             }
 770             return table;
 771         }
 772 
 773         @Override
 774         final Object fromNonNullOpenValue(Object openValue)
 775                 throws InvalidObjectException {
 776             final TabularData table = (TabularData) openValue;
 777             final Collection<CompositeData> rows = cast(table.values());
 778             final Map<Object, Object> valueMap =
 779                 sortedMap ? newSortedMap() : newInsertionOrderMap();
 780             for (CompositeData row : rows) {
 781                 final Object key =
 782                     keyMapping.fromOpenValue(row.get("key"));
 783                 final Object value =
 784                     valueMapping.fromOpenValue(row.get("value"));
 785                 if (valueMap.put(key, value) != null) {
 786                     final String msg =
 787                         "Duplicate entry in TabularData: key=" + key;
 788                     throw new InvalidObjectException(msg);
 789                 }
 790             }
 791             return valueMap;
 792         }
 793 
 794         @Override
 795         public void checkReconstructible() throws InvalidObjectException {
 796             keyMapping.checkReconstructible();
 797             valueMapping.checkReconstructible();
 798         }
 799 
 800         private final boolean sortedMap;
 801         private final MXBeanMapping keyMapping;
 802         private final MXBeanMapping valueMapping;
 803     }
 804 
 805     private final class CompositeMapping extends NonNullMXBeanMapping {
 806         CompositeMapping(Class<?> targetClass,
 807                          CompositeType compositeType,
 808                          String[] itemNames,
 809                          Method[] getters,
 810                          MXBeanMappingFactory factory) throws OpenDataException {
 811             super(targetClass, compositeType);
 812 
 813             assert(itemNames.length == getters.length);
 814 
 815             this.itemNames = itemNames;
 816             this.getters = getters;
 817             this.getterMappings = new MXBeanMapping[getters.length];
 818             for (int i = 0; i < getters.length; i++) {
 819                 Type retType = getters[i].getGenericReturnType();
 820                 getterMappings[i] = factory.mappingForType(retType, factory);
 821             }
 822         }
 823 
 824         @Override
 825         final Object toNonNullOpenValue(Object value)
 826                 throws OpenDataException {
 827             CompositeType ct = (CompositeType) getOpenType();
 828             if (value instanceof CompositeDataView)
 829                 return ((CompositeDataView) value).toCompositeData(ct);
 830             if (value == null)
 831                 return null;
 832 
 833             Object[] values = new Object[getters.length];
 834             for (int i = 0; i < getters.length; i++) {
 835                 try {
 836                     Object got = MethodUtil.invoke(getters[i], value, (Object[]) null);
 837                     values[i] = getterMappings[i].toOpenValue(got);
 838                 } catch (Exception e) {
 839                     throw openDataException("Error calling getter for " +
 840                                             itemNames[i] + ": " + e, e);
 841                 }
 842             }
 843             return new CompositeDataSupport(ct, itemNames, values);
 844         }
 845 
 846         /** Determine how to convert back from the CompositeData into
 847             the original Java type.  For a type that is not reconstructible,
 848             this method will fail every time, and will throw the right
 849             exception. */
 850         private synchronized void makeCompositeBuilder()
 851                 throws InvalidObjectException {
 852             if (compositeBuilder != null)
 853                 return;
 854 
 855             Class<?> targetClass = (Class<?>) getJavaType();
 856             /* In this 2D array, each subarray is a set of builders where
 857                there is no point in consulting the ones after the first if
 858                the first refuses.  */
 859             CompositeBuilder[][] builders = {
 860                 {
 861                     new CompositeBuilderViaFrom(targetClass, itemNames),
 862                 },
 863                 {
 864                     new CompositeBuilderViaConstructor(targetClass, itemNames),
 865                 },
 866                 {
 867                     new CompositeBuilderCheckGetters(targetClass, itemNames,
 868                                                      getterMappings),
 869                     new CompositeBuilderViaSetters(targetClass, itemNames),
 870                     new CompositeBuilderViaProxy(targetClass, itemNames),
 871                 },
 872             };
 873             CompositeBuilder foundBuilder = null;
 874             /* We try to make a meaningful exception message by
 875                concatenating each Builder's explanation of why it
 876                isn't applicable.  */
 877             final StringBuilder whyNots = new StringBuilder();
 878             Throwable possibleCause = null;
 879         find:
 880             for (CompositeBuilder[] relatedBuilders : builders) {
 881                 for (int i = 0; i < relatedBuilders.length; i++) {
 882                     CompositeBuilder builder = relatedBuilders[i];
 883                     String whyNot = builder.applicable(getters);
 884                     if (whyNot == null) {
 885                         foundBuilder = builder;
 886                         break find;
 887                     }
 888                     Throwable cause = builder.possibleCause();
 889                     if (cause != null)
 890                         possibleCause = cause;
 891                     if (whyNot.length() > 0) {
 892                         if (whyNots.length() > 0)
 893                             whyNots.append("; ");
 894                         whyNots.append(whyNot);
 895                         if (i == 0)
 896                            break; // skip other builders in this group
 897                     }
 898                 }
 899             }
 900             if (foundBuilder == null) {
 901                 String msg =
 902                     "Do not know how to make a " + targetClass.getName() +
 903                     " from a CompositeData: " + whyNots;
 904                 if (possibleCause != null)
 905                     msg += ". Remaining exceptions show a POSSIBLE cause.";
 906                 throw invalidObjectException(msg, possibleCause);
 907             }
 908             compositeBuilder = foundBuilder;
 909         }
 910 
 911         @Override
 912         public void checkReconstructible() throws InvalidObjectException {
 913             makeCompositeBuilder();
 914         }
 915 
 916         @Override
 917         final Object fromNonNullOpenValue(Object value)
 918                 throws InvalidObjectException {
 919             makeCompositeBuilder();
 920             return compositeBuilder.fromCompositeData((CompositeData) value,
 921                                                       itemNames,
 922                                                       getterMappings);
 923         }
 924 
 925         private final String[] itemNames;
 926         private final Method[] getters;
 927         private final MXBeanMapping[] getterMappings;
 928         private CompositeBuilder compositeBuilder;
 929     }
 930 
 931     /** Converts from a CompositeData to an instance of the targetClass.  */
 932     private static abstract class CompositeBuilder {
 933         CompositeBuilder(Class<?> targetClass, String[] itemNames) {
 934             this.targetClass = targetClass;
 935             this.itemNames = itemNames;
 936         }
 937 
 938         Class<?> getTargetClass() {
 939             return targetClass;
 940         }
 941 
 942         String[] getItemNames() {
 943             return itemNames;
 944         }
 945 
 946         /** If the subclass is appropriate for targetClass, then the
 947             method returns null.  If the subclass is not appropriate,
 948             then the method returns an explanation of why not.  If the
 949             subclass should be appropriate but there is a problem,
 950             then the method throws InvalidObjectException.  */
 951         abstract String applicable(Method[] getters)
 952                 throws InvalidObjectException;
 953 
 954         /** If the subclass returns an explanation of why it is not applicable,
 955             it can additionally indicate an exception with details.  This is
 956             potentially confusing, because the real problem could be that one
 957             of the other subclasses is supposed to be applicable but isn't.
 958             But the advantage of less information loss probably outweighs the
 959             disadvantage of possible confusion.  */
 960         Throwable possibleCause() {
 961             return null;
 962         }
 963 
 964         abstract Object fromCompositeData(CompositeData cd,
 965                                           String[] itemNames,
 966                                           MXBeanMapping[] converters)
 967                 throws InvalidObjectException;
 968 
 969         private final Class<?> targetClass;
 970         private final String[] itemNames;
 971     }
 972 
 973     /** Builder for when the target class has a method "public static
 974         from(CompositeData)".  */
 975     private static final class CompositeBuilderViaFrom
 976             extends CompositeBuilder {
 977 
 978         CompositeBuilderViaFrom(Class<?> targetClass, String[] itemNames) {
 979             super(targetClass, itemNames);
 980         }
 981 
 982         String applicable(Method[] getters) throws InvalidObjectException {
 983             // See if it has a method "T from(CompositeData)"
 984             // as is conventional for a CompositeDataView
 985             Class<?> targetClass = getTargetClass();
 986             try {
 987                 Method fromMethod =
 988                     targetClass.getMethod("from", CompositeData.class);
 989 
 990                 if (!Modifier.isStatic(fromMethod.getModifiers())) {
 991                     final String msg =
 992                         "Method from(CompositeData) is not static";
 993                     throw new InvalidObjectException(msg);
 994                 }
 995 
 996                 if (fromMethod.getReturnType() != getTargetClass()) {
 997                     final String msg =
 998                         "Method from(CompositeData) returns " +
 999                         typeName(fromMethod.getReturnType()) +
1000                         " not " + typeName(targetClass);
1001                     throw new InvalidObjectException(msg);
1002                 }
1003 
1004                 this.fromMethod = fromMethod;
1005                 return null; // success!
1006             } catch (InvalidObjectException e) {
1007                 throw e;
1008             } catch (Exception e) {
1009                 // OK: it doesn't have the method
1010                 return "no method from(CompositeData)";
1011             }
1012         }
1013 
1014         final Object fromCompositeData(CompositeData cd,
1015                                        String[] itemNames,
1016                                        MXBeanMapping[] converters)
1017                 throws InvalidObjectException {
1018             try {
1019                 return MethodUtil.invoke(fromMethod, null, new Object[] {cd});
1020             } catch (Exception e) {
1021                 final String msg = "Failed to invoke from(CompositeData)";
1022                 throw invalidObjectException(msg, e);
1023             }
1024         }
1025 
1026         private Method fromMethod;
1027     }
1028 
1029     /** This builder never actually returns success.  It simply serves
1030         to check whether the other builders in the same group have any
1031         chance of success.  If any getter in the targetClass returns
1032         a type that we don't know how to reconstruct, then we will
1033         not be able to make a builder, and there is no point in repeating
1034         the error about the problematic getter as many times as there are
1035         candidate builders.  Instead, the "applicable" method will return
1036         an explanatory string, and the other builders will be skipped.
1037         If all the getters are OK, then the "applicable" method will return
1038         an empty string and the other builders will be tried.  */
1039     private static class CompositeBuilderCheckGetters extends CompositeBuilder {
1040         CompositeBuilderCheckGetters(Class<?> targetClass, String[] itemNames,
1041                                      MXBeanMapping[] getterConverters) {
1042             super(targetClass, itemNames);
1043             this.getterConverters = getterConverters;
1044         }
1045 
1046         String applicable(Method[] getters) {
1047             for (int i = 0; i < getters.length; i++) {
1048                 try {
1049                     getterConverters[i].checkReconstructible();
1050                 } catch (InvalidObjectException e) {
1051                     possibleCause = e;
1052                     return "method " + getters[i].getName() + " returns type " +
1053                         "that cannot be mapped back from OpenData";
1054                 }
1055             }
1056             return "";
1057         }
1058 
1059         @Override
1060         Throwable possibleCause() {
1061             return possibleCause;
1062         }
1063 
1064         final Object fromCompositeData(CompositeData cd,
1065                                        String[] itemNames,
1066                                        MXBeanMapping[] converters) {
1067             throw new Error();
1068         }
1069 
1070         private final MXBeanMapping[] getterConverters;
1071         private Throwable possibleCause;
1072     }
1073 
1074     /** Builder for when the target class has a setter for every getter. */
1075     private static class CompositeBuilderViaSetters extends CompositeBuilder {
1076 
1077         CompositeBuilderViaSetters(Class<?> targetClass, String[] itemNames) {
1078             super(targetClass, itemNames);
1079         }
1080 
1081         String applicable(Method[] getters) {
1082             try {
1083                 Constructor<?> c = getTargetClass().getConstructor();
1084             } catch (Exception e) {
1085                 return "does not have a public no-arg constructor";
1086             }
1087 
1088             Method[] setters = new Method[getters.length];
1089             for (int i = 0; i < getters.length; i++) {
1090                 Method getter = getters[i];
1091                 Class<?> returnType = getter.getReturnType();
1092                 String name = propertyName(getter);
1093                 String setterName = "set" + name;
1094                 Method setter;
1095                 try {
1096                     setter = getTargetClass().getMethod(setterName, returnType);
1097                     if (setter.getReturnType() != void.class)
1098                         throw new Exception();
1099                 } catch (Exception e) {
1100                     return "not all getters have corresponding setters " +
1101                            "(" + getter + ")";
1102                 }
1103                 setters[i] = setter;
1104             }
1105             this.setters = setters;
1106             return null;
1107         }
1108 
1109         Object fromCompositeData(CompositeData cd,
1110                                  String[] itemNames,
1111                                  MXBeanMapping[] converters)
1112                 throws InvalidObjectException {
1113             Object o;
1114             try {
1115                 final Class<?> targetClass = getTargetClass();
1116                 ReflectUtil.checkPackageAccess(targetClass);
1117                 o = targetClass.newInstance();
1118                 for (int i = 0; i < itemNames.length; i++) {
1119                     if (cd.containsKey(itemNames[i])) {
1120                         Object openItem = cd.get(itemNames[i]);
1121                         Object javaItem =
1122                             converters[i].fromOpenValue(openItem);
1123                         MethodUtil.invoke(setters[i], o, new Object[] {javaItem});
1124                     }
1125                 }
1126             } catch (Exception e) {
1127                 throw invalidObjectException(e);
1128             }
1129             return o;
1130         }
1131 
1132         private Method[] setters;
1133     }
1134 
1135     /** Builder for when the target class has a constructor that is
1136         annotated with {@linkplain ConstructorParameters @ConstructorParameters}
1137         or {@code @ConstructorProperties} so we can see the correspondence to getters.  */
1138     private static final class CompositeBuilderViaConstructor
1139             extends CompositeBuilder {
1140 
1141         CompositeBuilderViaConstructor(Class<?> targetClass, String[] itemNames) {
1142             super(targetClass, itemNames);
1143         }
1144 
1145         private String[] getConstPropValues(Constructor<?> ctr) {
1146             // is constructor annotated by javax.management.ConstructorParameters ?
1147             ConstructorParameters ctrProps = ctr.getAnnotation(ConstructorParameters.class);
1148             if (ctrProps != null) {
1149                 return ctrProps.value();
1150             } else {
1151                 // try the legacy java.beans.ConstructorProperties annotation
1152                 String[] vals = JavaBeansAccessor.getConstructorPropertiesValue(ctr);
1153                 return vals;
1154             }
1155         }
1156 
1157         String applicable(Method[] getters) throws InvalidObjectException {
1158             Class<?> targetClass = getTargetClass();
1159             Constructor<?>[] constrs = targetClass.getConstructors();
1160 
1161             // Applicable if and only if there are any annotated constructors
1162             List<Constructor<?>> annotatedConstrList = newList();
1163             for (Constructor<?> constr : constrs) {
1164                 if (Modifier.isPublic(constr.getModifiers())
1165                         && getConstPropValues(constr) != null)
1166                     annotatedConstrList.add(constr);
1167             }
1168 
1169             if (annotatedConstrList.isEmpty())
1170                 return "no constructor has either @ConstructorParameters " +
1171                        "or @ConstructorProperties annotation";
1172 
1173             annotatedConstructors = newList();
1174 
1175             // Now check that all the annotated constructors are valid
1176             // and throw an exception if not.
1177 
1178             // First link the itemNames to their getter indexes.
1179             Map<String, Integer> getterMap = newMap();
1180             String[] itemNames = getItemNames();
1181             for (int i = 0; i < itemNames.length; i++)
1182                 getterMap.put(itemNames[i], i);
1183 
1184             // Run through the constructors making the checks in the spec.
1185             // For each constructor, remember the correspondence between its
1186             // parameters and the items.  The int[] for a constructor says
1187             // what parameter index should get what item.  For example,
1188             // if element 0 is 2 then that means that item 0 in the
1189             // CompositeData goes to parameter 2 of the constructor.  If an
1190             // element is -1, that item isn't given to the constructor.
1191             // Also remember the set of properties in that constructor
1192             // so we can test unambiguity.
1193             Set<BitSet> getterIndexSets = newSet();
1194             for (Constructor<?> constr : annotatedConstrList) {
1195                 String annotationName =
1196                     constr.isAnnotationPresent(ConstructorParameters.class) ?
1197                         "@ConstructorParameters" : "@ConstructorProperties";
1198 
1199                 String[] propertyNames = getConstPropValues(constr);
1200 
1201                 Type[] paramTypes = constr.getGenericParameterTypes();
1202                 if (paramTypes.length != propertyNames.length) {
1203                     final String msg =
1204                         "Number of constructor params does not match " +
1205                         annotationName + " annotation: " + constr;
1206                     throw new InvalidObjectException(msg);
1207                 }
1208 
1209                 int[] paramIndexes = new int[getters.length];
1210                 for (int i = 0; i < getters.length; i++)
1211                     paramIndexes[i] = -1;
1212                 BitSet present = new BitSet();
1213 
1214                 for (int i = 0; i < propertyNames.length; i++) {
1215                     String propertyName = propertyNames[i];
1216                     if (!getterMap.containsKey(propertyName)) {
1217                         String msg =
1218                             annotationName + " includes name " + propertyName +
1219                             " which does not correspond to a property";
1220                         for (String getterName : getterMap.keySet()) {
1221                             if (getterName.equalsIgnoreCase(propertyName)) {
1222                                 msg += " (differs only in case from property " +
1223                                         getterName + ")";
1224                             }
1225                         }
1226                         msg += ": " + constr;
1227                         throw new InvalidObjectException(msg);
1228                     }
1229                     int getterIndex = getterMap.get(propertyName);
1230                     paramIndexes[getterIndex] = i;
1231                     if (present.get(getterIndex)) {
1232                         final String msg =
1233                             annotationName + " contains property " +
1234                             propertyName + " more than once: " + constr;
1235                         throw new InvalidObjectException(msg);
1236                     }
1237                     present.set(getterIndex);
1238                     Method getter = getters[getterIndex];
1239                     Type propertyType = getter.getGenericReturnType();
1240                     if (!propertyType.equals(paramTypes[i])) {
1241                         final String msg =
1242                             annotationName + " gives property " + propertyName +
1243                             " of type " + propertyType + " for parameter " +
1244                             " of type " + paramTypes[i] + ": " + constr;
1245                         throw new InvalidObjectException(msg);
1246                     }
1247                 }
1248 
1249                 if (!getterIndexSets.add(present)) {
1250                     final String msg =
1251                         "More than one constructor has " +
1252                         "@ConstructorParameters or @ConstructorProperties " +
1253                         "annotation with this set of names: " +
1254                         Arrays.toString(propertyNames);
1255                     throw new InvalidObjectException(msg);
1256                 }
1257 
1258                 Constr c = new Constr(constr, paramIndexes, present);
1259                 annotatedConstructors.add(c);
1260             }
1261 
1262             /* Check that no possible set of items could lead to an ambiguous
1263              * choice of constructor (spec requires this check).  For any
1264              * pair of constructors, their union would be the minimal
1265              * ambiguous set.  If this set itself corresponds to a constructor,
1266              * there is no ambiguity for that pair.  In the usual case, one
1267              * of the constructors is a superset of the other so the union is
1268              * just the bigger constructor.
1269              *
1270              * The algorithm here is quadratic in the number of constructors
1271              * with a @ConstructorParameters or @ConstructructorProperties annotation.
1272              * Typically this corresponds to the number of versions of the class
1273              * there have been.  Ten would already be a large number, so although
1274              * it's probably possible to have an O(n lg n) algorithm it wouldn't be
1275              * worth the complexity.
1276              */
1277             for (BitSet a : getterIndexSets) {
1278                 boolean seen = false;
1279                 for (BitSet b : getterIndexSets) {
1280                     if (a == b)
1281                         seen = true;
1282                     else if (seen) {
1283                         BitSet u = new BitSet();
1284                         u.or(a); u.or(b);
1285                         if (!getterIndexSets.contains(u)) {
1286                             Set<String> names = new TreeSet<String>();
1287                             for (int i = u.nextSetBit(0); i >= 0;
1288                                  i = u.nextSetBit(i+1))
1289                                 names.add(itemNames[i]);
1290                             final String msg =
1291                                 "Constructors with @ConstructorParameters or " +
1292                                 "@ConstructorProperties annotation " +
1293                                 "would be ambiguous for these items: " +
1294                                 names;
1295                             throw new InvalidObjectException(msg);
1296                         }
1297                     }
1298                 }
1299             }
1300 
1301             return null; // success!
1302         }
1303 
1304         final Object fromCompositeData(CompositeData cd,
1305                                        String[] itemNames,
1306                                        MXBeanMapping[] mappings)
1307                 throws InvalidObjectException {
1308             // The CompositeData might come from an earlier version where
1309             // not all the items were present.  We look for a constructor
1310             // that accepts just the items that are present.  Because of
1311             // the ambiguity check in applicable(), we know there must be
1312             // at most one maximally applicable constructor.
1313             CompositeType ct = cd.getCompositeType();
1314             BitSet present = new BitSet();
1315             for (int i = 0; i < itemNames.length; i++) {
1316                 if (ct.getType(itemNames[i]) != null)
1317                     present.set(i);
1318             }
1319 
1320             Constr max = null;
1321             for (Constr constr : annotatedConstructors) {
1322                 if (subset(constr.presentParams, present) &&
1323                         (max == null ||
1324                          subset(max.presentParams, constr.presentParams)))
1325                     max = constr;
1326             }
1327 
1328             if (max == null) {
1329                 final String msg =
1330                     "No constructor has either @ConstructorParameters " +
1331                     "or @ConstructorProperties annotation for this set of " +
1332                     "items: " + ct.keySet();
1333                 throw new InvalidObjectException(msg);
1334             }
1335 
1336             Object[] params = new Object[max.presentParams.cardinality()];
1337             for (int i = 0; i < itemNames.length; i++) {
1338                 if (!max.presentParams.get(i))
1339                     continue;
1340                 Object openItem = cd.get(itemNames[i]);
1341                 Object javaItem = mappings[i].fromOpenValue(openItem);
1342                 int index = max.paramIndexes[i];
1343                 if (index >= 0)
1344                     params[index] = javaItem;
1345             }
1346 
1347             try {
1348                 ReflectUtil.checkPackageAccess(max.constructor.getDeclaringClass());
1349                 return max.constructor.newInstance(params);
1350             } catch (Exception e) {
1351                 final String msg =
1352                     "Exception constructing " + getTargetClass().getName();
1353                 throw invalidObjectException(msg, e);
1354             }
1355         }
1356 
1357         private static boolean subset(BitSet sub, BitSet sup) {
1358             BitSet subcopy = (BitSet) sub.clone();
1359             subcopy.andNot(sup);
1360             return subcopy.isEmpty();
1361         }
1362 
1363         private static class Constr {
1364             final Constructor<?> constructor;
1365             final int[] paramIndexes;
1366             final BitSet presentParams;
1367             Constr(Constructor<?> constructor, int[] paramIndexes,
1368                    BitSet presentParams) {
1369                 this.constructor = constructor;
1370                 this.paramIndexes = paramIndexes;
1371                 this.presentParams = presentParams;
1372             }
1373         }
1374 
1375         private List<Constr> annotatedConstructors;
1376     }
1377 
1378     /** Builder for when the target class is an interface and contains
1379         no methods other than getters.  Then we can make an instance
1380         using a dynamic proxy that forwards the getters to the source
1381         CompositeData.  */
1382     private static final class CompositeBuilderViaProxy
1383             extends CompositeBuilder {
1384 
1385         CompositeBuilderViaProxy(Class<?> targetClass, String[] itemNames) {
1386             super(targetClass, itemNames);
1387         }
1388 
1389         String applicable(Method[] getters) {
1390             Class<?> targetClass = getTargetClass();
1391             if (!targetClass.isInterface())
1392                 return "not an interface";
1393             Set<Method> methods =
1394                 newSet(Arrays.asList(targetClass.getMethods()));
1395             methods.removeAll(Arrays.asList(getters));
1396             /* If the interface has any methods left over, they better be
1397              * public methods that are already present in java.lang.Object.
1398              */
1399             String bad = null;
1400             for (Method m : methods) {
1401                 String mname = m.getName();
1402                 Class<?>[] mparams = m.getParameterTypes();
1403                 try {
1404                     Method om = Object.class.getMethod(mname, mparams);
1405                     if (!Modifier.isPublic(om.getModifiers()))
1406                         bad = mname;
1407                 } catch (NoSuchMethodException e) {
1408                     bad = mname;
1409                 }
1410                 /* We don't catch SecurityException since it shouldn't
1411                  * happen for a method in Object and if it does we would
1412                  * like to know about it rather than mysteriously complaining.
1413                  */
1414             }
1415             if (bad != null)
1416                 return "contains methods other than getters (" + bad + ")";
1417             return null; // success!
1418         }
1419 
1420         final Object fromCompositeData(CompositeData cd,
1421                                        String[] itemNames,
1422                                        MXBeanMapping[] converters) {
1423             final Class<?> targetClass = getTargetClass();
1424             return
1425                 Proxy.newProxyInstance(targetClass.getClassLoader(),
1426                                        new Class<?>[] {targetClass},
1427                                        new CompositeDataInvocationHandler(cd));
1428         }
1429     }
1430 
1431     static InvalidObjectException invalidObjectException(String msg,
1432                                                          Throwable cause) {
1433         return EnvHelp.initCause(new InvalidObjectException(msg), cause);
1434     }
1435 
1436     static InvalidObjectException invalidObjectException(Throwable cause) {
1437         return invalidObjectException(cause.getMessage(), cause);
1438     }
1439 
1440     static OpenDataException openDataException(String msg, Throwable cause) {
1441         return EnvHelp.initCause(new OpenDataException(msg), cause);
1442     }
1443 
1444     static OpenDataException openDataException(Throwable cause) {
1445         return openDataException(cause.getMessage(), cause);
1446     }
1447 
1448     static void mustBeComparable(Class<?> collection, Type element)
1449             throws OpenDataException {
1450         if (!(element instanceof Class<?>)
1451             || !Comparable.class.isAssignableFrom((Class<?>) element)) {
1452             final String msg =
1453                 "Parameter class " + element + " of " +
1454                 collection.getName() + " does not implement " +
1455                 Comparable.class.getName();
1456             throw new OpenDataException(msg);
1457         }
1458     }
1459 
1460     /**
1461      * Utility method to take a string and convert it to normal Java variable
1462      * name capitalization.  This normally means converting the first
1463      * character from upper case to lower case, but in the (unusual) special
1464      * case when there is more than one character and both the first and
1465      * second characters are upper case, we leave it alone.
1466      * <p>
1467      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
1468      * as "URL".
1469      *
1470      * @param  name The string to be decapitalized.
1471      * @return  The decapitalized version of the string.
1472      */
1473     public static String decapitalize(String name) {
1474         if (name == null || name.length() == 0) {
1475             return name;
1476         }
1477         int offset1 = Character.offsetByCodePoints(name, 0, 1);
1478         // Should be name.offsetByCodePoints but 6242664 makes this fail
1479         if (offset1 < name.length() &&
1480                 Character.isUpperCase(name.codePointAt(offset1)))
1481             return name;
1482         return name.substring(0, offset1).toLowerCase() +
1483                name.substring(offset1);
1484     }
1485 
1486     /**
1487      * Reverse operation for java.beans.Introspector.decapitalize.  For any s,
1488      * capitalize(decapitalize(s)).equals(s).  The reverse is not true:
1489      * e.g. capitalize("uRL") produces "URL" which is unchanged by
1490      * decapitalize.
1491      */
1492     static String capitalize(String name) {
1493         if (name == null || name.length() == 0)
1494             return name;
1495         int offset1 = name.offsetByCodePoints(0, 1);
1496         return name.substring(0, offset1).toUpperCase() +
1497                name.substring(offset1);
1498     }
1499 
1500     public static String propertyName(Method m) {
1501         String rest = null;
1502         String name = m.getName();
1503         if (name.startsWith("get"))
1504             rest = name.substring(3);
1505         else if (name.startsWith("is") && m.getReturnType() == boolean.class)
1506             rest = name.substring(2);
1507         if (rest == null || rest.length() == 0
1508             || m.getParameterTypes().length > 0
1509             || m.getReturnType() == void.class
1510             || name.equals("getClass"))
1511             return null;
1512         return rest;
1513     }
1514 
1515     private final static Map<Type, Type> inProgress = newIdentityHashMap();
1516     // really an IdentityHashSet but that doesn't exist
1517 }