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