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