changeset 353:af0a68f46dde

6562936: Support custom type mappings in MXBeans Reviewed-by: dfuchs
author emcmanus
date Thu, 05 Jun 2008 13:42:47 +0200
parents b715e82ef7e1
children 657f24cdfc02 f570cbc8d4ff 025fdbd9090b
files src/share/classes/com/sun/jmx/mbeanserver/ConvertingMethod.java src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java src/share/classes/com/sun/jmx/mbeanserver/Introspector.java src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java src/share/classes/com/sun/jmx/mbeanserver/MXBeanLookup.java src/share/classes/com/sun/jmx/mbeanserver/MXBeanProxy.java src/share/classes/com/sun/jmx/mbeanserver/MXBeanSupport.java src/share/classes/com/sun/jmx/mbeanserver/NotificationMBeanSupport.java src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java src/share/classes/com/sun/jmx/mbeanserver/PerInterface.java src/share/classes/com/sun/jmx/mbeanserver/StandardMBeanSupport.java src/share/classes/javax/management/JMX.java src/share/classes/javax/management/MBeanServerInvocationHandler.java src/share/classes/javax/management/MXBean.java src/share/classes/javax/management/StandardMBean.java src/share/classes/javax/management/ToQueryString.java src/share/classes/javax/management/openmbean/CompositeDataInvocationHandler.java src/share/classes/javax/management/openmbean/CompositeType.java src/share/classes/javax/management/openmbean/MXBeanMapping.java src/share/classes/javax/management/openmbean/MXBeanMappingClass.java src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java src/share/classes/javax/management/openmbean/MXBeanMappingFactoryClass.java src/share/classes/javax/management/openmbean/OpenType.java test/javax/management/mxbean/CustomTypeTest.java test/javax/management/mxbean/customtypes/CustomLongMXBean.java test/javax/management/mxbean/customtypes/CustomMXBean.java test/javax/management/mxbean/customtypes/IntegerIsLongFactory.java test/javax/management/mxbean/customtypes/IntegerIsStringFactory.java test/javax/management/mxbean/customtypes/package-info.java
diffstat 32 files changed, 3948 insertions(+), 1761 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/jmx/mbeanserver/ConvertingMethod.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/ConvertingMethod.java	Thu Jun 05 13:42:47 2008 +0200
@@ -31,13 +31,15 @@
 
 import javax.management.Descriptor;
 import javax.management.MBeanException;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingFactory;
 import javax.management.openmbean.OpenDataException;
 import javax.management.openmbean.OpenType;
 
 final class ConvertingMethod {
-    static ConvertingMethod from(Method m) {
+    static ConvertingMethod from(Method m, MXBeanMappingFactory mappingFactory) {
         try {
-            return new ConvertingMethod(m);
+            return new ConvertingMethod(m, mappingFactory);
         } catch (OpenDataException ode) {
             final String msg = "Method " + m.getDeclaringClass().getName() +
                 "." + m.getName() + " has parameter or return type that " +
@@ -67,13 +69,13 @@
     }
 
     OpenType getOpenReturnType() {
-        return returnConverter.getOpenType();
+        return returnMapping.getOpenType();
     }
 
     OpenType[] getOpenParameterTypes() {
-        final OpenType[] types = new OpenType[paramConverters.length];
-        for (int i = 0; i < paramConverters.length; i++)
-            types[i] = paramConverters[i].getOpenType();
+        final OpenType[] types = new OpenType[paramMappings.length];
+        for (int i = 0; i < paramMappings.length; i++)
+            types[i] = paramMappings[i].getOpenType();
         return types;
     }
 
@@ -85,9 +87,9 @@
      * value will be converted to an Open Type, so if it is convertible
      * at all there is no further check needed.
      */
-    void checkCallFromOpen() throws IllegalArgumentException {
+    void checkCallFromOpen() {
         try {
-            for (OpenConverter paramConverter : paramConverters)
+            for (MXBeanMapping paramConverter : paramMappings)
                 paramConverter.checkReconstructible();
         } catch (InvalidObjectException e) {
             throw new IllegalArgumentException(e);
@@ -102,32 +104,32 @@
      * open types, so if it is convertible at all there is no further
      * check needed.
      */
-    void checkCallToOpen() throws IllegalArgumentException {
+    void checkCallToOpen() {
         try {
-            returnConverter.checkReconstructible();
+            returnMapping.checkReconstructible();
         } catch (InvalidObjectException e) {
             throw new IllegalArgumentException(e);
         }
     }
 
     String[] getOpenSignature() {
-        if (paramConverters.length == 0)
+        if (paramMappings.length == 0)
             return noStrings;
 
-        String[] sig = new String[paramConverters.length];
-        for (int i = 0; i < paramConverters.length; i++)
-            sig[i] = paramConverters[i].getOpenClass().getName();
+        String[] sig = new String[paramMappings.length];
+        for (int i = 0; i < paramMappings.length; i++)
+            sig[i] = paramMappings[i].getOpenClass().getName();
         return sig;
     }
 
     final Object toOpenReturnValue(MXBeanLookup lookup, Object ret)
             throws OpenDataException {
-        return returnConverter.toOpenValue(lookup, ret);
+        return returnMapping.toOpenValue(ret);
     }
 
     final Object fromOpenReturnValue(MXBeanLookup lookup, Object ret)
             throws InvalidObjectException {
-        return returnConverter.fromOpenValue(lookup, ret);
+        return returnMapping.fromOpenValue(ret);
     }
 
     final Object[] toOpenParameters(MXBeanLookup lookup, Object[] params)
@@ -136,17 +138,17 @@
             return params;
         final Object[] oparams = new Object[params.length];
         for (int i = 0; i < params.length; i++)
-            oparams[i] = paramConverters[i].toOpenValue(lookup, params[i]);
+            oparams[i] = paramMappings[i].toOpenValue(params[i]);
         return oparams;
     }
 
-    final Object[] fromOpenParameters(MXBeanLookup lookup, Object[] params)
+    final Object[] fromOpenParameters(Object[] params)
             throws InvalidObjectException {
         if (paramConversionIsIdentity || params == null)
             return params;
         final Object[] jparams = new Object[params.length];
         for (int i = 0; i < params.length; i++)
-            jparams[i] = paramConverters[i].fromOpenValue(lookup, params[i]);
+            jparams[i] = paramMappings[i].fromOpenValue(params[i]);
         return jparams;
     }
 
@@ -154,23 +156,35 @@
                                  Object param,
                                  int paramNo)
         throws OpenDataException {
-        return paramConverters[paramNo].toOpenValue(lookup, param);
+        return paramMappings[paramNo].toOpenValue(param);
     }
 
     final Object fromOpenParameter(MXBeanLookup lookup,
                                    Object param,
                                    int paramNo)
         throws InvalidObjectException {
-        return paramConverters[paramNo].fromOpenValue(lookup, param);
+        return paramMappings[paramNo].fromOpenValue(param);
     }
 
     Object invokeWithOpenReturn(MXBeanLookup lookup,
                                 Object obj, Object[] params)
             throws MBeanException, IllegalAccessException,
                    InvocationTargetException {
+        MXBeanLookup old = MXBeanLookup.getLookup();
+        try {
+            MXBeanLookup.setLookup(lookup);
+            return invokeWithOpenReturn(obj, params);
+        } finally {
+            MXBeanLookup.setLookup(old);
+        }
+    }
+
+    private Object invokeWithOpenReturn(Object obj, Object[] params)
+            throws MBeanException, IllegalAccessException,
+                   InvocationTargetException {
         final Object[] javaParams;
         try {
-            javaParams = fromOpenParameters(lookup, params);
+            javaParams = fromOpenParameters(params);
         } catch (InvalidObjectException e) {
             // probably can't happen
             final String msg = methodName() + ": cannot convert parameters " +
@@ -179,7 +193,7 @@
         }
         final Object javaReturn = method.invoke(obj, javaParams);
         try {
-            return returnConverter.toOpenValue(lookup, javaReturn);
+            return returnMapping.toOpenValue(javaReturn);
         } catch (OpenDataException e) {
             // probably can't happen
             final String msg = methodName() + ": cannot convert return " +
@@ -192,15 +206,17 @@
         return method.getDeclaringClass() + "." + method.getName();
     }
 
-    private ConvertingMethod(Method m) throws OpenDataException {
+    private ConvertingMethod(Method m, MXBeanMappingFactory mappingFactory)
+    throws OpenDataException {
         this.method = m;
-        returnConverter = OpenConverter.toConverter(m.getGenericReturnType());
+        returnMapping =
+                mappingFactory.mappingForType(m.getGenericReturnType(), mappingFactory);
         Type[] params = m.getGenericParameterTypes();
-        paramConverters = new OpenConverter[params.length];
+        paramMappings = new MXBeanMapping[params.length];
         boolean identity = true;
         for (int i = 0; i < params.length; i++) {
-            paramConverters[i] = OpenConverter.toConverter(params[i]);
-            identity &= paramConverters[i].isIdentity();
+            paramMappings[i] = mappingFactory.mappingForType(params[i], mappingFactory);
+            identity &= DefaultMXBeanMappingFactory.isIdentity(paramMappings[i]);
         }
         paramConversionIsIdentity = identity;
     }
@@ -208,7 +224,7 @@
     private static final String[] noStrings = new String[0];
 
     private final Method method;
-    private final OpenConverter returnConverter;
-    private final OpenConverter[] paramConverters;
+    private final MXBeanMapping returnMapping;
+    private final MXBeanMapping[] paramMappings;
     private final boolean paramConversionIsIdentity;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,1519 @@
+/*
+ * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.mbeanserver;
+
+import static com.sun.jmx.mbeanserver.Util.*;
+import java.lang.annotation.ElementType;
+import javax.management.openmbean.MXBeanMappingClass;
+
+import static javax.management.openmbean.SimpleType.*;
+
+import com.sun.jmx.remote.util.EnvHelp;
+
+import java.beans.ConstructorProperties;
+import java.io.InvalidObjectException;
+import java.lang.annotation.ElementType;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.WeakHashMap;
+
+import javax.management.JMX;
+import javax.management.ObjectName;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataInvocationHandler;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeDataView;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+/**
+ *   <p>A converter between Java types and the limited set of classes
+ *   defined by Open MBeans.</p>
+ *
+ *   <p>A Java type is an instance of java.lang.reflect.Type.  For our
+ *   purposes, it is either a Class, such as String.class or int.class;
+ *   or a ParameterizedType, such as List<String> or Map<Integer,
+ *   String[]>.  On J2SE 1.4 and earlier, it can only be a Class.</p>
+ *
+ *   <p>Each Type is associated with an DefaultMXBeanMappingFactory.  The
+ *   DefaultMXBeanMappingFactory defines an OpenType corresponding to the Type, plus a
+ *   Java class corresponding to the OpenType.  For example:</p>
+ *
+ *   <pre>
+ *   Type                     Open class     OpenType
+ *   ----                     ----------     --------
+ *   Integer                Integer        SimpleType.INTEGER
+ *   int                            int            SimpleType.INTEGER
+ *   Integer[]              Integer[]      ArrayType(1, SimpleType.INTEGER)
+ *   int[]                  Integer[]      ArrayType(SimpleType.INTEGER, true)
+ *   String[][]             String[][]     ArrayType(2, SimpleType.STRING)
+ *   List<String>                   String[]       ArrayType(1, SimpleType.STRING)
+ *   ThreadState (an Enum)    String         SimpleType.STRING
+ *   Map<Integer, String[]>   TabularData          TabularType(
+ *                                           CompositeType(
+ *                                             {"key", SimpleType.INTEGER},
+ *                                             {"value",
+ *                                               ArrayType(1,
+ *                                                SimpleType.STRING)}),
+ *                                           indexNames={"key"})
+ *   </pre>
+ *
+ *   <p>Apart from simple types, arrays, and collections, Java types are
+ *   converted through introspection into CompositeType.  The Java type
+ *   must have at least one getter (method such as "int getSize()" or
+ *   "boolean isBig()"), and we must be able to deduce how to
+ *   reconstruct an instance of the Java class from the values of the
+ *   getters using one of various heuristics.</p>
+ *
+ * @since 1.6
+ */
+public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
+    static abstract class NonNullMXBeanMapping extends MXBeanMapping {
+        NonNullMXBeanMapping(Type javaType, OpenType openType) {
+            super(javaType, openType);
+        }
+
+        @Override
+        public final Object fromOpenValue(Object openValue)
+        throws InvalidObjectException {
+            if (openValue == null)
+                return null;
+            else
+                return fromNonNullOpenValue(openValue);
+        }
+
+        @Override
+        public final Object toOpenValue(Object javaValue) throws OpenDataException {
+            if (javaValue == null)
+                return null;
+            else
+                return toNonNullOpenValue(javaValue);
+        }
+
+        abstract Object fromNonNullOpenValue(Object openValue)
+        throws InvalidObjectException;
+
+        abstract Object toNonNullOpenValue(Object javaValue)
+        throws OpenDataException;
+
+        /**
+         * <p>True if and only if this MXBeanMapping's toOpenValue and
+         * fromOpenValue methods are the identity function.</p>
+         */
+        boolean isIdentity() {
+            return false;
+        }
+    }
+
+    static boolean isIdentity(MXBeanMapping mapping) {
+        return (mapping instanceof NonNullMXBeanMapping &&
+                ((NonNullMXBeanMapping) mapping).isIdentity());
+    }
+
+    private static final class Mappings
+        extends WeakHashMap<Type, WeakReference<MXBeanMapping>> {}
+
+    private static final Map<MXBeanMappingFactory, Mappings> factoryMappings =
+            new WeakHashMap<MXBeanMappingFactory, Mappings>();
+
+    private static final Map<Type, MXBeanMapping> permanentMappings = newMap();
+
+    private static synchronized MXBeanMapping getMapping(
+            Type type, MXBeanMappingFactory factory) {
+        Mappings mappings = factoryMappings.get(factory);
+        if (mappings == null) {
+            mappings = new Mappings();
+            factoryMappings.put(factory, mappings);
+        }
+        WeakReference<MXBeanMapping> wr = mappings.get(type);
+        return (wr == null) ? null : wr.get();
+    }
+
+    private static synchronized void putMapping(
+            Type type, MXBeanMapping mapping, MXBeanMappingFactory factory) {
+        Mappings mappings = factoryMappings.get(factory);
+        if (mappings == null) {
+            mappings = new Mappings();
+            factoryMappings.put(factory, mappings);
+        }
+        WeakReference<MXBeanMapping> wr =
+            new WeakReference<MXBeanMapping>(mapping);
+        mappings.put(type, wr);
+    }
+
+    static {
+        /* Set up the mappings for Java types that map to SimpleType.  */
+
+        final OpenType[] simpleTypes = {
+            BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
+            DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
+            VOID,
+        };
+
+        for (int i = 0; i < simpleTypes.length; i++) {
+            final OpenType t = simpleTypes[i];
+            Class c;
+            try {
+                c = Class.forName(t.getClassName(), false,
+                                  ObjectName.class.getClassLoader());
+            } catch (ClassNotFoundException e) {
+                // the classes that these predefined types declare must exist!
+                throw new Error(e);
+            }
+            final MXBeanMapping mapping = new IdentityMapping(c, t);
+            permanentMappings.put(c, mapping);
+
+            if (c.getName().startsWith("java.lang.")) {
+                try {
+                    final Field typeField = c.getField("TYPE");
+                    final Class<?> primitiveType = (Class<?>) typeField.get(null);
+                    final MXBeanMapping primitiveMapping =
+                        new IdentityMapping(primitiveType, t);
+                    permanentMappings.put(primitiveType, primitiveMapping);
+                    if (primitiveType != void.class) {
+                        final Class<?> primitiveArrayType =
+                            Array.newInstance(primitiveType, 0).getClass();
+                        final OpenType primitiveArrayOpenType =
+                            ArrayType.getPrimitiveArrayType(primitiveArrayType);
+                        final MXBeanMapping primitiveArrayMapping =
+                            new IdentityMapping(primitiveArrayType,
+                                                primitiveArrayOpenType);
+                        permanentMappings.put(primitiveArrayType,
+                                              primitiveArrayMapping);
+                    }
+                } catch (NoSuchFieldException e) {
+                    // OK: must not be a primitive wrapper
+                } catch (IllegalAccessException e) {
+                    // Should not reach here
+                    assert(false);
+                }
+            }
+        }
+    }
+
+    /** Get the converter for the given Java type, creating it if necessary. */
+    @Override
+    public synchronized MXBeanMapping mappingForType(Type objType,
+                                                     MXBeanMappingFactory factory)
+            throws OpenDataException {
+        if (inProgress.containsKey(objType))
+            throw new OpenDataException("Recursive data structure");
+
+        MXBeanMapping mapping;
+
+        mapping = getMapping(objType, null);
+        if (mapping != null)
+            return mapping;
+
+        inProgress.put(objType, objType);
+        try {
+            mapping = makeMapping(objType, factory);
+        } finally {
+            inProgress.remove(objType);
+        }
+
+        putMapping(objType, mapping, factory);
+        return mapping;
+    }
+
+    private MXBeanMapping makeMapping(Type objType, MXBeanMappingFactory factory)
+    throws OpenDataException {
+
+        /* It's not yet worth formalizing these tests by having for example
+           an array of factory classes, each of which says whether it
+           recognizes the Type (Chain of Responsibility pattern).  */
+        MXBeanMapping mapping = permanentMappings.get(objType);
+        if (mapping != null)
+            return mapping;
+        Class<?> erasure = erasure(objType);
+        MXBeanMappingClass mappingClass =
+                erasure.getAnnotation(MXBeanMappingClass.class);
+        if (mappingClass != null)
+            return makeAnnotationMapping(mappingClass, objType, factory);
+        if (objType instanceof GenericArrayType) {
+            Type componentType =
+                ((GenericArrayType) objType).getGenericComponentType();
+            return makeArrayOrCollectionMapping(objType, componentType, factory);
+        } else if (objType instanceof Class) {
+            Class<?> objClass = (Class<?>) objType;
+            if (objClass.isEnum()) {
+                // Huge hack to avoid compiler warnings here.  The ElementType
+                // parameter is ignored but allows us to obtain a type variable
+                // T that matches <T extends Enum<T>>.
+                return makeEnumMapping((Class) objClass, ElementType.class);
+            } else if (objClass.isArray()) {
+                Type componentType = objClass.getComponentType();
+                return makeArrayOrCollectionMapping(objClass, componentType,
+                        factory);
+            } else if (JMX.isMXBeanInterface(objClass)) {
+                return makeMXBeanRefMapping(objClass);
+            } else {
+                return makeCompositeMapping(objClass, factory);
+            }
+        } else if (objType instanceof ParameterizedType) {
+            return makeParameterizedTypeMapping((ParameterizedType) objType,
+                                                factory);
+        } else
+            throw new OpenDataException("Cannot map type: " + objType);
+    }
+
+    private static MXBeanMapping
+            makeAnnotationMapping(MXBeanMappingClass mappingClass,
+                                  Type objType,
+                                  MXBeanMappingFactory factory)
+    throws OpenDataException {
+        Class<? extends MXBeanMapping> c = mappingClass.value();
+        Constructor<? extends MXBeanMapping> cons;
+        try {
+            cons = c.getConstructor(Type.class);
+        } catch (NoSuchMethodException e) {
+            final String msg =
+                    "Annotation @" + MXBeanMappingClass.class.getName() +
+                    " must name a class with a public constructor that has a " +
+                    "single " + Type.class.getName() + " argument";
+            OpenDataException ode = new OpenDataException(msg);
+            ode.initCause(e);
+            throw ode;
+        }
+        try {
+            return cons.newInstance(objType);
+        } catch (Exception e) {
+            final String msg =
+                    "Could not construct a " + c.getName() + " for @" +
+                    MXBeanMappingClass.class.getName();
+            OpenDataException ode = new OpenDataException(msg);
+            ode.initCause(e);
+            throw ode;
+        }
+    }
+
+    private static Class<?> erasure(Type t) {
+        if (t instanceof Class<?>)
+            return (Class<?>) t;
+        if (t instanceof ParameterizedType)
+            return erasure(((ParameterizedType) t).getRawType());
+        /* Other cases: GenericArrayType, TypeVariable, WildcardType.
+         * Returning the erasure of GenericArrayType is not necessary because
+         * anyway we will be recursing on the element type, and we'll erase
+         * then.  Returning the erasure of the other two would mean returning
+         * the type bound (e.g. Foo in <T extends Foo> or <? extends Foo>)
+         * and since we don't treat this as Foo elsewhere we shouldn't here.
+         */
+        return Object.class;
+    }
+
+    private static <T extends Enum<T>> MXBeanMapping
+            makeEnumMapping(Class enumClass, Class<T> fake) {
+        return new EnumMapping<T>(Util.<Class<T>>cast(enumClass));
+    }
+
+    /* Make the converter for an array type, or a collection such as
+     * List<String> or Set<Integer>.  We never see one-dimensional
+     * primitive arrays (e.g. int[]) here because they use the identity
+     * converter and are registered as such in the static initializer.
+     */
+    private MXBeanMapping
+        makeArrayOrCollectionMapping(Type collectionType, Type elementType,
+                                     MXBeanMappingFactory factory)
+            throws OpenDataException {
+
+        final MXBeanMapping elementMapping = factory.mappingForType(elementType, factory);
+        final OpenType<?> elementOpenType = elementMapping.getOpenType();
+        final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
+        final Class<?> elementOpenClass = elementMapping.getOpenClass();
+
+        final Class<?> openArrayClass;
+        final String openArrayClassName;
+        if (elementOpenClass.isArray())
+            openArrayClassName = "[" + elementOpenClass.getName();
+        else
+            openArrayClassName = "[L" + elementOpenClass.getName() + ";";
+        try {
+            openArrayClass = Class.forName(openArrayClassName);
+        } catch (ClassNotFoundException e) {
+            throw openDataException("Cannot obtain array class", e);
+        }
+
+        if (collectionType instanceof ParameterizedType) {
+            return new CollectionMapping(collectionType,
+                                         openType, openArrayClass,
+                                         elementMapping);
+        } else {
+            if (isIdentity(elementMapping)) {
+                return new IdentityMapping(collectionType,
+                                           openType);
+            } else {
+                return new ArrayMapping(collectionType,
+                                          openType,
+                                          openArrayClass,
+                                          elementMapping);
+            }
+        }
+    }
+
+    private static final String[] keyArray = {"key"};
+    private static final String[] keyValueArray = {"key", "value"};
+
+    private MXBeanMapping
+        makeTabularMapping(Type objType, boolean sortedMap,
+                           Type keyType, Type valueType,
+                           MXBeanMappingFactory factory)
+            throws OpenDataException {
+
+        final String objTypeName = objType.toString();
+        final MXBeanMapping keyMapping = factory.mappingForType(keyType, factory);
+        final MXBeanMapping valueMapping = factory.mappingForType(valueType, factory);
+        final OpenType keyOpenType = keyMapping.getOpenType();
+        final OpenType valueOpenType = valueMapping.getOpenType();
+        final CompositeType rowType =
+            new CompositeType(objTypeName,
+                              objTypeName,
+                              keyValueArray,
+                              keyValueArray,
+                              new OpenType[] {keyOpenType, valueOpenType});
+        final TabularType tabularType =
+            new TabularType(objTypeName, objTypeName, rowType, keyArray);
+        return new TabularMapping(objType, sortedMap, tabularType,
+                                    keyMapping, valueMapping);
+    }
+
+    /* We know how to translate List<E>, Set<E>, SortedSet<E>,
+       Map<K,V>, SortedMap<K,V>, and that's it.  We don't accept
+       subtypes of those because we wouldn't know how to deserialize
+       them.  We don't accept Queue<E> because it is unlikely people
+       would use that as a parameter or return type in an MBean.  */
+    private MXBeanMapping
+            makeParameterizedTypeMapping(ParameterizedType objType,
+                                         MXBeanMappingFactory factory)
+            throws OpenDataException {
+
+        final Type rawType = objType.getRawType();
+
+        if (rawType instanceof Class) {
+            Class c = (Class<?>) rawType;
+            if (c == List.class || c == Set.class || c == SortedSet.class) {
+                Type[] actuals = objType.getActualTypeArguments();
+                assert(actuals.length == 1);
+                if (c == SortedSet.class)
+                    mustBeComparable(c, actuals[0]);
+                return makeArrayOrCollectionMapping(objType, actuals[0], factory);
+            } else {
+                boolean sortedMap = (c == SortedMap.class);
+                if (c == Map.class || sortedMap) {
+                    Type[] actuals = objType.getActualTypeArguments();
+                    assert(actuals.length == 2);
+                    if (sortedMap)
+                        mustBeComparable(c, actuals[0]);
+                    return makeTabularMapping(objType, sortedMap,
+                            actuals[0], actuals[1], factory);
+                }
+            }
+        }
+        throw new OpenDataException("Cannot convert type: " + objType);
+    }
+
+    private static MXBeanMapping makeMXBeanRefMapping(Type t)
+            throws OpenDataException {
+        return new MXBeanRefMapping(t);
+    }
+
+    private MXBeanMapping makeCompositeMapping(Class c,
+                                               MXBeanMappingFactory factory)
+            throws OpenDataException {
+
+        // For historical reasons GcInfo implements CompositeData but we
+        // shouldn't count its CompositeData.getCompositeType() field as
+        // an item in the computed CompositeType.
+        final boolean gcInfoHack =
+            (c.getName().equals("com.sun.management.GcInfo") &&
+                c.getClassLoader() == null);
+
+        final List<Method> methods =
+                MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
+        final SortedMap<String,Method> getterMap = newSortedMap();
+
+        /* Select public methods that look like "T getX()" or "boolean
+           isX()", where T is not void and X is not the empty
+           string.  Exclude "Class getClass()" inherited from Object.  */
+        for (Method method : methods) {
+            final String propertyName = propertyName(method);
+
+            if (propertyName == null)
+                continue;
+            if (gcInfoHack && propertyName.equals("CompositeType"))
+                continue;
+
+            Method old =
+                getterMap.put(decapitalize(propertyName),
+                            method);
+            if (old != null) {
+                final String msg =
+                    "Class " + c.getName() + " has method name clash: " +
+                    old.getName() + ", " + method.getName();
+                throw new OpenDataException(msg);
+            }
+        }
+
+        final int nitems = getterMap.size();
+
+        if (nitems == 0) {
+            throw new OpenDataException("Can't map " + c.getName() +
+                                        " to an open data type");
+        }
+
+        final Method[] getters = new Method[nitems];
+        final String[] itemNames = new String[nitems];
+        final OpenType[] openTypes = new OpenType[nitems];
+        int i = 0;
+        for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
+            itemNames[i] = entry.getKey();
+            final Method getter = entry.getValue();
+            getters[i] = getter;
+            final Type retType = getter.getGenericReturnType();
+            openTypes[i] = factory.mappingForType(retType, factory).getOpenType();
+            i++;
+        }
+
+        CompositeType compositeType =
+            new CompositeType(c.getName(),
+                              c.getName(),
+                              itemNames, // field names
+                              itemNames, // field descriptions
+                              openTypes);
+
+        return new CompositeMapping(c,
+                                    compositeType,
+                                    itemNames,
+                                    getters,
+                                    factory);
+    }
+
+    /* Converter for classes where the open data is identical to the
+       original data.  This is true for any of the SimpleType types,
+       and for an any-dimension array of those.  It is also true for
+       primitive types as of JMX 1.3, since an int[]
+       can be directly represented by an ArrayType, and an int needs no mapping
+       because reflection takes care of it.  */
+    private static final class IdentityMapping extends NonNullMXBeanMapping {
+        IdentityMapping(Type targetType, OpenType openType) {
+            super(targetType, openType);
+        }
+
+        boolean isIdentity() {
+            return true;
+        }
+
+        @Override
+        Object fromNonNullOpenValue(Object openValue)
+        throws InvalidObjectException {
+            return openValue;
+        }
+
+        @Override
+        Object toNonNullOpenValue(Object javaValue) throws OpenDataException {
+            return javaValue;
+        }
+    }
+
+    private static final class EnumMapping<T extends Enum<T>>
+            extends NonNullMXBeanMapping {
+
+        EnumMapping(Class<T> enumClass) {
+            super(enumClass, SimpleType.STRING);
+            this.enumClass = enumClass;
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object value) {
+            return ((Enum) value).name();
+        }
+
+        @Override
+        final T fromNonNullOpenValue(Object value)
+                throws InvalidObjectException {
+            try {
+                return Enum.valueOf(enumClass, (String) value);
+            } catch (Exception e) {
+                throw invalidObjectException("Cannot convert to enum: " +
+                                             value, e);
+            }
+        }
+
+        private final Class<T> enumClass;
+    }
+
+    private static final class ArrayMapping extends NonNullMXBeanMapping {
+        ArrayMapping(Type targetType,
+                     ArrayType openArrayType, Class openArrayClass,
+                     MXBeanMapping elementMapping) {
+            super(targetType, openArrayType);
+            this.elementMapping = elementMapping;
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object value)
+                throws OpenDataException {
+            Object[] valueArray = (Object[]) value;
+            final int len = valueArray.length;
+            final Object[] openArray = (Object[])
+                Array.newInstance(getOpenClass().getComponentType(), len);
+            for (int i = 0; i < len; i++)
+                openArray[i] = elementMapping.toOpenValue(valueArray[i]);
+            return openArray;
+        }
+
+        @Override
+        final Object fromNonNullOpenValue(Object openValue)
+                throws InvalidObjectException {
+            final Object[] openArray = (Object[]) openValue;
+            final Type javaType = getJavaType();
+            final Object[] valueArray;
+            final Type componentType;
+            if (javaType instanceof GenericArrayType) {
+                componentType =
+                    ((GenericArrayType) javaType).getGenericComponentType();
+            } else if (javaType instanceof Class &&
+                       ((Class<?>) javaType).isArray()) {
+                componentType = ((Class<?>) javaType).getComponentType();
+            } else {
+                throw new IllegalArgumentException("Not an array: " +
+                                                   javaType);
+            }
+            valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
+                                                      openArray.length);
+            for (int i = 0; i < openArray.length; i++)
+                valueArray[i] = elementMapping.fromOpenValue(openArray[i]);
+            return valueArray;
+        }
+
+        public void checkReconstructible() throws InvalidObjectException {
+            elementMapping.checkReconstructible();
+        }
+
+        /**
+         * DefaultMXBeanMappingFactory for the elements of this array.  If this is an
+         *          array of arrays, the converter converts the second-level arrays,
+         *          not the deepest elements.
+         */
+        private final MXBeanMapping elementMapping;
+    }
+
+    private static final class CollectionMapping extends NonNullMXBeanMapping {
+        CollectionMapping(Type targetType,
+                          ArrayType openArrayType,
+                          Class openArrayClass,
+                          MXBeanMapping elementMapping) {
+            super(targetType, openArrayType);
+            this.elementMapping = elementMapping;
+
+            /* Determine the concrete class to be used when converting
+               back to this Java type.  We convert all Lists to ArrayList
+               and all Sets to TreeSet.  (TreeSet because it is a SortedSet,
+               so works for both Set and SortedSet.)  */
+            Type raw = ((ParameterizedType) targetType).getRawType();
+            Class c = (Class<?>) raw;
+            if (c == List.class)
+                collectionClass = ArrayList.class;
+            else if (c == Set.class)
+                collectionClass = HashSet.class;
+            else if (c == SortedSet.class)
+                collectionClass = TreeSet.class;
+            else { // can't happen
+                assert(false);
+                collectionClass = null;
+            }
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object value)
+                throws OpenDataException {
+            final Collection valueCollection = (Collection) value;
+            if (valueCollection instanceof SortedSet) {
+                Comparator comparator =
+                    ((SortedSet) valueCollection).comparator();
+                if (comparator != null) {
+                    final String msg =
+                        "Cannot convert SortedSet with non-null comparator: " +
+                        comparator;
+                    throw new OpenDataException(msg);
+                }
+            }
+            final Object[] openArray = (Object[])
+                Array.newInstance(getOpenClass().getComponentType(),
+                                  valueCollection.size());
+            int i = 0;
+            for (Object o : valueCollection)
+                openArray[i++] = elementMapping.toOpenValue(o);
+            return openArray;
+        }
+
+        @Override
+        final Object fromNonNullOpenValue(Object openValue)
+                throws InvalidObjectException {
+            final Object[] openArray = (Object[]) openValue;
+            final Collection<Object> valueCollection;
+            try {
+                valueCollection = cast(collectionClass.newInstance());
+            } catch (Exception e) {
+                throw invalidObjectException("Cannot create collection", e);
+            }
+            for (Object o : openArray) {
+                Object value = elementMapping.fromOpenValue(o);
+                if (!valueCollection.add(value)) {
+                    final String msg =
+                        "Could not add " + o + " to " +
+                        collectionClass.getName() +
+                        " (duplicate set element?)";
+                    throw new InvalidObjectException(msg);
+                }
+            }
+            return valueCollection;
+        }
+
+        public void checkReconstructible() throws InvalidObjectException {
+            elementMapping.checkReconstructible();
+        }
+
+        private final Class<? extends Collection> collectionClass;
+        private final MXBeanMapping elementMapping;
+    }
+
+    private static final class MXBeanRefMapping extends NonNullMXBeanMapping {
+        MXBeanRefMapping(Type intf) {
+            super(intf, SimpleType.OBJECTNAME);
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object javaValue)
+                throws OpenDataException {
+            MXBeanLookup lookup = lookupNotNull(OpenDataException.class);
+            ObjectName name = lookup.mxbeanToObjectName(javaValue);
+            if (name == null)
+                throw new OpenDataException("No name for object: " + javaValue);
+            return name;
+        }
+
+        @Override
+        final Object fromNonNullOpenValue(Object openValue)
+                throws InvalidObjectException {
+            MXBeanLookup lookup = lookupNotNull(InvalidObjectException.class);
+            ObjectName name = (ObjectName) openValue;
+            Object mxbean =
+                lookup.objectNameToMXBean(name, (Class<?>) getJavaType());
+            if (mxbean == null) {
+                final String msg =
+                    "No MXBean for name: " + name;
+                throw new InvalidObjectException(msg);
+            }
+            return mxbean;
+        }
+
+        private <T extends Exception> MXBeanLookup
+            lookupNotNull(Class<T> excClass)
+                throws T {
+            MXBeanLookup lookup = MXBeanLookup.getLookup();
+            if (lookup == null) {
+                final String msg =
+                    "Cannot convert MXBean interface in this context";
+                T exc;
+                try {
+                    Constructor<T> con = excClass.getConstructor(String.class);
+                    exc = con.newInstance(msg);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                throw exc;
+            }
+            return lookup;
+        }
+    }
+
+    private static final class TabularMapping extends NonNullMXBeanMapping {
+        TabularMapping(Type targetType,
+                       boolean sortedMap,
+                       TabularType tabularType,
+                       MXBeanMapping keyConverter,
+                       MXBeanMapping valueConverter) {
+            super(targetType, tabularType);
+            this.sortedMap = sortedMap;
+            this.keyMapping = keyConverter;
+            this.valueMapping = valueConverter;
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object value) throws OpenDataException {
+            final Map<Object, Object> valueMap = cast(value);
+            if (valueMap instanceof SortedMap) {
+                Comparator comparator = ((SortedMap) valueMap).comparator();
+                if (comparator != null) {
+                    final String msg =
+                        "Cannot convert SortedMap with non-null comparator: " +
+                        comparator;
+                    throw new OpenDataException(msg);
+                }
+            }
+            final TabularType tabularType = (TabularType) getOpenType();
+            final TabularData table = new TabularDataSupport(tabularType);
+            final CompositeType rowType = tabularType.getRowType();
+            for (Map.Entry entry : valueMap.entrySet()) {
+                final Object openKey = keyMapping.toOpenValue(entry.getKey());
+                final Object openValue = valueMapping.toOpenValue(entry.getValue());
+                final CompositeData row;
+                row =
+                    new CompositeDataSupport(rowType, keyValueArray,
+                                             new Object[] {openKey,
+                                                           openValue});
+                table.put(row);
+            }
+            return table;
+        }
+
+        @Override
+        final Object fromNonNullOpenValue(Object openValue)
+                throws InvalidObjectException {
+            final TabularData table = (TabularData) openValue;
+            final Collection<CompositeData> rows = cast(table.values());
+            final Map<Object, Object> valueMap =
+                sortedMap ? newSortedMap() : newMap();
+            for (CompositeData row : rows) {
+                final Object key =
+                    keyMapping.fromOpenValue(row.get("key"));
+                final Object value =
+                    valueMapping.fromOpenValue(row.get("value"));
+                if (valueMap.put(key, value) != null) {
+                    final String msg =
+                        "Duplicate entry in TabularData: key=" + key;
+                    throw new InvalidObjectException(msg);
+                }
+            }
+            return valueMap;
+        }
+
+        @Override
+        public void checkReconstructible() throws InvalidObjectException {
+            keyMapping.checkReconstructible();
+            valueMapping.checkReconstructible();
+        }
+
+        private final boolean sortedMap;
+        private final MXBeanMapping keyMapping;
+        private final MXBeanMapping valueMapping;
+    }
+
+    private final class CompositeMapping extends NonNullMXBeanMapping {
+        CompositeMapping(Class targetClass,
+                         CompositeType compositeType,
+                         String[] itemNames,
+                         Method[] getters,
+                         MXBeanMappingFactory factory) throws OpenDataException {
+            super(targetClass, compositeType);
+
+            assert(itemNames.length == getters.length);
+
+            this.itemNames = itemNames;
+            this.getters = getters;
+            this.getterMappings = new MXBeanMapping[getters.length];
+            for (int i = 0; i < getters.length; i++) {
+                Type retType = getters[i].getGenericReturnType();
+                getterMappings[i] = factory.mappingForType(retType, factory);
+            }
+        }
+
+        @Override
+        final Object toNonNullOpenValue(Object value)
+                throws OpenDataException {
+            CompositeType ct = (CompositeType) getOpenType();
+            if (value instanceof CompositeDataView)
+                return ((CompositeDataView) value).toCompositeData(ct);
+            if (value == null)
+                return null;
+
+            Object[] values = new Object[getters.length];
+            for (int i = 0; i < getters.length; i++) {
+                try {
+                    Object got = getters[i].invoke(value, (Object[]) null);
+                    values[i] = getterMappings[i].toOpenValue(got);
+                } catch (Exception e) {
+                    throw openDataException("Error calling getter for " +
+                                            itemNames[i] + ": " + e, e);
+                }
+            }
+            return new CompositeDataSupport(ct, itemNames, values);
+        }
+
+        /** Determine how to convert back from the CompositeData into
+            the original Java type.  For a type that is not reconstructible,
+            this method will fail every time, and will throw the right
+            exception. */
+        private synchronized void makeCompositeBuilder()
+                throws InvalidObjectException {
+            if (compositeBuilder != null)
+                return;
+
+            Class targetClass = (Class<?>) getJavaType();
+            /* In this 2D array, each subarray is a set of builders where
+               there is no point in consulting the ones after the first if
+               the first refuses.  */
+            CompositeBuilder[][] builders = {
+                {
+                    new CompositeBuilderViaFrom(targetClass, itemNames),
+                },
+                {
+                    new CompositeBuilderViaConstructor(targetClass, itemNames),
+                },
+                {
+                    new CompositeBuilderCheckGetters(targetClass, itemNames,
+                                                     getterMappings),
+                    new CompositeBuilderViaSetters(targetClass, itemNames),
+                    new CompositeBuilderViaProxy(targetClass, itemNames),
+                },
+            };
+            CompositeBuilder foundBuilder = null;
+            /* We try to make a meaningful exception message by
+               concatenating each Builder's explanation of why it
+               isn't applicable.  */
+            final StringBuilder whyNots = new StringBuilder();
+        find:
+            for (CompositeBuilder[] relatedBuilders : builders) {
+                for (int i = 0; i < relatedBuilders.length; i++) {
+                    CompositeBuilder builder = relatedBuilders[i];
+                    String whyNot = builder.applicable(getters);
+                    if (whyNot == null) {
+                        foundBuilder = builder;
+                        break find;
+                    }
+                    if (whyNot.length() > 0) {
+                        if (whyNots.length() > 0)
+                            whyNots.append("; ");
+                        whyNots.append(whyNot);
+                        if (i == 0)
+                           break; // skip other builders in this group
+                    }
+                }
+            }
+            if (foundBuilder == null) {
+                final String msg =
+                    "Do not know how to make a " + targetClass.getName() +
+                    " from a CompositeData: " + whyNots;
+                throw new InvalidObjectException(msg);
+            }
+            compositeBuilder = foundBuilder;
+        }
+
+        @Override
+        public void checkReconstructible() throws InvalidObjectException {
+            makeCompositeBuilder();
+        }
+
+        @Override
+        final Object fromNonNullOpenValue(Object value)
+                throws InvalidObjectException {
+            makeCompositeBuilder();
+            return compositeBuilder.fromCompositeData((CompositeData) value,
+                                                      itemNames,
+                                                      getterMappings);
+        }
+
+        private final String[] itemNames;
+        private final Method[] getters;
+        private final MXBeanMapping[] getterMappings;
+        private CompositeBuilder compositeBuilder;
+    }
+
+    /** Converts from a CompositeData to an instance of the targetClass.  */
+    private static abstract class CompositeBuilder {
+        CompositeBuilder(Class targetClass, String[] itemNames) {
+            this.targetClass = targetClass;
+            this.itemNames = itemNames;
+        }
+
+        Class<?> getTargetClass() {
+            return targetClass;
+        }
+
+        String[] getItemNames() {
+            return itemNames;
+        }
+
+        /** If the subclass is appropriate for targetClass, then the
+            method returns null.  If the subclass is not appropriate,
+            then the method returns an explanation of why not.  If the
+            subclass should be appropriate but there is a problem,
+            then the method throws InvalidObjectException.  */
+        abstract String applicable(Method[] getters)
+                throws InvalidObjectException;
+
+        abstract Object fromCompositeData(CompositeData cd,
+                                          String[] itemNames,
+                                          MXBeanMapping[] converters)
+                throws InvalidObjectException;
+
+        private final Class<?> targetClass;
+        private final String[] itemNames;
+    }
+
+    /** Builder for when the target class has a method "public static
+        from(CompositeData)".  */
+    private static final class CompositeBuilderViaFrom
+            extends CompositeBuilder {
+
+        CompositeBuilderViaFrom(Class targetClass, String[] itemNames) {
+            super(targetClass, itemNames);
+        }
+
+        String applicable(Method[] getters) throws InvalidObjectException {
+            // See if it has a method "T from(CompositeData)"
+            // as is conventional for a CompositeDataView
+            Class<?> targetClass = getTargetClass();
+            try {
+                Method fromMethod =
+                    targetClass.getMethod("from",
+                                          new Class[] {CompositeData.class});
+
+                if (!Modifier.isStatic(fromMethod.getModifiers())) {
+                    final String msg =
+                        "Method from(CompositeData) is not static";
+                    throw new InvalidObjectException(msg);
+                }
+
+                if (fromMethod.getReturnType() != getTargetClass()) {
+                    final String msg =
+                        "Method from(CompositeData) returns " +
+                        fromMethod.getReturnType().getName() +
+                        " not " + targetClass.getName();
+                    throw new InvalidObjectException(msg);
+                }
+
+                this.fromMethod = fromMethod;
+                return null; // success!
+            } catch (InvalidObjectException e) {
+                throw e;
+            } catch (Exception e) {
+                // OK: it doesn't have the method
+                return "no method from(CompositeData)";
+            }
+        }
+
+        final Object fromCompositeData(CompositeData cd,
+                                       String[] itemNames,
+                                       MXBeanMapping[] converters)
+                throws InvalidObjectException {
+            try {
+                return fromMethod.invoke(null, cd);
+            } catch (Exception e) {
+                final String msg = "Failed to invoke from(CompositeData)";
+                throw invalidObjectException(msg, e);
+            }
+        }
+
+        private Method fromMethod;
+    }
+
+    /** This builder never actually returns success.  It simply serves
+        to check whether the other builders in the same group have any
+        chance of success.  If any getter in the targetClass returns
+        a type that we don't know how to reconstruct, then we will
+        not be able to make a builder, and there is no point in repeating
+        the error about the problematic getter as many times as there are
+        candidate builders.  Instead, the "applicable" method will return
+        an explanatory string, and the other builders will be skipped.
+        If all the getters are OK, then the "applicable" method will return
+        an empty string and the other builders will be tried.  */
+    private static class CompositeBuilderCheckGetters extends CompositeBuilder {
+        CompositeBuilderCheckGetters(Class targetClass, String[] itemNames,
+                                     MXBeanMapping[] getterConverters) {
+            super(targetClass, itemNames);
+            this.getterConverters = getterConverters;
+        }
+
+        String applicable(Method[] getters) {
+            for (int i = 0; i < getters.length; i++) {
+                try {
+                    getterConverters[i].checkReconstructible();
+                } catch (InvalidObjectException e) {
+                    return "method " + getters[i].getName() + " returns type " +
+                        "that cannot be mapped back from OpenData";
+                }
+            }
+            return "";
+        }
+
+        final Object fromCompositeData(CompositeData cd,
+                                       String[] itemNames,
+                                       MXBeanMapping[] converters) {
+            throw new Error();
+        }
+
+        private final MXBeanMapping[] getterConverters;
+    }
+
+    /** Builder for when the target class has a setter for every getter. */
+    private static class CompositeBuilderViaSetters extends CompositeBuilder {
+
+        CompositeBuilderViaSetters(Class<?> targetClass, String[] itemNames) {
+            super(targetClass, itemNames);
+        }
+
+        String applicable(Method[] getters) {
+            try {
+                Constructor<?> c = getTargetClass().getConstructor();
+            } catch (Exception e) {
+                return "does not have a public no-arg constructor";
+            }
+
+            Method[] setters = new Method[getters.length];
+            for (int i = 0; i < getters.length; i++) {
+                Method getter = getters[i];
+                Class returnType = getter.getReturnType();
+                String name = propertyName(getter);
+                String setterName = "set" + name;
+                Method setter;
+                try {
+                    setter = getTargetClass().getMethod(setterName, returnType);
+                    if (setter.getReturnType() != void.class)
+                        throw new Exception();
+                } catch (Exception e) {
+                    return "not all getters have corresponding setters " +
+                           "(" + getter + ")";
+                }
+                setters[i] = setter;
+            }
+            this.setters = setters;
+            return null;
+        }
+
+        Object fromCompositeData(CompositeData cd,
+                                 String[] itemNames,
+                                 MXBeanMapping[] converters)
+                throws InvalidObjectException {
+            Object o;
+            try {
+                o = getTargetClass().newInstance();
+                for (int i = 0; i < itemNames.length; i++) {
+                    if (cd.containsKey(itemNames[i])) {
+                        Object openItem = cd.get(itemNames[i]);
+                        Object javaItem =
+                            converters[i].fromOpenValue(openItem);
+                        setters[i].invoke(o, javaItem);
+                    }
+                }
+            } catch (Exception e) {
+                throw invalidObjectException(e);
+            }
+            return o;
+        }
+
+        private Method[] setters;
+    }
+
+    /** Builder for when the target class has a constructor that is
+        annotated with @ConstructorProperties so we can see the correspondence
+        to getters.  */
+    private static final class CompositeBuilderViaConstructor
+            extends CompositeBuilder {
+
+        CompositeBuilderViaConstructor(Class targetClass, String[] itemNames) {
+            super(targetClass, itemNames);
+        }
+
+        String applicable(Method[] getters) throws InvalidObjectException {
+
+            final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
+
+            Class targetClass = getTargetClass();
+            Constructor[] constrs = targetClass.getConstructors();
+
+            // Applicable if and only if there are any annotated constructors
+            List<Constructor> annotatedConstrList = newList();
+            for (Constructor<?> constr : constrs) {
+                if (Modifier.isPublic(constr.getModifiers())
+                        && constr.getAnnotation(propertyNamesClass) != null)
+                    annotatedConstrList.add(constr);
+            }
+
+            if (annotatedConstrList.isEmpty())
+                return "no constructor has @ConstructorProperties annotation";
+
+            annotatedConstructors = newList();
+
+            // Now check that all the annotated constructors are valid
+            // and throw an exception if not.
+
+            // First link the itemNames to their getter indexes.
+            Map<String, Integer> getterMap = newMap();
+            String[] itemNames = getItemNames();
+            for (int i = 0; i < itemNames.length; i++)
+                getterMap.put(itemNames[i], i);
+
+            // Run through the constructors making the checks in the spec.
+            // For each constructor, remember the correspondence between its
+            // parameters and the items.  The int[] for a constructor says
+            // what parameter index should get what item.  For example,
+            // if element 0 is 2 then that means that item 0 in the
+            // CompositeData goes to parameter 2 of the constructor.  If an
+            // element is -1, that item isn't given to the constructor.
+            // Also remember the set of properties in that constructor
+            // so we can test unambiguity.
+            Set<BitSet> getterIndexSets = newSet();
+            for (Constructor constr : annotatedConstrList) {
+                String[] propertyNames =
+                    constr.getAnnotation(propertyNamesClass).value();
+
+                Type[] paramTypes = constr.getGenericParameterTypes();
+                if (paramTypes.length != propertyNames.length) {
+                    final String msg =
+                        "Number of constructor params does not match " +
+                        "@ConstructorProperties annotation: " + constr;
+                    throw new InvalidObjectException(msg);
+                }
+
+                int[] paramIndexes = new int[getters.length];
+                for (int i = 0; i < getters.length; i++)
+                    paramIndexes[i] = -1;
+                BitSet present = new BitSet();
+
+                for (int i = 0; i < propertyNames.length; i++) {
+                    String propertyName = propertyNames[i];
+                    if (!getterMap.containsKey(propertyName)) {
+                        final String msg =
+                            "@ConstructorProperties includes name " + propertyName +
+                            " which does not correspond to a property: " +
+                            constr;
+                        throw new InvalidObjectException(msg);
+                    }
+                    int getterIndex = getterMap.get(propertyName);
+                    paramIndexes[getterIndex] = i;
+                    if (present.get(getterIndex)) {
+                        final String msg =
+                            "@ConstructorProperties contains property " +
+                            propertyName + " more than once: " + constr;
+                        throw new InvalidObjectException(msg);
+                    }
+                    present.set(getterIndex);
+                    Method getter = getters[getterIndex];
+                    Type propertyType = getter.getGenericReturnType();
+                    if (!propertyType.equals(paramTypes[i])) {
+                        final String msg =
+                            "@ConstructorProperties gives property " + propertyName +
+                            " of type " + propertyType + " for parameter " +
+                            " of type " + paramTypes[i] + ": " + constr;
+                        throw new InvalidObjectException(msg);
+                    }
+                }
+
+                if (!getterIndexSets.add(present)) {
+                    final String msg =
+                        "More than one constructor has a @ConstructorProperties " +
+                        "annotation with this set of names: " +
+                        Arrays.toString(propertyNames);
+                    throw new InvalidObjectException(msg);
+                }
+
+                Constr c = new Constr(constr, paramIndexes, present);
+                annotatedConstructors.add(c);
+            }
+
+            /* Check that no possible set of items could lead to an ambiguous
+             * choice of constructor (spec requires this check).  For any
+             * pair of constructors, their union would be the minimal
+             * ambiguous set.  If this set itself corresponds to a constructor,
+             * there is no ambiguity for that pair.  In the usual case, one
+             * of the constructors is a superset of the other so the union is
+             * just the bigger constuctor.
+             *
+             * The algorithm here is quadratic in the number of constructors
+             * with a @ConstructorProperties annotation.  Typically this corresponds
+             * to the number of versions of the class there have been.  Ten
+             * would already be a large number, so although it's probably
+             * possible to have an O(n lg n) algorithm it wouldn't be
+             * worth the complexity.
+             */
+            for (BitSet a : getterIndexSets) {
+                boolean seen = false;
+                for (BitSet b : getterIndexSets) {
+                    if (a == b)
+                        seen = true;
+                    else if (seen) {
+                        BitSet u = new BitSet();
+                        u.or(a); u.or(b);
+                        if (!getterIndexSets.contains(u)) {
+                            Set<String> names = new TreeSet<String>();
+                            for (int i = u.nextSetBit(0); i >= 0;
+                                 i = u.nextSetBit(i+1))
+                                names.add(itemNames[i]);
+                            final String msg =
+                                "Constructors with @ConstructorProperties annotation " +
+                                " would be ambiguous for these items: " +
+                                names;
+                            throw new InvalidObjectException(msg);
+                        }
+                    }
+                }
+            }
+
+            return null; // success!
+        }
+
+        final Object fromCompositeData(CompositeData cd,
+                                       String[] itemNames,
+                                       MXBeanMapping[] mappings)
+                throws InvalidObjectException {
+            // The CompositeData might come from an earlier version where
+            // not all the items were present.  We look for a constructor
+            // that accepts just the items that are present.  Because of
+            // the ambiguity check in applicable(), we know there must be
+            // at most one maximally applicable constructor.
+            CompositeType ct = cd.getCompositeType();
+            BitSet present = new BitSet();
+            for (int i = 0; i < itemNames.length; i++) {
+                if (ct.getType(itemNames[i]) != null)
+                    present.set(i);
+            }
+
+            Constr max = null;
+            for (Constr constr : annotatedConstructors) {
+                if (subset(constr.presentParams, present) &&
+                        (max == null ||
+                         subset(max.presentParams, constr.presentParams)))
+                    max = constr;
+            }
+
+            if (max == null) {
+                final String msg =
+                    "No constructor has a @ConstructorProperties for this set of " +
+                    "items: " + ct.keySet();
+                throw new InvalidObjectException(msg);
+            }
+
+            Object[] params = new Object[max.presentParams.cardinality()];
+            for (int i = 0; i < itemNames.length; i++) {
+                if (!max.presentParams.get(i))
+                    continue;
+                Object openItem = cd.get(itemNames[i]);
+                Object javaItem = mappings[i].fromOpenValue(openItem);
+                int index = max.paramIndexes[i];
+                if (index >= 0)
+                    params[index] = javaItem;
+            }
+
+            try {
+                return max.constructor.newInstance(params);
+            } catch (Exception e) {
+                final String msg =
+                    "Exception constructing " + getTargetClass().getName();
+                throw invalidObjectException(msg, e);
+            }
+        }
+
+        private static boolean subset(BitSet sub, BitSet sup) {
+            BitSet subcopy = (BitSet) sub.clone();
+            subcopy.andNot(sup);
+            return subcopy.isEmpty();
+        }
+
+        private static class Constr {
+            final Constructor constructor;
+            final int[] paramIndexes;
+            final BitSet presentParams;
+            Constr(Constructor constructor, int[] paramIndexes,
+                   BitSet presentParams) {
+                this.constructor = constructor;
+                this.paramIndexes = paramIndexes;
+                this.presentParams = presentParams;
+            }
+        }
+
+        private List<Constr> annotatedConstructors;
+    }
+
+    /** Builder for when the target class is an interface and contains
+        no methods other than getters.  Then we can make an instance
+        using a dynamic proxy that forwards the getters to the source
+        CompositeData.  */
+    private static final class CompositeBuilderViaProxy
+            extends CompositeBuilder {
+
+        CompositeBuilderViaProxy(Class targetClass, String[] itemNames) {
+            super(targetClass, itemNames);
+        }
+
+        String applicable(Method[] getters) {
+            Class targetClass = getTargetClass();
+            if (!targetClass.isInterface())
+                return "not an interface";
+            Set<Method> methods =
+                newSet(Arrays.asList(targetClass.getMethods()));
+            methods.removeAll(Arrays.asList(getters));
+            /* If the interface has any methods left over, they better be
+             * public methods that are already present in java.lang.Object.
+             */
+            String bad = null;
+            for (Method m : methods) {
+                String mname = m.getName();
+                Class[] mparams = m.getParameterTypes();
+                try {
+                    Method om = Object.class.getMethod(mname, mparams);
+                    if (!Modifier.isPublic(om.getModifiers()))
+                        bad = mname;
+                } catch (NoSuchMethodException e) {
+                    bad = mname;
+                }
+                /* We don't catch SecurityException since it shouldn't
+                 * happen for a method in Object and if it does we would
+                 * like to know about it rather than mysteriously complaining.
+                 */
+            }
+            if (bad != null)
+                return "contains methods other than getters (" + bad + ")";
+            return null; // success!
+        }
+
+        final Object fromCompositeData(CompositeData cd,
+                                       String[] itemNames,
+                                       MXBeanMapping[] converters) {
+            final Class targetClass = getTargetClass();
+            return
+                Proxy.newProxyInstance(targetClass.getClassLoader(),
+                                       new Class[] {targetClass},
+                                       new CompositeDataInvocationHandler(cd));
+        }
+    }
+
+    static InvalidObjectException invalidObjectException(String msg,
+                                                         Throwable cause) {
+        return EnvHelp.initCause(new InvalidObjectException(msg), cause);
+    }
+
+    static InvalidObjectException invalidObjectException(Throwable cause) {
+        return invalidObjectException(cause.getMessage(), cause);
+    }
+
+    static OpenDataException openDataException(String msg, Throwable cause) {
+        return EnvHelp.initCause(new OpenDataException(msg), cause);
+    }
+
+    static OpenDataException openDataException(Throwable cause) {
+        return openDataException(cause.getMessage(), cause);
+    }
+
+    static void mustBeComparable(Class collection, Type element)
+            throws OpenDataException {
+        if (!(element instanceof Class)
+            || !Comparable.class.isAssignableFrom((Class<?>) element)) {
+            final String msg =
+                "Parameter class " + element + " of " +
+                collection.getName() + " does not implement " +
+                Comparable.class.getName();
+            throw new OpenDataException(msg);
+        }
+    }
+
+    /**
+     * Utility method to take a string and convert it to normal Java variable
+     * name capitalization.  This normally means converting the first
+     * character from upper case to lower case, but in the (unusual) special
+     * case when there is more than one character and both the first and
+     * second characters are upper case, we leave it alone.
+     * <p>
+     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
+     * as "URL".
+     *
+     * @param  name The string to be decapitalized.
+     * @return  The decapitalized version of the string.
+     */
+    public static String decapitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        int offset1 = Character.offsetByCodePoints(name, 0, 1);
+        // Should be name.offsetByCodePoints but 6242664 makes this fail
+        if (offset1 < name.length() &&
+                Character.isUpperCase(name.codePointAt(offset1)))
+            return name;
+        return name.substring(0, offset1).toLowerCase() +
+               name.substring(offset1);
+    }
+
+    /**
+     * Reverse operation for java.beans.Introspector.decapitalize.  For any s,
+     * capitalize(decapitalize(s)).equals(s).  The reverse is not true:
+     * e.g. capitalize("uRL") produces "URL" which is unchanged by
+     * decapitalize.
+     */
+    static String capitalize(String name) {
+        if (name == null || name.length() == 0)
+            return name;
+        int offset1 = name.offsetByCodePoints(0, 1);
+        return name.substring(0, offset1).toUpperCase() +
+               name.substring(offset1);
+    }
+
+    public static String propertyName(Method m) {
+        String rest = null;
+        String name = m.getName();
+        if (name.startsWith("get"))
+            rest = name.substring(3);
+        else if (name.startsWith("is") && m.getReturnType() == boolean.class)
+            rest = name.substring(2);
+        if (rest == null || rest.length() == 0
+            || m.getParameterTypes().length > 0
+            || m.getReturnType() == void.class
+            || name.equals("getClass"))
+            return null;
+        return rest;
+    }
+
+    private final static Map<Type, Type> inProgress = newIdentityHashMap();
+    // really an IdentityHashSet but that doesn't exist
+}
--- a/src/share/classes/com/sun/jmx/mbeanserver/Introspector.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/Introspector.java	Thu Jun 05 13:42:47 2008 +0200
@@ -42,7 +42,6 @@
 import javax.management.MBeanInfo;
 import javax.management.NotCompliantMBeanException;
 
-import com.sun.jmx.mbeanserver.Util;
 import com.sun.jmx.remote.util.EnvHelp;
 import java.beans.BeanInfo;
 import java.beans.PropertyDescriptor;
@@ -50,6 +49,7 @@
 import java.lang.reflect.InvocationTargetException;
 import javax.management.AttributeNotFoundException;
 import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * This class contains the methods for performing all the tests needed to verify
@@ -165,30 +165,34 @@
         throw new NotCompliantMBeanException(msg);
     }
 
-    public static DynamicMBean makeDynamicMBean(Object mbean)
-        throws NotCompliantMBeanException {
+    public static <T> DynamicMBean makeDynamicMBean(T mbean)
+    throws NotCompliantMBeanException {
+        if (mbean == null)
+            throw new NotCompliantMBeanException("Null MBean object");
         if (mbean instanceof DynamicMBean)
             return (DynamicMBean) mbean;
         final Class mbeanClass = mbean.getClass();
-        Class<?> c = null;
+        Class<? super T> c = null;
         try {
-            c = getStandardMBeanInterface(mbeanClass);
+            c = Util.cast(getStandardMBeanInterface(mbeanClass));
         } catch (NotCompliantMBeanException e) {
             // Ignore exception - we need to check whether
             // mbean is an MXBean first.
         }
         if (c != null)
-            return new StandardMBeanSupport(mbean, Util.<Class<Object>>cast(c));
+            return new StandardMBeanSupport(mbean, c);
 
         try {
-            c = getMXBeanInterface(mbeanClass);
+            c = Util.cast(getMXBeanInterface(mbeanClass));
         } catch (NotCompliantMBeanException e) {
             // Ignore exception - we cannot decide whether mbean was supposed
             // to be an MBean or an MXBean. We will call checkCompliance()
             // to generate the appropriate exception.
         }
-        if (c != null)
-            return new MXBeanSupport(mbean, Util.<Class<Object>>cast(c));
+        if (c != null) {
+            MXBeanMappingFactory factory = MXBeanMappingFactory.forInterface(c);
+            return new MXBeanSupport(mbean, c, factory);
+        }
         checkCompliance(mbeanClass);
         throw new NotCompliantMBeanException("Not compliant"); // not reached
     }
@@ -217,9 +221,10 @@
         return testCompliance(baseClass, null);
     }
 
-    public static void testComplianceMXBeanInterface(Class interfaceClass)
+    public static void testComplianceMXBeanInterface(Class interfaceClass,
+                                                     MXBeanMappingFactory factory)
             throws NotCompliantMBeanException {
-        MXBeanIntrospector.getInstance().getAnalyzer(interfaceClass);
+        MXBeanIntrospector.getInstance(factory).getAnalyzer(interfaceClass);
     }
 
     /**
@@ -325,6 +330,15 @@
         }
     }
 
+    public static <T> Class<? super T> getStandardOrMXBeanInterface(
+            Class<T> baseClass, boolean mxbean)
+    throws NotCompliantMBeanException {
+        if (mxbean)
+            return getMXBeanInterface(baseClass);
+        else
+            return getStandardMBeanInterface(baseClass);
+    }
+
     /*
      * ------------------------------------------
      *  PRIVATE METHODS
--- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanAnalyzer.java	Thu Jun 05 13:42:47 2008 +0200
@@ -29,13 +29,10 @@
 
 import java.lang.reflect.Method;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.management.NotCompliantMBeanException;
 
 /**
@@ -54,15 +51,15 @@
  */
 class MBeanAnalyzer<M> {
 
-    static interface MBeanVisitor<M> {
+    static interface MBeanVisitor<M, X extends Exception> {
         public void visitAttribute(String attributeName,
                 M getter,
-                M setter);
+                M setter) throws X;
         public void visitOperation(String operationName,
-                M operation);
+                M operation) throws X;
     }
 
-    void visit(MBeanVisitor<M> visitor) {
+    <X extends Exception> void visit(MBeanVisitor<M, X> visitor) throws X {
         // visit attributes
         for (Map.Entry<String, AttrMethods<M>> entry : attrMap.entrySet()) {
             String name = entry.getKey();
@@ -98,21 +95,21 @@
     // cached PerInterface object for an MBean interface means that
     // an analyzer will not be recreated for a second MBean using the
     // same interface.
-    static <M> MBeanAnalyzer<M> analyzer(Class<?> mbeanInterface,
+    static <M> MBeanAnalyzer<M> analyzer(Class<?> mbeanType,
             MBeanIntrospector<M> introspector)
             throws NotCompliantMBeanException {
-        return new MBeanAnalyzer<M>(mbeanInterface, introspector);
+        return new MBeanAnalyzer<M>(mbeanType, introspector);
     }
 
-    private MBeanAnalyzer(Class<?> mbeanInterface,
+    private MBeanAnalyzer(Class<?> mbeanType,
             MBeanIntrospector<M> introspector)
             throws NotCompliantMBeanException {
-        introspector.checkCompliance(mbeanInterface);
+        introspector.checkCompliance(mbeanType);
 
         try {
-            initMaps(mbeanInterface, introspector);
+            initMaps(mbeanType, introspector);
         } catch (Exception x) {
-            throw Introspector.throwException(mbeanInterface,x);
+            throw Introspector.throwException(mbeanType,x);
         }
     }
 
@@ -126,7 +123,8 @@
         /* Run through the methods to detect inconsistencies and to enable
            us to give getter and setter together to visitAttribute. */
         for (Method m : methods) {
-            String name = m.getName();
+            final String name = m.getName();
+            final int nParams = m.getParameterTypes().length;
 
             final M cm = introspector.mFrom(m);
 
@@ -137,7 +135,7 @@
             && m.getReturnType() == boolean.class)
                 attrName = name.substring(2);
 
-            if (attrName.length() != 0 && m.getParameterTypes().length == 0
+            if (attrName.length() != 0 && nParams == 0
                     && m.getReturnType() != void.class) {
                 // It's a getter
                 // Check we don't have both isX and getX
@@ -154,7 +152,7 @@
                 am.getter = cm;
                 attrMap.put(attrName, am);
             } else if (name.startsWith("set") && name.length() > 3
-                    && m.getParameterTypes().length == 1 &&
+                    && nParams == 1 &&
                     m.getReturnType() == void.class) {
                 // It's a setter
                 attrName = name.substring(3);
@@ -228,7 +226,11 @@
        but only the overriding one is of interest.  We return the methods
        in the same order they arrived in.  This isn't required by the spec
        but existing code may depend on it and users may be used to seeing
-       operations or attributes appear in a particular order.  */
+       operations or attributes appear in a particular order.
+
+       Because of the way this method works, if the same Method appears
+       more than once in the given List then it will be completely deleted!
+       So don't do that.  */
     static List<Method>
             eliminateCovariantMethods(List<Method> startMethods) {
         // We are assuming that you never have very many methods with the
@@ -243,7 +245,7 @@
             final Method m0 = sorted[i-1];
             final Method m1 = sorted[i];
 
-            // Methods that don't have the same name can't override each others
+            // Methods that don't have the same name can't override each other
             if (!m0.getName().equals(m1.getName())) continue;
 
             // Methods that have the same name and same signature override
@@ -251,7 +253,8 @@
             // due to the way we have sorted them in MethodOrder.
             if (Arrays.equals(m0.getParameterTypes(),
                     m1.getParameterTypes())) {
-                overridden.add(m0);
+                if (!overridden.add(m0))
+                    throw new RuntimeException("Internal error: duplicate Method");
             }
         }
 
--- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Thu Jun 05 13:42:47 2008 +0200
@@ -40,6 +40,7 @@
 
 import javax.management.Descriptor;
 import javax.management.ImmutableDescriptor;
+import javax.management.IntrospectionException;
 import javax.management.InvalidAttributeValueException;
 import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanConstructorInfo;
@@ -53,8 +54,9 @@
 
 /**
  * An introspector for MBeans of a certain type.  There is one instance
- * of this class for Standard MBeans and one for MXBeans, characterized
- * by the two concrete subclasses of this abstract class.
+ * of this class for Standard MBeans, and one for every MXBeanMappingFactory;
+ * these two cases correspond to the two concrete subclasses of this abstract
+ * class.
  *
  * @param <M> the representation of methods for this kind of MBean:
  * Method for Standard MBeans, ConvertingMethod for MXBeans.
@@ -119,7 +121,7 @@
      * MXBean interface is not valid if one of its parameters cannot be
      * mapped to an Open Type.
      */
-    abstract void checkMethod(M m) throws IllegalArgumentException;
+    abstract void checkMethod(M m);
 
     /**
      * Invoke the method with the given target and arguments.
@@ -149,7 +151,8 @@
      * may be null.
      */
     abstract MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
-            M getter, M setter);
+            M getter, M setter) throws IntrospectionException;
+
     /**
      * Construct an MBeanOperationInfo for the given operation based on
      * the M it was derived from.
@@ -170,6 +173,16 @@
      */
     abstract Descriptor getMBeanDescriptor(Class<?> resourceClass);
 
+    /**
+     * Get any additional Descriptor entries for this introspector instance.
+     * If there is a non-default MXBeanMappingFactory, it will appear in
+     * this Descriptor.
+     * @return Additional Descriptor entries, or an empty Descriptor if none.
+     */
+    Descriptor getSpecificMBeanDescriptor() {
+        return ImmutableDescriptor.EMPTY_DESCRIPTOR;
+    }
+
     void checkCompliance(Class<?> mbeanType) throws NotCompliantMBeanException {
         if (!mbeanType.isInterface()) {
             throw new NotCompliantMBeanException("Not an interface: " +
@@ -216,7 +229,7 @@
      * the MBeanInfo's Descriptor.
      */
     private MBeanInfo makeInterfaceMBeanInfo(Class<?> mbeanInterface,
-            MBeanAnalyzer<M> analyzer) {
+            MBeanAnalyzer<M> analyzer) throws IntrospectionException {
         final MBeanInfoMaker maker = new MBeanInfoMaker();
         analyzer.visit(maker);
         final String description =
@@ -317,11 +330,12 @@
     }
 
     /** A visitor that constructs the per-interface MBeanInfo. */
-    private class MBeanInfoMaker implements MBeanAnalyzer.MBeanVisitor<M> {
+    private class MBeanInfoMaker
+            implements MBeanAnalyzer.MBeanVisitor<M, IntrospectionException> {
 
         public void visitAttribute(String attributeName,
                 M getter,
-                M setter) {
+                M setter) throws IntrospectionException {
             MBeanAttributeInfo mbai =
                     getMBeanAttributeInfo(attributeName, getter, setter);
 
@@ -346,13 +360,14 @@
                     ops.toArray(new MBeanOperationInfo[0]);
             final String interfaceClassName =
                     "interfaceClassName=" + mbeanInterface.getName();
-            final Descriptor interfDescriptor =
+            final Descriptor classNameDescriptor =
                     new ImmutableDescriptor(interfaceClassName);
             final Descriptor mbeanDescriptor = getBasicMBeanDescriptor();
             final Descriptor annotatedDescriptor =
                     Introspector.descriptorForElement(mbeanInterface);
             final Descriptor descriptor =
-                    DescriptorCache.getInstance().union(interfDescriptor,
+                DescriptorCache.getInstance().union(
+                    classNameDescriptor,
                     mbeanDescriptor,
                     annotatedDescriptor);
 
@@ -388,20 +403,24 @@
      * Return the MBeanInfo for the given resource, based on the given
      * per-interface data.
      */
-    final MBeanInfo getMBeanInfo(Object resource, PerInterface<M> perInterface) {
+    final MBeanInfo getMBeanInfo(Object resource, PerInterface<M> perInterface)
+    throws NotCompliantMBeanException {
         MBeanInfo mbi =
                 getClassMBeanInfo(resource.getClass(), perInterface);
         MBeanNotificationInfo[] notifs = findNotifications(resource);
-        if (notifs == null || notifs.length == 0)
+        Descriptor d = getSpecificMBeanDescriptor();
+        boolean anyNotifs = (notifs != null && notifs.length > 0);
+        if (!anyNotifs && ImmutableDescriptor.EMPTY_DESCRIPTOR.equals(d))
             return mbi;
         else {
+            d = ImmutableDescriptor.union(d, mbi.getDescriptor());
             return new MBeanInfo(mbi.getClassName(),
                     mbi.getDescription(),
                     mbi.getAttributes(),
                     mbi.getConstructors(),
                     mbi.getOperations(),
                     notifs,
-                    mbi.getDescriptor());
+                    d);
         }
     }
 
@@ -446,7 +465,7 @@
             return null;
         MBeanNotificationInfo[] mbn =
                 ((NotificationBroadcaster) moi).getNotificationInfo();
-        if (mbn == null)
+        if (mbn == null || mbn.length == 0)
             return null;
         MBeanNotificationInfo[] result =
                 new MBeanNotificationInfo[mbn.length];
--- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java	Thu Jun 05 13:42:47 2008 +0200
@@ -38,6 +38,7 @@
 import javax.management.NotCompliantMBeanException;
 import javax.management.ObjectName;
 import javax.management.ReflectionException;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * Base class for MBeans.  There is one instance of this class for
@@ -121,24 +122,26 @@
 public abstract class MBeanSupport<M>
         implements DynamicMBean2, MBeanRegistration {
 
-    <T> MBeanSupport(T resource, Class<T> mbeanInterface)
+    <T> MBeanSupport(T resource, Class<T> mbeanInterfaceType,
+                     MXBeanMappingFactory mappingFactory)
             throws NotCompliantMBeanException {
-        if (mbeanInterface == null)
+        if (mbeanInterfaceType == null)
             throw new NotCompliantMBeanException("Null MBean interface");
-        if (!mbeanInterface.isInstance(resource)) {
+        if (!mbeanInterfaceType.isInstance(resource)) {
             final String msg =
                 "Resource class " + resource.getClass().getName() +
-                " is not an instance of " + mbeanInterface.getName();
+                " is not an instance of " + mbeanInterfaceType.getName();
             throw new NotCompliantMBeanException(msg);
         }
         this.resource = resource;
-        MBeanIntrospector<M> introspector = getMBeanIntrospector();
-        this.perInterface = introspector.getPerInterface(mbeanInterface);
+        MBeanIntrospector<M> introspector = getMBeanIntrospector(mappingFactory);
+        this.perInterface = introspector.getPerInterface(mbeanInterfaceType);
         this.mbeanInfo = introspector.getMBeanInfo(resource, perInterface);
     }
 
     /** Return the appropriate introspector for this type of MBean. */
-    abstract MBeanIntrospector<M> getMBeanIntrospector();
+    abstract MBeanIntrospector<M>
+            getMBeanIntrospector(MXBeanMappingFactory mappingFactory);
 
     /**
      * Return a cookie for this MBean.  This cookie will be passed to
@@ -162,9 +165,8 @@
     public final ObjectName preRegister(MBeanServer server, ObjectName name)
             throws Exception {
         if (resource instanceof MBeanRegistration)
-            return ((MBeanRegistration) resource).preRegister(server, name);
-        else
-            return name;
+            name = ((MBeanRegistration) resource).preRegister(server, name);
+        return name;
     }
 
     public final void preRegister2(MBeanServer server, ObjectName name)
--- a/src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java	Thu Jun 05 13:42:47 2008 +0200
@@ -25,18 +25,26 @@
 
 package com.sun.jmx.mbeanserver;
 
+import com.sun.jmx.mbeanserver.MBeanIntrospector.MBeanInfoMap;
+import com.sun.jmx.mbeanserver.MBeanIntrospector.PerInterfaceMap;
 import java.lang.annotation.Annotation;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.WeakHashMap;
 import javax.management.Descriptor;
 import javax.management.ImmutableDescriptor;
+import javax.management.IntrospectionException;
+import javax.management.JMX;
 import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanException;
 import javax.management.MBeanOperationInfo;
 import javax.management.MBeanParameterInfo;
 import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.MXBeanMappingFactory;
 import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
 import javax.management.openmbean.OpenMBeanOperationInfoSupport;
 import javax.management.openmbean.OpenMBeanParameterInfo;
@@ -49,10 +57,36 @@
  * @since 1.6
  */
 class MXBeanIntrospector extends MBeanIntrospector<ConvertingMethod> {
-    private static final MXBeanIntrospector instance = new MXBeanIntrospector();
+    /* We keep one MXBeanIntrospector per MXBeanMappingFactory, since the results
+     * of the introspection depend on the factory.  The MXBeanIntrospector
+     * has a reference back to the factory, so we wrap it in a WeakReference.
+     * It will be strongly referenced by any MXBeanSupport instances using it;
+     * if there are none then it is OK to gc it.
+     */
+    private static final
+            Map<MXBeanMappingFactory, WeakReference<MXBeanIntrospector>> map =
+            new WeakHashMap<MXBeanMappingFactory, WeakReference<MXBeanIntrospector>>();
 
-    static MXBeanIntrospector getInstance() {
-        return instance;
+    static MXBeanIntrospector getInstance(MXBeanMappingFactory factory) {
+        if (factory == null)
+            factory = MXBeanMappingFactory.DEFAULT;
+        synchronized (map) {
+            MXBeanIntrospector intro;
+            WeakReference<MXBeanIntrospector> wr = map.get(factory);
+            if (wr != null) {
+                intro = wr.get();
+                if (intro != null)
+                    return intro;
+            }
+            intro = new MXBeanIntrospector(factory);
+            wr = new WeakReference<MXBeanIntrospector>(intro);
+            map.put(factory, wr);
+            return intro;
+        }
+    }
+
+    private MXBeanIntrospector(MXBeanMappingFactory factory) {
+        this.mappingFactory = factory;
     }
 
     @Override
@@ -78,7 +112,7 @@
 
     @Override
     ConvertingMethod mFrom(Method m) {
-        return ConvertingMethod.from(m);
+        return ConvertingMethod.from(m, mappingFactory);
     }
 
     @Override
@@ -139,7 +173,8 @@
 
     @Override
     MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
-            ConvertingMethod getter, ConvertingMethod setter) {
+            ConvertingMethod getter, ConvertingMethod setter)
+            throws IntrospectionException {
 
         final boolean isReadable = (getter != null);
         final boolean isWritable = (setter != null);
@@ -222,14 +257,14 @@
                     Introspector.descriptorForAnnotations(annots[i]));
             final MBeanParameterInfo pi;
             if (canUseOpenInfo(originalType)) {
-                pi = new OpenMBeanParameterInfoSupport("p" + i,
+                pi = new OpenMBeanParameterInfoSupport(paramName,
                                                        paramDescription,
                                                        openType,
                                                        descriptor);
             } else {
                 openParameterTypes = false;
                 pi = new MBeanParameterInfo(
-                    "p" + i,
+                    paramName,
                     originalTypeString(originalType),
                     paramDescription,
                     descriptor);
@@ -291,6 +326,17 @@
         return ImmutableDescriptor.EMPTY_DESCRIPTOR;
     }
 
+    @Override
+    Descriptor getSpecificMBeanDescriptor() {
+        if (mappingFactory == MXBeanMappingFactory.DEFAULT)
+            return ImmutableDescriptor.EMPTY_DESCRIPTOR;
+        else {
+            return new ImmutableDescriptor(
+                    JMX.MXBEAN_MAPPING_FACTORY_CLASS_FIELD + "=" +
+                    mappingFactory.getClass().getName());
+        }
+    }
+
     private static Descriptor typeDescriptor(OpenType openType,
                                              Type originalType) {
         return new ImmutableDescriptor(
@@ -331,8 +377,10 @@
             return type.toString();
     }
 
-    private static final PerInterfaceMap<ConvertingMethod>
+    private final PerInterfaceMap<ConvertingMethod>
         perInterfaceMap = new PerInterfaceMap<ConvertingMethod>();
 
-    private static final MBeanInfoMap mbeanInfoMap = new MBeanInfoMap();
+    private final MBeanInfoMap mbeanInfoMap = new MBeanInfoMap();
+
+    private final MXBeanMappingFactory mappingFactory;
 }
--- a/src/share/classes/com/sun/jmx/mbeanserver/MXBeanLookup.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MXBeanLookup.java	Thu Jun 05 13:42:47 2008 +0200
@@ -25,15 +25,21 @@
 
 package com.sun.jmx.mbeanserver;
 
+import com.sun.jmx.remote.util.EnvHelp;
+import java.io.InvalidObjectException;
 import static com.sun.jmx.mbeanserver.Util.*;
 import java.util.Map;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import javax.management.InstanceAlreadyExistsException;
 import javax.management.JMX;
 import javax.management.MBeanServerConnection;
 import javax.management.MBeanServerInvocationHandler;
+import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
+import javax.management.openmbean.OpenDataException;
 
 /**
  * @since 1.6
@@ -80,71 +86,199 @@
  *
  * From the above, it is clear that the logic for getX on an MXBean is
  * the same as for setX on a proxy, and vice versa.
+ *
+ * The above describes the logic for "plain" MXBeanLookup, represented
+ * by MXBeanLookup.Plain.  When namespaces enter the picture, we see
+ * MXBeanLookup.Prefix.  Here, the idea is that the name of the ModuleMXBean
+ * might be a//m:m=m.  In this case, we don't accept a reference to
+ * an MXBean object, since that would require different namespaces to know
+ * each others' objects.  We only accept proxies.  Suppose you have a proxy
+ * for a//m:m=m, call it moduleProxy, and you call
+ * moduleProxy.setProduct(productProxy).  Then if productProxy is for
+ * a//p:p=p we should convert this to just p:p=p.  If productProxy is for
+ * a//b//p:p=p we should convert it to b//p:p=p.  Conversely, if getProduct
+ * returns an ObjectName like b//p:p=p then we should convert it into a proxy
+ * for a//b//p:p=p.
  */
-public class MXBeanLookup {
+public abstract class MXBeanLookup {
     private MXBeanLookup(MBeanServerConnection mbsc) {
         this.mbsc = mbsc;
     }
 
-    static MXBeanLookup lookupFor(MBeanServerConnection mbsc) {
-        synchronized (mbscToLookup) {
-            WeakReference<MXBeanLookup> weakLookup = mbscToLookup.get(mbsc);
-            MXBeanLookup lookup = (weakLookup == null) ? null : weakLookup.get();
-            if (lookup == null) {
-                lookup = new MXBeanLookup(mbsc);
-                mbscToLookup.put(mbsc, new WeakReference<MXBeanLookup>(lookup));
+    static MXBeanLookup lookupFor(MBeanServerConnection mbsc, String prefix) {
+        if (prefix == null)
+            return Plain.lookupFor(mbsc);
+        else
+            return new Prefix(mbsc, prefix);
+    }
+
+    abstract <T> T objectNameToMXBean(ObjectName name, Class<T> type)
+            throws InvalidObjectException;
+
+    abstract ObjectName mxbeanToObjectName(Object mxbean)
+            throws OpenDataException;
+
+    static class Plain extends MXBeanLookup {
+        Plain(MBeanServerConnection mbsc) {
+            super(mbsc);
+        }
+
+        static Plain lookupFor(MBeanServerConnection mbsc) {
+            synchronized (mbscToLookup) {
+                WeakReference<Plain> weakLookup = mbscToLookup.get(mbsc);
+                Plain lookup = (weakLookup == null) ? null : weakLookup.get();
+                if (lookup == null) {
+                    lookup = new Plain(mbsc);
+                    mbscToLookup.put(mbsc, new WeakReference<Plain>(lookup));
+                }
+                return lookup;
             }
-            return lookup;
+        }
+
+        @Override
+        synchronized <T> T objectNameToMXBean(ObjectName name, Class<T> type) {
+            WeakReference<Object> wr = objectNameToProxy.get(name);
+            if (wr != null) {
+                Object proxy = wr.get();
+                if (type.isInstance(proxy))
+                    return type.cast(proxy);
+            }
+            T proxy = JMX.newMXBeanProxy(mbsc, name, type);
+            objectNameToProxy.put(name, new WeakReference<Object>(proxy));
+            return proxy;
+        }
+
+        @Override
+        synchronized ObjectName mxbeanToObjectName(Object mxbean)
+        throws OpenDataException {
+            String wrong;
+            if (mxbean instanceof Proxy) {
+                InvocationHandler ih = Proxy.getInvocationHandler(mxbean);
+                if (ih instanceof MBeanServerInvocationHandler) {
+                    MBeanServerInvocationHandler mbsih =
+                            (MBeanServerInvocationHandler) ih;
+                    if (mbsih.getMBeanServerConnection().equals(mbsc))
+                        return mbsih.getObjectName();
+                    else
+                        wrong = "proxy for a different MBeanServer";
+                } else
+                    wrong = "not a JMX proxy";
+            } else {
+                ObjectName name = mxbeanToObjectName.get(mxbean);
+                if (name != null)
+                    return name;
+                wrong = "not an MXBean registered in this MBeanServer";
+            }
+            String s = (mxbean == null) ?
+                "null" : "object of type " + mxbean.getClass().getName();
+            throw new OpenDataException(
+                    "Could not convert " + s + " to an ObjectName: " + wrong);
+            // Message will be strange if mxbean is null but it is not
+            // supposed to be.
+        }
+
+        synchronized void addReference(ObjectName name, Object mxbean)
+        throws InstanceAlreadyExistsException {
+            ObjectName existing = mxbeanToObjectName.get(mxbean);
+            if (existing != null) {
+                String multiname = AccessController.doPrivileged(
+                        new GetPropertyAction("jmx.mxbean.multiname"));
+                if (!"true".equalsIgnoreCase(multiname)) {
+                    throw new InstanceAlreadyExistsException(
+                            "MXBean already registered with name " + existing);
+                }
+            }
+            mxbeanToObjectName.put(mxbean, name);
+        }
+
+        synchronized boolean removeReference(ObjectName name, Object mxbean) {
+            if (name.equals(mxbeanToObjectName.get(mxbean))) {
+                mxbeanToObjectName.remove(mxbean);
+                return true;
+            } else
+                return false;
+            /* removeReference can be called when the above condition fails,
+             * notably if you try to register the same MXBean twice.
+             */
+        }
+
+        private final WeakIdentityHashMap<Object, ObjectName>
+            mxbeanToObjectName = WeakIdentityHashMap.make();
+        private final Map<ObjectName, WeakReference<Object>>
+            objectNameToProxy = newMap();
+        private static WeakIdentityHashMap<MBeanServerConnection,
+                                           WeakReference<Plain>>
+            mbscToLookup = WeakIdentityHashMap.make();
+    }
+
+    private static class Prefix extends MXBeanLookup {
+        private final String prefix;
+
+        Prefix(MBeanServerConnection mbsc, String prefix) {
+            super(mbsc);
+            this.prefix = prefix;
+        }
+
+        @Override
+        <T> T objectNameToMXBean(ObjectName name, Class<T> type)
+        throws InvalidObjectException {
+            String domain = prefix + name.getDomain();
+            try {
+                name = switchDomain(domain, name);
+            } catch (MalformedObjectNameException e) {
+                throw EnvHelp.initCause(
+                        new InvalidObjectException(e.getMessage()), e);
+            }
+            return JMX.newMXBeanProxy(mbsc, name, type);
+        }
+
+        @Override
+        ObjectName mxbeanToObjectName(Object mxbean)
+        throws OpenDataException {
+            ObjectName name = proxyToObjectName(mxbean);
+            String domain = name.getDomain();
+            if (!domain.startsWith(prefix)) {
+                throw new OpenDataException(
+                        "Proxy's name does not start with " + prefix + ": " + name);
+            }
+            try {
+                name = switchDomain(domain.substring(prefix.length()), name);
+            } catch (MalformedObjectNameException e) {
+                throw EnvHelp.initCause(new OpenDataException(e.getMessage()), e);
+            }
+            return name;
         }
     }
 
-    synchronized <T> T objectNameToMXBean(ObjectName name, Class<T> type) {
-        WeakReference<Object> wr = objectNameToProxy.get(name);
-        if (wr != null) {
-            Object proxy = wr.get();
-            if (type.isInstance(proxy))
-                return type.cast(proxy);
+    ObjectName proxyToObjectName(Object proxy) {
+        InvocationHandler ih = Proxy.getInvocationHandler(proxy);
+        if (ih instanceof MBeanServerInvocationHandler) {
+            MBeanServerInvocationHandler mbsih =
+                    (MBeanServerInvocationHandler) ih;
+            if (mbsih.getMBeanServerConnection().equals(mbsc))
+                return mbsih.getObjectName();
         }
-        T proxy = JMX.newMXBeanProxy(mbsc, name, type);
-        objectNameToProxy.put(name, new WeakReference<Object>(proxy));
-        return proxy;
+        return null;
     }
 
-    synchronized ObjectName mxbeanToObjectName(Object mxbean) {
-        if (mxbean instanceof Proxy) {
-            InvocationHandler ih = Proxy.getInvocationHandler(mxbean);
-            if (ih instanceof MBeanServerInvocationHandler) {
-                MBeanServerInvocationHandler mbsih =
-                        (MBeanServerInvocationHandler) ih;
-                if (mbsih.getMBeanServerConnection().equals(mbsc))
-                    return mbsih.getObjectName();
-            }
-            return null;
-        } else
-            return mxbeanToObjectName.get(mxbean);
+    static MXBeanLookup getLookup() {
+        return currentLookup.get();
     }
 
-    synchronized void addReference(ObjectName name, Object mxbean) {
-        mxbeanToObjectName.put(mxbean, name);
+    static void setLookup(MXBeanLookup lookup) {
+        currentLookup.set(lookup);
     }
 
-    synchronized boolean removeReference(ObjectName name, Object mxbean) {
-        if (name.equals(mxbeanToObjectName.get(mxbean))) {
-            mxbeanToObjectName.remove(mxbean);
-            return true;
-        } else
-            return false;
-        /* removeReference can be called when the above condition fails,
-         * notably if you try to register the same MXBean twice.
-         */
+    // Method temporarily added until we have ObjectName.switchDomain in the
+    // public API.  Note that this method DOES NOT PRESERVE the order of
+    // keys in the ObjectName so it must not be used in the final release.
+    static ObjectName switchDomain(String domain, ObjectName name)
+            throws MalformedObjectNameException {
+        return new ObjectName(domain, name.getKeyPropertyList());
     }
 
-    private final MBeanServerConnection mbsc;
-    private final WeakIdentityHashMap<Object, ObjectName>
-        mxbeanToObjectName = WeakIdentityHashMap.make();
-    private final Map<ObjectName, WeakReference<Object>>
-        objectNameToProxy = newMap();
-    private static WeakIdentityHashMap<MBeanServerConnection,
-                                       WeakReference<MXBeanLookup>>
-        mbscToLookup = WeakIdentityHashMap.make();
+    private static final ThreadLocal<MXBeanLookup> currentLookup =
+            new ThreadLocal<MXBeanLookup>();
+
+    final MBeanServerConnection mbsc;
 }
--- a/src/share/classes/com/sun/jmx/mbeanserver/MXBeanProxy.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MXBeanProxy.java	Thu Jun 05 13:42:47 2008 +0200
@@ -27,14 +27,15 @@
 
 import static com.sun.jmx.mbeanserver.Util.*;
 
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.Map;
 
 import javax.management.Attribute;
 import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
 import javax.management.NotCompliantMBeanException;
 import javax.management.ObjectName;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
    <p>Helper class for an {@link InvocationHandler} that forwards methods from an
@@ -46,8 +47,7 @@
    @since 1.6
 */
 public class MXBeanProxy {
-    public MXBeanProxy(Class<?> mxbeanInterface)
-            throws IllegalArgumentException {
+    public MXBeanProxy(Class<?> mxbeanInterface, MXBeanMappingFactory factory) {
 
         if (mxbeanInterface == null)
             throw new IllegalArgumentException("Null parameter");
@@ -55,14 +55,15 @@
         final MBeanAnalyzer<ConvertingMethod> analyzer;
         try {
             analyzer =
-                MXBeanIntrospector.getInstance().getAnalyzer(mxbeanInterface);
+                MXBeanIntrospector.getInstance(factory).getAnalyzer(mxbeanInterface);
         } catch (NotCompliantMBeanException e) {
             throw new IllegalArgumentException(e);
         }
         analyzer.visit(new Visitor());
     }
 
-    private class Visitor implements MBeanAnalyzer.MBeanVisitor<ConvertingMethod> {
+    private class Visitor
+            implements MBeanAnalyzer.MBeanVisitor<ConvertingMethod, RuntimeException> {
         public void visitAttribute(String attributeName,
                                    ConvertingMethod getter,
                                    ConvertingMethod setter) {
@@ -160,10 +161,29 @@
 
         Handler handler = handlerMap.get(method);
         ConvertingMethod cm = handler.getConvertingMethod();
-        MXBeanLookup lookup = MXBeanLookup.lookupFor(mbsc);
-        Object[] openArgs = cm.toOpenParameters(lookup, args);
-        Object result = handler.invoke(mbsc, name, openArgs);
-        return cm.fromOpenReturnValue(lookup, result);
+        String prefix = extractPrefix(name);
+        MXBeanLookup lookup = MXBeanLookup.lookupFor(mbsc, prefix);
+        MXBeanLookup oldLookup = MXBeanLookup.getLookup();
+        try {
+            MXBeanLookup.setLookup(lookup);
+            Object[] openArgs = cm.toOpenParameters(lookup, args);
+            Object result = handler.invoke(mbsc, name, openArgs);
+            return cm.fromOpenReturnValue(lookup, result);
+        } finally {
+            MXBeanLookup.setLookup(oldLookup);
+        }
+    }
+
+    private static String extractPrefix(ObjectName name)
+            throws MalformedObjectNameException {
+        String domain = name.getDomain();
+        int slashslash = domain.lastIndexOf("//");
+        if (slashslash > 0 && domain.charAt(slashslash - 1) == '/')
+            slashslash--;
+        if (slashslash >= 0)
+            return domain.substring(0, slashslash + 2);
+        else
+            return null;
     }
 
     private final Map<Method, Handler> handlerMap = newMap();
--- a/src/share/classes/com/sun/jmx/mbeanserver/MXBeanSupport.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/MXBeanSupport.java	Thu Jun 05 13:42:47 2008 +0200
@@ -35,6 +35,8 @@
 import javax.management.MBeanServer;
 import javax.management.NotCompliantMBeanException;
 import javax.management.ObjectName;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.MXBeanMappingFactoryClass;
 
 /**
  * Base class for MXBeans.
@@ -61,14 +63,16 @@
        if it does not implement the class {@code mxbeanInterface} or if
        that class is not a valid MXBean interface.
     */
-    public <T> MXBeanSupport(T resource, Class<T> mxbeanInterface)
+    public <T> MXBeanSupport(T resource, Class<T> mxbeanInterface,
+                             MXBeanMappingFactory mappingFactory)
             throws NotCompliantMBeanException {
-        super(resource, mxbeanInterface);
+        super(resource, mxbeanInterface, mappingFactory);
     }
 
     @Override
-    MBeanIntrospector<ConvertingMethod> getMBeanIntrospector() {
-        return MXBeanIntrospector.getInstance();
+    MBeanIntrospector<ConvertingMethod>
+            getMBeanIntrospector(MXBeanMappingFactory mappingFactory) {
+        return MXBeanIntrospector.getInstance(mappingFactory);
     }
 
     @Override
@@ -76,8 +80,7 @@
         return mxbeanLookup;
     }
 
-    static Class<?> findMXBeanInterface(Class<?> resourceClass)
-        throws IllegalArgumentException {
+    static <T> Class<? super T> findMXBeanInterface(Class<T> resourceClass) {
         if (resourceClass == null)
             throw new IllegalArgumentException("Null resource class");
         final Set<Class<?>> intfs = transitiveInterfaces(resourceClass);
@@ -104,7 +107,7 @@
             throw new IllegalArgumentException(msg);
         }
         if (candidates.iterator().hasNext()) {
-            return candidates.iterator().next();
+            return Util.cast(candidates.iterator().next());
         } else {
             final String msg =
                 "Class " + resourceClass.getName() +
@@ -116,7 +119,7 @@
     /* Return all interfaces inherited by this class, directly or
      * indirectly through the parent class and interfaces.
      */
-    private static Set<Class<?>> transitiveInterfaces(Class c) {
+    private static Set<Class<?>> transitiveInterfaces(Class<?> c) {
         Set<Class<?>> set = newSet();
         transitiveInterfaces(c, set);
         return set;
@@ -127,7 +130,7 @@
         if (c.isInterface())
             intfs.add(c);
         transitiveInterfaces(c.getSuperclass(), intfs);
-        for (Class sup : c.getInterfaces())
+        for (Class<?> sup : c.getInterfaces())
             transitiveInterfaces(sup, intfs);
     }
 
@@ -157,12 +160,7 @@
         // eventually we could have some logic to supply a default name
 
         synchronized (lock) {
-            if (this.objectName != null) {
-                final String msg =
-                    "MXBean already registered with name " + this.objectName;
-                throw new InstanceAlreadyExistsException(msg);
-            }
-            this.mxbeanLookup = MXBeanLookup.lookupFor(server);
+            this.mxbeanLookup = MXBeanLookup.Plain.lookupFor(server);
             this.mxbeanLookup.addReference(name, getResource());
             this.objectName = name;
         }
@@ -171,12 +169,20 @@
     @Override
     public void unregister() {
         synchronized (lock) {
-            if (mxbeanLookup.removeReference(objectName, getResource()))
-                objectName = null;
+            if (mxbeanLookup != null) {
+                if (mxbeanLookup.removeReference(objectName, getResource()))
+                    objectName = null;
+            }
+            // XXX: need to revisit the whole register/unregister logic in
+            // the face of wrapping.  The mxbeanLookup!=null test is a hack.
+            // If you wrap an MXBean in a MyWrapperMBean and register it,
+            // the lookup table should contain the wrapped object.  But that
+            // implies that MyWrapperMBean calls register, which today it
+            // can't within the public API.
         }
     }
+    private final Object lock = new Object(); // for mxbeanLookup and objectName
 
-    private Object lock = new Object(); // for mxbeanLookup and objectName
-    private MXBeanLookup mxbeanLookup;
+    private MXBeanLookup.Plain mxbeanLookup;
     private ObjectName objectName;
 }
--- a/src/share/classes/com/sun/jmx/mbeanserver/NotificationMBeanSupport.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/NotificationMBeanSupport.java	Thu Jun 05 13:42:47 2008 +0200
@@ -30,6 +30,7 @@
 import java.util.List;
 import javax.management.NotCompliantMBeanException;
 import javax.management.Notification;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * <p>A variant of {@code StandardMBeanSupport} where the only
@@ -48,7 +49,7 @@
     }
 
     @Override
-    MBeanIntrospector<Method> getMBeanIntrospector() {
+    MBeanIntrospector<Method> getMBeanIntrospector(MXBeanMappingFactory ignored) {
         return introspector;
     }
 
--- a/src/share/classes/com/sun/jmx/mbeanserver/OpenConverter.java	Thu Jun 05 13:40:09 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1465 +0,0 @@
-/*
- * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package com.sun.jmx.mbeanserver;
-
-import static com.sun.jmx.mbeanserver.Util.*;
-
-import static javax.management.openmbean.SimpleType.*;
-
-import com.sun.jmx.remote.util.EnvHelp;
-
-import java.beans.ConstructorProperties;
-import java.io.InvalidObjectException;
-import java.lang.annotation.ElementType;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Array;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Proxy;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.WeakHashMap;
-
-import javax.management.JMX;
-import javax.management.ObjectName;
-import javax.management.openmbean.ArrayType;
-import javax.management.openmbean.CompositeData;
-import javax.management.openmbean.CompositeDataInvocationHandler;
-import javax.management.openmbean.CompositeDataSupport;
-import javax.management.openmbean.CompositeDataView;
-import javax.management.openmbean.CompositeType;
-import javax.management.openmbean.OpenDataException;
-import javax.management.openmbean.OpenType;
-import javax.management.openmbean.SimpleType;
-import javax.management.openmbean.TabularData;
-import javax.management.openmbean.TabularDataSupport;
-import javax.management.openmbean.TabularType;
-
-/**
-   <p>A converter between Java types and the limited set of classes
-   defined by Open MBeans.</p>
-
-   <p>A Java type is an instance of java.lang.reflect.Type.  For our
-   purposes, it is either a Class, such as String.class or int.class;
-   or a ParameterizedType, such as List<String> or Map<Integer,
-   String[]>.  On J2SE 1.4 and earlier, it can only be a Class.</p>
-
-   <p>Each Type is associated with an OpenConverter.  The
-   OpenConverter defines an OpenType corresponding to the Type, plus a
-   Java class corresponding to the OpenType.  For example:</p>
-
-   <pre>
-   Type                     Open class     OpenType
-   ----                     ----------     --------
-   Integer                  Integer        SimpleType.INTEGER
-   int                      int            SimpleType.INTEGER
-   Integer[]                Integer[]      ArrayType(1, SimpleType.INTEGER)
-   int[]                    Integer[]      ArrayType(SimpleType.INTEGER, true)
-   String[][]               String[][]     ArrayType(2, SimpleType.STRING)
-   List<String>             String[]       ArrayType(1, SimpleType.STRING)
-   ThreadState (an Enum)    String         SimpleType.STRING
-   Map<Integer, String[]>   TabularData    TabularType(
-                                             CompositeType(
-                                               {"key", SimpleType.INTEGER},
-                                               {"value",
-                                                 ArrayType(1,
-                                                  SimpleType.STRING)}),
-                                             indexNames={"key"})
-   </pre>
-
-   <p>Apart from simple types, arrays, and collections, Java types are
-   converted through introspection into CompositeType.  The Java type
-   must have at least one getter (method such as "int getSize()" or
-   "boolean isBig()"), and we must be able to deduce how to
-   reconstruct an instance of the Java class from the values of the
-   getters using one of various heuristics.</p>
-
-   @since 1.6
- */
-public abstract class OpenConverter {
-    private OpenConverter(Type targetType, OpenType openType,
-                          Class openClass) {
-        this.targetType = targetType;
-        this.openType = openType;
-        this.openClass = openClass;
-    }
-
-    /** <p>Convert an instance of openClass into an instance of targetType. */
-    public final Object fromOpenValue(MXBeanLookup lookup, Object value)
-            throws InvalidObjectException {
-        if (value == null)
-            return null;
-        else
-            return fromNonNullOpenValue(lookup, value);
-    }
-
-    abstract Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
-            throws InvalidObjectException;
-
-    /** <p>Throw an appropriate InvalidObjectException if we will not be able
-        to convert back from the open data to the original Java object.</p> */
-    void checkReconstructible() throws InvalidObjectException {
-        // subclasses override if action necessary
-    }
-
-    /** <p>Convert an instance of targetType into an instance of openClass. */
-    final Object toOpenValue(MXBeanLookup lookup, Object value)
-            throws OpenDataException {
-        if (value == null)
-            return null;
-        else
-            return toNonNullOpenValue(lookup, value);
-    }
-
-    abstract Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-            throws OpenDataException;
-
-    /** <p>True if and only if this OpenConverter's toOpenValue and fromOpenValue
-        methods are the identity function.</p> */
-    boolean isIdentity() {
-        return false;
-    }
-
-    /** <p>True if and only if isIdentity() and even an array of the underlying type
-       is transformed as the identity.  This is true for Integer and
-       ObjectName, for instance, but not for int.</p> */
-    final Type getTargetType() {
-        return targetType;
-    }
-
-    final OpenType getOpenType() {
-        return openType;
-    }
-
-    /* The Java class corresponding to getOpenType().  This is the class
-       named by getOpenType().getClassName(), except that it may be a
-       primitive type or an array of primitive type.  */
-    final Class getOpenClass() {
-        return openClass;
-    }
-
-    private final Type targetType;
-    private final OpenType openType;
-    private final Class openClass;
-
-    private static final class ConverterMap
-        extends WeakHashMap<Type, WeakReference<OpenConverter>> {}
-
-    private static final ConverterMap converterMap = new ConverterMap();
-
-    /** Following List simply serves to keep a reference to predefined
-        OpenConverters so they don't get garbage collected. */
-    private static final List<OpenConverter> permanentConverters = newList();
-
-    private static synchronized OpenConverter getConverter(Type type) {
-        WeakReference<OpenConverter> wr = converterMap.get(type);
-        return (wr == null) ? null : wr.get();
-    }
-
-    private static synchronized void putConverter(Type type,
-                                                  OpenConverter conv) {
-        WeakReference<OpenConverter> wr =
-            new WeakReference<OpenConverter>(conv);
-        converterMap.put(type, wr);
-    }
-
-    private static synchronized void putPermanentConverter(Type type,
-                                                           OpenConverter conv) {
-        putConverter(type, conv);
-        permanentConverters.add(conv);
-    }
-
-    static {
-        /* Set up the mappings for Java types that map to SimpleType.  */
-
-        final OpenType[] simpleTypes = {
-            BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
-            DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
-            VOID,
-        };
-
-        for (int i = 0; i < simpleTypes.length; i++) {
-            final OpenType t = simpleTypes[i];
-            Class c;
-            try {
-                c = Class.forName(t.getClassName(), false,
-                                  ObjectName.class.getClassLoader());
-            } catch (ClassNotFoundException e) {
-                // the classes that these predefined types declare must exist!
-                throw new Error(e);
-            }
-            final OpenConverter conv = new IdentityConverter(c, t, c);
-            putPermanentConverter(c, conv);
-
-            if (c.getName().startsWith("java.lang.")) {
-                try {
-                    final Field typeField = c.getField("TYPE");
-                    final Class primitiveType = (Class) typeField.get(null);
-                    final OpenConverter primitiveConv =
-                        new IdentityConverter(primitiveType, t, primitiveType);
-                    putPermanentConverter(primitiveType,
-                                          primitiveConv);
-                    if (primitiveType != void.class) {
-                        final Class<?> primitiveArrayType =
-                            Array.newInstance(primitiveType, 0).getClass();
-                        final OpenType primitiveArrayOpenType =
-                            ArrayType.getPrimitiveArrayType(primitiveArrayType);
-                        final OpenConverter primitiveArrayConv =
-                            new IdentityConverter(primitiveArrayType,
-                                                  primitiveArrayOpenType,
-                                                  primitiveArrayType);
-                        putPermanentConverter(primitiveArrayType,
-                                              primitiveArrayConv);
-                    }
-                } catch (NoSuchFieldException e) {
-                    // OK: must not be a primitive wrapper
-                } catch (IllegalAccessException e) {
-                    // Should not reach here
-                    assert(false);
-                }
-            }
-        }
-    }
-
-    /** Get the converter for the given Java type, creating it if necessary. */
-    public static synchronized OpenConverter toConverter(Type objType)
-            throws OpenDataException {
-
-        if (inProgress.containsKey(objType))
-            throw new OpenDataException("Recursive data structure");
-
-        OpenConverter conv;
-
-        conv = getConverter(objType);
-        if (conv != null)
-            return conv;
-
-        inProgress.put(objType, objType);
-        try {
-            conv = makeConverter(objType);
-        } finally {
-            inProgress.remove(objType);
-        }
-
-        putConverter(objType, conv);
-        return conv;
-    }
-
-    private static OpenConverter makeConverter(Type objType)
-            throws OpenDataException {
-
-        /* It's not yet worth formalizing these tests by having for example
-           an array of factory classes, each of which says whether it
-           recognizes the Type (Chain of Responsibility pattern).  */
-        if (objType instanceof GenericArrayType) {
-            Type componentType =
-                ((GenericArrayType) objType).getGenericComponentType();
-            return makeArrayOrCollectionConverter(objType, componentType);
-        } else if (objType instanceof Class) {
-            Class<?> objClass = (Class<?>) objType;
-            if (objClass.isEnum()) {
-                // Huge hack to avoid compiler warnings here.  The ElementType
-                // parameter is ignored but allows us to obtain a type variable
-                // T that matches <T extends Enum<T>>.
-                return makeEnumConverter(objClass, ElementType.class);
-            } else if (objClass.isArray()) {
-                Type componentType = objClass.getComponentType();
-                return makeArrayOrCollectionConverter(objClass, componentType);
-            } else if (JMX.isMXBeanInterface(objClass)) {
-                return makeMXBeanConverter(objClass);
-            } else {
-                return makeCompositeConverter(objClass);
-            }
-        } else if (objType instanceof ParameterizedType) {
-            return makeParameterizedConverter((ParameterizedType) objType);
-        } else
-            throw new OpenDataException("Cannot map type: " + objType);
-    }
-
-    private static <T extends Enum<T>> OpenConverter
-            makeEnumConverter(Class<?> enumClass, Class<T> fake) {
-        Class<T> enumClassT = Util.cast(enumClass);
-        return new EnumConverter<T>(enumClassT);
-    }
-
-    /* Make the converter for an array type, or a collection such as
-     * List<String> or Set<Integer>.  We never see one-dimensional
-     * primitive arrays (e.g. int[]) here because they use the identity
-     * converter and are registered as such in the static initializer.
-     */
-    private static OpenConverter
-        makeArrayOrCollectionConverter(Type collectionType, Type elementType)
-            throws OpenDataException {
-
-        final OpenConverter elementConverter = toConverter(elementType);
-        final OpenType<?> elementOpenType = elementConverter.getOpenType();
-        final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
-        final Class<?> elementOpenClass = elementConverter.getOpenClass();
-
-        final Class<?> openArrayClass;
-        final String openArrayClassName;
-        if (elementOpenClass.isArray())
-            openArrayClassName = "[" + elementOpenClass.getName();
-        else
-            openArrayClassName = "[L" + elementOpenClass.getName() + ";";
-        try {
-            openArrayClass = Class.forName(openArrayClassName);
-        } catch (ClassNotFoundException e) {
-            throw openDataException("Cannot obtain array class", e);
-        }
-
-        if (collectionType instanceof ParameterizedType) {
-            return new CollectionConverter(collectionType,
-                                           openType, openArrayClass,
-                                           elementConverter);
-        } else {
-            if (elementConverter.isIdentity()) {
-                return new IdentityConverter(collectionType,
-                                             openType,
-                                             openArrayClass);
-            } else {
-                return new ArrayConverter(collectionType,
-                                          openType,
-                                          openArrayClass,
-                                          elementConverter);
-            }
-        }
-    }
-
-    private static final String[] keyArray = {"key"};
-    private static final String[] keyValueArray = {"key", "value"};
-
-    private static OpenConverter
-        makeTabularConverter(Type objType, boolean sortedMap,
-                             Type keyType, Type valueType)
-            throws OpenDataException {
-
-        final String objTypeName = objType.toString();
-        final OpenConverter keyConverter = toConverter(keyType);
-        final OpenConverter valueConverter = toConverter(valueType);
-        final OpenType keyOpenType = keyConverter.getOpenType();
-        final OpenType valueOpenType = valueConverter.getOpenType();
-        final CompositeType rowType =
-            new CompositeType(objTypeName,
-                              objTypeName,
-                              keyValueArray,
-                              keyValueArray,
-                              new OpenType[] {keyOpenType, valueOpenType});
-        final TabularType tabularType =
-            new TabularType(objTypeName, objTypeName, rowType, keyArray);
-        return new TabularConverter(objType, sortedMap, tabularType,
-                                    keyConverter, valueConverter);
-    }
-
-    /* We know how to translate List<E>, Set<E>, SortedSet<E>,
-       Map<K,V>, SortedMap<K,V>, and that's it.  We don't accept
-       subtypes of those because we wouldn't know how to deserialize
-       them.  We don't accept Queue<E> because it is unlikely people
-       would use that as a parameter or return type in an MBean.  */
-    private static OpenConverter
-        makeParameterizedConverter(ParameterizedType objType) throws OpenDataException {
-
-        final Type rawType = objType.getRawType();
-
-        if (rawType instanceof Class) {
-            Class c = (Class<?>) rawType;
-            if (c == List.class || c == Set.class || c == SortedSet.class) {
-                Type[] actuals = objType.getActualTypeArguments();
-                assert(actuals.length == 1);
-                if (c == SortedSet.class)
-                    mustBeComparable(c, actuals[0]);
-                return makeArrayOrCollectionConverter(objType, actuals[0]);
-            } else {
-                boolean sortedMap = (c == SortedMap.class);
-                if (c == Map.class || sortedMap) {
-                    Type[] actuals = objType.getActualTypeArguments();
-                    assert(actuals.length == 2);
-                    if (sortedMap)
-                        mustBeComparable(c, actuals[0]);
-                    return makeTabularConverter(objType, sortedMap,
-                            actuals[0], actuals[1]);
-                }
-            }
-        }
-        throw new OpenDataException("Cannot convert type: " + objType);
-    }
-
-    private static OpenConverter makeMXBeanConverter(Type t)
-            throws OpenDataException {
-        return new MXBeanConverter(t);
-    }
-
-    private static OpenConverter makeCompositeConverter(Class c)
-            throws OpenDataException {
-
-        // For historical reasons GcInfo implements CompositeData but we
-        // shouldn't count its CompositeData.getCompositeType() field as
-        // an item in the computed CompositeType.
-        final boolean gcInfoHack =
-            (c.getName().equals("com.sun.management.GcInfo") &&
-                c.getClassLoader() == null);
-
-        final List<Method> methods =
-                MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
-        final SortedMap<String,Method> getterMap = newSortedMap();
-
-        /* Select public methods that look like "T getX()" or "boolean
-           isX()", where T is not void and X is not the empty
-           string.  Exclude "Class getClass()" inherited from Object.  */
-        for (Method method : methods) {
-            final String propertyName = propertyName(method);
-
-            if (propertyName == null)
-                continue;
-            if (gcInfoHack && propertyName.equals("CompositeType"))
-                continue;
-
-            Method old =
-                getterMap.put(decapitalize(propertyName),
-                            method);
-            if (old != null) {
-                final String msg =
-                    "Class " + c.getName() + " has method name clash: " +
-                    old.getName() + ", " + method.getName();
-                throw new OpenDataException(msg);
-            }
-        }
-
-        final int nitems = getterMap.size();
-
-        if (nitems == 0) {
-            throw new OpenDataException("Can't map " + c.getName() +
-                                        " to an open data type");
-        }
-
-        final Method[] getters = new Method[nitems];
-        final String[] itemNames = new String[nitems];
-        final OpenType[] openTypes = new OpenType[nitems];
-        int i = 0;
-        for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
-            itemNames[i] = entry.getKey();
-            final Method getter = entry.getValue();
-            getters[i] = getter;
-            final Type retType = getter.getGenericReturnType();
-            openTypes[i] = toConverter(retType).getOpenType();
-            i++;
-        }
-
-        CompositeType compositeType =
-            new CompositeType(c.getName(),
-                              c.getName(),
-                              itemNames, // field names
-                              itemNames, // field descriptions
-                              openTypes);
-
-        return new CompositeConverter(c,
-                                      compositeType,
-                                      itemNames,
-                                      getters);
-    }
-
-    /* Converter for classes where the open data is identical to the
-       original data.  This is true for any of the SimpleType types,
-       and for an any-dimension array of those.  It is also true for
-       primitive types as of JMX 1.3, since an int[] needs to
-       can be directly represented by an ArrayType, and an int needs no mapping
-       because reflection takes care of it.  */
-    private static final class IdentityConverter extends OpenConverter {
-        IdentityConverter(Type targetType, OpenType openType,
-                          Class openClass) {
-            super(targetType, openType, openClass);
-        }
-
-        boolean isIdentity() {
-            return true;
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
-            return value;
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) {
-            return value;
-        }
-    }
-
-    private static final class EnumConverter<T extends Enum<T>>
-            extends OpenConverter {
-
-        EnumConverter(Class<T> enumClass) {
-            super(enumClass, SimpleType.STRING, String.class);
-            this.enumClass = enumClass;
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) {
-            return ((Enum) value).name();
-        }
-
-        // return type could be T, but after erasure that would be
-        // java.lang.Enum, which doesn't exist on J2SE 1.4
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws InvalidObjectException {
-            try {
-                return Enum.valueOf(enumClass, (String) value);
-            } catch (Exception e) {
-                throw invalidObjectException("Cannot convert to enum: " +
-                                             value, e);
-            }
-        }
-
-        private final Class<T> enumClass;
-    }
-
-    private static final class ArrayConverter extends OpenConverter {
-        ArrayConverter(Type targetType,
-                       ArrayType openArrayType, Class openArrayClass,
-                       OpenConverter elementConverter) {
-            super(targetType, openArrayType, openArrayClass);
-            this.elementConverter = elementConverter;
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws OpenDataException {
-            Object[] valueArray = (Object[]) value;
-            final int len = valueArray.length;
-            final Object[] openArray = (Object[])
-                Array.newInstance(getOpenClass().getComponentType(), len);
-            for (int i = 0; i < len; i++) {
-                openArray[i] =
-                    elementConverter.toOpenValue(lookup, valueArray[i]);
-            }
-            return openArray;
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
-                throws InvalidObjectException {
-            final Object[] openArray = (Object[]) openValue;
-            final Type targetType = getTargetType();
-            final Object[] valueArray;
-            final Type componentType;
-            if (targetType instanceof GenericArrayType) {
-                componentType =
-                    ((GenericArrayType) targetType).getGenericComponentType();
-            } else if (targetType instanceof Class &&
-                       ((Class<?>) targetType).isArray()) {
-                componentType = ((Class<?>) targetType).getComponentType();
-            } else {
-                throw new IllegalArgumentException("Not an array: " +
-                                                   targetType);
-            }
-            valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
-                                                      openArray.length);
-            for (int i = 0; i < openArray.length; i++) {
-                valueArray[i] =
-                    elementConverter.fromOpenValue(lookup, openArray[i]);
-            }
-            return valueArray;
-        }
-
-        void checkReconstructible() throws InvalidObjectException {
-            elementConverter.checkReconstructible();
-        }
-
-        /** OpenConverter for the elements of this array.  If this is an
-            array of arrays, the converter converts the second-level arrays,
-            not the deepest elements.  */
-        private final OpenConverter elementConverter;
-    }
-
-    private static final class CollectionConverter extends OpenConverter {
-        CollectionConverter(Type targetType,
-                            ArrayType openArrayType,
-                            Class openArrayClass,
-                            OpenConverter elementConverter) {
-            super(targetType, openArrayType, openArrayClass);
-            this.elementConverter = elementConverter;
-
-            /* Determine the concrete class to be used when converting
-               back to this Java type.  We convert all Lists to ArrayList
-               and all Sets to TreeSet.  (TreeSet because it is a SortedSet,
-               so works for both Set and SortedSet.)  */
-            Type raw = ((ParameterizedType) targetType).getRawType();
-            Class c = (Class<?>) raw;
-            if (c == List.class)
-                collectionClass = ArrayList.class;
-            else if (c == Set.class)
-                collectionClass = HashSet.class;
-            else if (c == SortedSet.class)
-                collectionClass = TreeSet.class;
-            else { // can't happen
-                assert(false);
-                collectionClass = null;
-            }
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws OpenDataException {
-            final Collection valueCollection = (Collection) value;
-            if (valueCollection instanceof SortedSet) {
-                Comparator comparator =
-                    ((SortedSet) valueCollection).comparator();
-                if (comparator != null) {
-                    final String msg =
-                        "Cannot convert SortedSet with non-null comparator: " +
-                        comparator;
-                    throw new OpenDataException(msg);
-                }
-            }
-            final Object[] openArray = (Object[])
-                Array.newInstance(getOpenClass().getComponentType(),
-                                  valueCollection.size());
-            int i = 0;
-            for (Object o : valueCollection)
-                openArray[i++] = elementConverter.toOpenValue(lookup, o);
-            return openArray;
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
-                throws InvalidObjectException {
-            final Object[] openArray = (Object[]) openValue;
-            final Collection<Object> valueCollection;
-            try {
-                valueCollection = Util.cast(collectionClass.newInstance());
-            } catch (Exception e) {
-                throw invalidObjectException("Cannot create collection", e);
-            }
-            for (Object o : openArray) {
-                Object value = elementConverter.fromOpenValue(lookup, o);
-                if (!valueCollection.add(value)) {
-                    final String msg =
-                        "Could not add " + o + " to " +
-                        collectionClass.getName() +
-                        " (duplicate set element?)";
-                    throw new InvalidObjectException(msg);
-                }
-            }
-            return valueCollection;
-        }
-
-        void checkReconstructible() throws InvalidObjectException {
-            elementConverter.checkReconstructible();
-        }
-
-        private final Class<? extends Collection> collectionClass;
-        private final OpenConverter elementConverter;
-    }
-
-    private static final class MXBeanConverter extends OpenConverter {
-        MXBeanConverter(Type intf) {
-            super(intf, SimpleType.OBJECTNAME, ObjectName.class);
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws OpenDataException {
-            lookupNotNull(lookup, OpenDataException.class);
-            ObjectName name = lookup.mxbeanToObjectName(value);
-            if (name == null)
-                throw new OpenDataException("No name for object: " + value);
-            return name;
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws InvalidObjectException {
-            lookupNotNull(lookup, InvalidObjectException.class);
-            ObjectName name = (ObjectName) value;
-            Object mxbean =
-                lookup.objectNameToMXBean(name, (Class<?>) getTargetType());
-            if (mxbean == null) {
-                final String msg =
-                    "No MXBean for name: " + name;
-                throw new InvalidObjectException(msg);
-            }
-            return mxbean;
-        }
-
-        private <T extends Exception> void
-            lookupNotNull(MXBeanLookup lookup, Class<T> excClass)
-                throws T {
-            if (lookup == null) {
-                final String msg =
-                    "Cannot convert MXBean interface in this context";
-                T exc;
-                try {
-                    Constructor<T> con = excClass.getConstructor(String.class);
-                    exc = con.newInstance(msg);
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-                throw exc;
-            }
-        }
-    }
-
-    private static final class TabularConverter extends OpenConverter {
-        TabularConverter(Type targetType,
-                         boolean sortedMap,
-                         TabularType tabularType,
-                         OpenConverter keyConverter,
-                         OpenConverter valueConverter) {
-            super(targetType, tabularType, TabularData.class);
-            this.sortedMap = sortedMap;
-            this.keyConverter = keyConverter;
-            this.valueConverter = valueConverter;
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws OpenDataException {
-            final Map<Object, Object> valueMap = Util.cast(value);
-            if (valueMap instanceof SortedMap) {
-                Comparator comparator = ((SortedMap) valueMap).comparator();
-                if (comparator != null) {
-                    final String msg =
-                        "Cannot convert SortedMap with non-null comparator: " +
-                        comparator;
-                    throw new OpenDataException(msg);
-                }
-            }
-            final TabularType tabularType = (TabularType) getOpenType();
-            final TabularData table = new TabularDataSupport(tabularType);
-            final CompositeType rowType = tabularType.getRowType();
-            for (Map.Entry entry : valueMap.entrySet()) {
-                final Object openKey =
-                    keyConverter.toOpenValue(lookup, entry.getKey());
-                final Object openValue =
-                    valueConverter.toOpenValue(lookup, entry.getValue());
-                final CompositeData row;
-                row =
-                    new CompositeDataSupport(rowType, keyValueArray,
-                                             new Object[] {openKey,
-                                                           openValue});
-                table.put(row);
-            }
-            return table;
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue)
-                throws InvalidObjectException {
-            final TabularData table = (TabularData) openValue;
-            final Collection<CompositeData> rows = Util.cast(table.values());
-            final Map<Object, Object> valueMap =
-                sortedMap ? newSortedMap() : newMap();
-            for (CompositeData row : rows) {
-                final Object key =
-                    keyConverter.fromOpenValue(lookup, row.get("key"));
-                final Object value =
-                    valueConverter.fromOpenValue(lookup, row.get("value"));
-                if (valueMap.put(key, value) != null) {
-                    final String msg =
-                        "Duplicate entry in TabularData: key=" + key;
-                    throw new InvalidObjectException(msg);
-                }
-            }
-            return valueMap;
-        }
-
-        void checkReconstructible() throws InvalidObjectException {
-            keyConverter.checkReconstructible();
-            valueConverter.checkReconstructible();
-        }
-
-        private final boolean sortedMap;
-        private final OpenConverter keyConverter;
-        private final OpenConverter valueConverter;
-    }
-
-    private static final class CompositeConverter extends OpenConverter {
-        CompositeConverter(Class targetClass,
-                           CompositeType compositeType,
-                           String[] itemNames,
-                           Method[] getters) throws OpenDataException {
-            super(targetClass, compositeType, CompositeData.class);
-
-            assert(itemNames.length == getters.length);
-
-            this.itemNames = itemNames;
-            this.getters = getters;
-            this.getterConverters = new OpenConverter[getters.length];
-            for (int i = 0; i < getters.length; i++) {
-                Type retType = getters[i].getGenericReturnType();
-                getterConverters[i] = OpenConverter.toConverter(retType);
-            }
-        }
-
-        final Object toNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws OpenDataException {
-            CompositeType ct = (CompositeType) getOpenType();
-            if (value instanceof CompositeDataView)
-                return ((CompositeDataView) value).toCompositeData(ct);
-            if (value == null)
-                return null;
-
-            Object[] values = new Object[getters.length];
-            for (int i = 0; i < getters.length; i++) {
-                try {
-                    Object got = getters[i].invoke(value, (Object[]) null);
-                    values[i] = getterConverters[i].toOpenValue(lookup, got);
-                } catch (Exception e) {
-                    throw openDataException("Error calling getter for " +
-                                            itemNames[i] + ": " + e, e);
-                }
-            }
-            return new CompositeDataSupport(ct, itemNames, values);
-        }
-
-        /** Determine how to convert back from the CompositeData into
-            the original Java type.  For a type that is not reconstructible,
-            this method will fail every time, and will throw the right
-            exception. */
-        private synchronized void makeCompositeBuilder()
-                throws InvalidObjectException {
-            if (compositeBuilder != null)
-                return;
-
-            Class targetClass = (Class<?>) getTargetType();
-            /* In this 2D array, each subarray is a set of builders where
-               there is no point in consulting the ones after the first if
-               the first refuses.  */
-            CompositeBuilder[][] builders = {
-                {
-                    new CompositeBuilderViaFrom(targetClass, itemNames),
-                },
-                {
-                    new CompositeBuilderViaConstructor(targetClass, itemNames),
-                },
-                {
-                    new CompositeBuilderCheckGetters(targetClass, itemNames,
-                                                     getterConverters),
-                    new CompositeBuilderViaSetters(targetClass, itemNames),
-                    new CompositeBuilderViaProxy(targetClass, itemNames),
-                },
-            };
-            CompositeBuilder foundBuilder = null;
-            /* We try to make a meaningful exception message by
-               concatenating each Builder's explanation of why it
-               isn't applicable.  */
-            final StringBuilder whyNots = new StringBuilder();
-        find:
-            for (CompositeBuilder[] relatedBuilders : builders) {
-                for (int i = 0; i < relatedBuilders.length; i++) {
-                    CompositeBuilder builder = relatedBuilders[i];
-                    String whyNot = builder.applicable(getters);
-                    if (whyNot == null) {
-                        foundBuilder = builder;
-                        break find;
-                    }
-                    if (whyNot.length() > 0) {
-                        if (whyNots.length() > 0)
-                            whyNots.append("; ");
-                        whyNots.append(whyNot);
-                        if (i == 0)
-                           break; // skip other builders in this group
-                    }
-                }
-            }
-            if (foundBuilder == null) {
-                final String msg =
-                    "Do not know how to make a " + targetClass.getName() +
-                    " from a CompositeData: " + whyNots;
-                throw new InvalidObjectException(msg);
-            }
-            compositeBuilder = foundBuilder;
-        }
-
-        void checkReconstructible() throws InvalidObjectException {
-            makeCompositeBuilder();
-        }
-
-        public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value)
-                throws InvalidObjectException {
-            makeCompositeBuilder();
-            return compositeBuilder.fromCompositeData(lookup,
-                                                      (CompositeData) value,
-                                                      itemNames,
-                                                      getterConverters);
-        }
-
-        private final String[] itemNames;
-        private final Method[] getters;
-        private final OpenConverter[] getterConverters;
-        private CompositeBuilder compositeBuilder;
-    }
-
-    /** Converts from a CompositeData to an instance of the targetClass.  */
-    private static abstract class CompositeBuilder {
-        CompositeBuilder(Class targetClass, String[] itemNames) {
-            this.targetClass = targetClass;
-            this.itemNames = itemNames;
-        }
-
-        Class<?> getTargetClass() {
-            return targetClass;
-        }
-
-        String[] getItemNames() {
-            return itemNames;
-        }
-
-        /** If the subclass is appropriate for targetClass, then the
-            method returns null.  If the subclass is not appropriate,
-            then the method returns an explanation of why not.  If the
-            subclass should be appropriate but there is a problem,
-            then the method throws InvalidObjectException.  */
-        abstract String applicable(Method[] getters)
-                throws InvalidObjectException;
-
-        abstract Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                          String[] itemNames,
-                                          OpenConverter[] converters)
-                throws InvalidObjectException;
-
-        private final Class<?> targetClass;
-        private final String[] itemNames;
-    }
-
-    /** Builder for when the target class has a method "public static
-        from(CompositeData)".  */
-    private static final class CompositeBuilderViaFrom
-            extends CompositeBuilder {
-
-        CompositeBuilderViaFrom(Class targetClass, String[] itemNames) {
-            super(targetClass, itemNames);
-        }
-
-        String applicable(Method[] getters) throws InvalidObjectException {
-            // See if it has a method "T from(CompositeData)"
-            // as is conventional for a CompositeDataView
-            Class<?> targetClass = getTargetClass();
-            try {
-                Method fromMethod =
-                    targetClass.getMethod("from",
-                                          new Class[] {CompositeData.class});
-
-                if (!Modifier.isStatic(fromMethod.getModifiers())) {
-                    final String msg =
-                        "Method from(CompositeData) is not static";
-                    throw new InvalidObjectException(msg);
-                }
-
-                if (fromMethod.getReturnType() != getTargetClass()) {
-                    final String msg =
-                        "Method from(CompositeData) returns " +
-                        fromMethod.getReturnType().getName() +
-                        " not " + targetClass.getName();
-                    throw new InvalidObjectException(msg);
-                }
-
-                this.fromMethod = fromMethod;
-                return null; // success!
-            } catch (InvalidObjectException e) {
-                throw e;
-            } catch (Exception e) {
-                // OK: it doesn't have the method
-                return "no method from(CompositeData)";
-            }
-        }
-
-        final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                 String[] itemNames,
-                                 OpenConverter[] converters)
-                throws InvalidObjectException {
-            try {
-                return fromMethod.invoke(null, cd);
-            } catch (Exception e) {
-                final String msg = "Failed to invoke from(CompositeData)";
-                throw invalidObjectException(msg, e);
-            }
-        }
-
-        private Method fromMethod;
-    }
-
-    /** This builder never actually returns success.  It simply serves
-        to check whether the other builders in the same group have any
-        chance of success.  If any getter in the targetClass returns
-        a type that we don't know how to reconstruct, then we will
-        not be able to make a builder, and there is no point in repeating
-        the error about the problematic getter as many times as there are
-        candidate builders.  Instead, the "applicable" method will return
-        an explanatory string, and the other builders will be skipped.
-        If all the getters are OK, then the "applicable" method will return
-        an empty string and the other builders will be tried.  */
-    private static class CompositeBuilderCheckGetters extends CompositeBuilder {
-        CompositeBuilderCheckGetters(Class targetClass, String[] itemNames,
-                                     OpenConverter[] getterConverters) {
-            super(targetClass, itemNames);
-            this.getterConverters = getterConverters;
-        }
-
-        String applicable(Method[] getters) {
-            for (int i = 0; i < getters.length; i++) {
-                try {
-                    getterConverters[i].checkReconstructible();
-                } catch (InvalidObjectException e) {
-                    return "method " + getters[i].getName() + " returns type " +
-                        "that cannot be mapped back from OpenData";
-                }
-            }
-            return "";
-        }
-
-        final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                       String[] itemNames,
-                                       OpenConverter[] converters) {
-            throw new Error();
-        }
-
-        private final OpenConverter[] getterConverters;
-    }
-
-    /** Builder for when the target class has a setter for every getter. */
-    private static class CompositeBuilderViaSetters extends CompositeBuilder {
-
-        CompositeBuilderViaSetters(Class targetClass, String[] itemNames) {
-            super(targetClass, itemNames);
-        }
-
-        String applicable(Method[] getters) {
-            try {
-                Constructor<?> c = getTargetClass().getConstructor((Class[]) null);
-            } catch (Exception e) {
-                return "does not have a public no-arg constructor";
-            }
-
-            Method[] setters = new Method[getters.length];
-            for (int i = 0; i < getters.length; i++) {
-                Method getter = getters[i];
-                Class returnType = getter.getReturnType();
-                String name = propertyName(getter);
-                String setterName = "set" + name;
-                Method setter;
-                try {
-                    setter = getTargetClass().getMethod(setterName, returnType);
-                    if (setter.getReturnType() != void.class)
-                        throw new Exception();
-                } catch (Exception e) {
-                    return "not all getters have corresponding setters " +
-                           "(" + getter + ")";
-                }
-                setters[i] = setter;
-            }
-            this.setters = setters;
-            return null;
-        }
-
-        Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                 String[] itemNames,
-                                 OpenConverter[] converters)
-                throws InvalidObjectException {
-            Object o;
-            try {
-                o = getTargetClass().newInstance();
-                for (int i = 0; i < itemNames.length; i++) {
-                    if (cd.containsKey(itemNames[i])) {
-                        Object openItem = cd.get(itemNames[i]);
-                        Object javaItem =
-                            converters[i].fromOpenValue(lookup, openItem);
-                        setters[i].invoke(o, javaItem);
-                    }
-                }
-            } catch (Exception e) {
-                throw invalidObjectException(e);
-            }
-            return o;
-        }
-
-        private Method[] setters;
-    }
-
-    /** Builder for when the target class has a constructor that is
-        annotated with @ConstructorProperties so we can see the correspondence
-        to getters.  */
-    private static final class CompositeBuilderViaConstructor
-            extends CompositeBuilder {
-
-        CompositeBuilderViaConstructor(Class targetClass, String[] itemNames) {
-            super(targetClass, itemNames);
-        }
-
-        String applicable(Method[] getters) throws InvalidObjectException {
-
-            final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
-
-            Class targetClass = getTargetClass();
-            Constructor<?>[] constrs = targetClass.getConstructors();
-
-            // Applicable if and only if there are any annotated constructors
-            List<Constructor<?>> annotatedConstrList = newList();
-            for (Constructor<?> constr : constrs) {
-                if (Modifier.isPublic(constr.getModifiers())
-                        && constr.getAnnotation(propertyNamesClass) != null)
-                    annotatedConstrList.add(constr);
-            }
-
-            if (annotatedConstrList.isEmpty())
-                return "no constructor has @ConstructorProperties annotation";
-
-            annotatedConstructors = newList();
-
-            // Now check that all the annotated constructors are valid
-            // and throw an exception if not.
-
-            // First link the itemNames to their getter indexes.
-            Map<String, Integer> getterMap = newMap();
-            String[] itemNames = getItemNames();
-            for (int i = 0; i < itemNames.length; i++)
-                getterMap.put(itemNames[i], i);
-
-            // Run through the constructors making the checks in the spec.
-            // For each constructor, remember the correspondence between its
-            // parameters and the items.  The int[] for a constructor says
-            // what parameter index should get what item.  For example,
-            // if element 0 is 2 then that means that item 0 in the
-            // CompositeData goes to parameter 2 of the constructor.  If an
-            // element is -1, that item isn't given to the constructor.
-            // Also remember the set of properties in that constructor
-            // so we can test unambiguity.
-            Set<BitSet> getterIndexSets = newSet();
-            for (Constructor<?> constr : annotatedConstrList) {
-                String[] propertyNames =
-                    constr.getAnnotation(propertyNamesClass).value();
-
-                Type[] paramTypes = constr.getGenericParameterTypes();
-                if (paramTypes.length != propertyNames.length) {
-                    final String msg =
-                        "Number of constructor params does not match " +
-                        "@ConstructorProperties annotation: " + constr;
-                    throw new InvalidObjectException(msg);
-                }
-
-                int[] paramIndexes = new int[getters.length];
-                for (int i = 0; i < getters.length; i++)
-                    paramIndexes[i] = -1;
-                BitSet present = new BitSet();
-
-                for (int i = 0; i < propertyNames.length; i++) {
-                    String propertyName = propertyNames[i];
-                    if (!getterMap.containsKey(propertyName)) {
-                        final String msg =
-                            "@ConstructorProperties includes name " + propertyName +
-                            " which does not correspond to a property: " +
-                            constr;
-                        throw new InvalidObjectException(msg);
-                    }
-                    int getterIndex = getterMap.get(propertyName);
-                    paramIndexes[getterIndex] = i;
-                    if (present.get(getterIndex)) {
-                        final String msg =
-                            "@ConstructorProperties contains property " +
-                            propertyName + " more than once: " + constr;
-                        throw new InvalidObjectException(msg);
-                    }
-                    present.set(getterIndex);
-                    Method getter = getters[getterIndex];
-                    Type propertyType = getter.getGenericReturnType();
-                    if (!propertyType.equals(paramTypes[i])) {
-                        final String msg =
-                            "@ConstructorProperties gives property " + propertyName +
-                            " of type " + propertyType + " for parameter " +
-                            " of type " + paramTypes[i] + ": " + constr;
-                        throw new InvalidObjectException(msg);
-                    }
-                }
-
-                if (!getterIndexSets.add(present)) {
-                    final String msg =
-                        "More than one constructor has a @ConstructorProperties " +
-                        "annotation with this set of names: " +
-                        Arrays.toString(propertyNames);
-                    throw new InvalidObjectException(msg);
-                }
-
-                Constr c = new Constr(constr, paramIndexes, present);
-                annotatedConstructors.add(c);
-            }
-
-            /* Check that no possible set of items could lead to an ambiguous
-             * choice of constructor (spec requires this check).  For any
-             * pair of constructors, their union would be the minimal
-             * ambiguous set.  If this set itself corresponds to a constructor,
-             * there is no ambiguity for that pair.  In the usual case, one
-             * of the constructors is a superset of the other so the union is
-             * just the bigger constuctor.
-             *
-             * The algorithm here is quadratic in the number of constructors
-             * with a @ConstructorProperties annotation.  Typically this corresponds
-             * to the number of versions of the class there have been.  Ten
-             * would already be a large number, so although it's probably
-             * possible to have an O(n lg n) algorithm it wouldn't be
-             * worth the complexity.
-             */
-            for (BitSet a : getterIndexSets) {
-                boolean seen = false;
-                for (BitSet b : getterIndexSets) {
-                    if (a == b)
-                        seen = true;
-                    else if (seen) {
-                        BitSet u = new BitSet();
-                        u.or(a); u.or(b);
-                        if (!getterIndexSets.contains(u)) {
-                            Set<String> names = new TreeSet<String>();
-                            for (int i = u.nextSetBit(0); i >= 0;
-                                 i = u.nextSetBit(i+1))
-                                names.add(itemNames[i]);
-                            final String msg =
-                                "Constructors with @ConstructorProperties annotation " +
-                                " would be ambiguous for these items: " +
-                                names;
-                            throw new InvalidObjectException(msg);
-                        }
-                    }
-                }
-            }
-
-            return null; // success!
-        }
-
-        Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                 String[] itemNames,
-                                 OpenConverter[] converters)
-                throws InvalidObjectException {
-            // The CompositeData might come from an earlier version where
-            // not all the items were present.  We look for a constructor
-            // that accepts just the items that are present.  Because of
-            // the ambiguity check in applicable(), we know there must be
-            // at most one maximally applicable constructor.
-            CompositeType ct = cd.getCompositeType();
-            BitSet present = new BitSet();
-            for (int i = 0; i < itemNames.length; i++) {
-                if (ct.getType(itemNames[i]) != null)
-                    present.set(i);
-            }
-
-            Constr max = null;
-            for (Constr constr : annotatedConstructors) {
-                if (subset(constr.presentParams, present) &&
-                        (max == null ||
-                         subset(max.presentParams, constr.presentParams)))
-                    max = constr;
-            }
-
-            if (max == null) {
-                final String msg =
-                    "No constructor has a @ConstructorProperties for this set of " +
-                    "items: " + ct.keySet();
-                throw new InvalidObjectException(msg);
-            }
-
-            Object[] params = new Object[max.presentParams.cardinality()];
-            for (int i = 0; i < itemNames.length; i++) {
-                if (!max.presentParams.get(i))
-                    continue;
-                Object openItem = cd.get(itemNames[i]);
-                Object javaItem = converters[i].fromOpenValue(lookup, openItem);
-                int index = max.paramIndexes[i];
-                if (index >= 0)
-                    params[index] = javaItem;
-            }
-
-            try {
-                return max.constructor.newInstance(params);
-            } catch (Exception e) {
-                final String msg =
-                    "Exception constructing " + getTargetClass().getName();
-                throw invalidObjectException(msg, e);
-            }
-        }
-
-        private static boolean subset(BitSet sub, BitSet sup) {
-            BitSet subcopy = (BitSet) sub.clone();
-            subcopy.andNot(sup);
-            return subcopy.isEmpty();
-        }
-
-        private static class Constr {
-            final Constructor<?> constructor;
-            final int[] paramIndexes;
-            final BitSet presentParams;
-            Constr(Constructor<?> constructor, int[] paramIndexes,
-                   BitSet presentParams) {
-                this.constructor = constructor;
-                this.paramIndexes = paramIndexes;
-                this.presentParams = presentParams;
-            }
-        }
-
-        private List<Constr> annotatedConstructors;
-    }
-
-    /** Builder for when the target class is an interface and contains
-        no methods other than getters.  Then we can make an instance
-        using a dynamic proxy that forwards the getters to the source
-        CompositeData.  */
-    private static final class CompositeBuilderViaProxy
-            extends CompositeBuilder {
-
-        CompositeBuilderViaProxy(Class targetClass, String[] itemNames) {
-            super(targetClass, itemNames);
-        }
-
-        String applicable(Method[] getters) {
-            Class targetClass = getTargetClass();
-            if (!targetClass.isInterface())
-                return "not an interface";
-            Set<Method> methods =
-                newSet(Arrays.asList(targetClass.getMethods()));
-            methods.removeAll(Arrays.asList(getters));
-            /* If the interface has any methods left over, they better be
-             * public methods that are already present in java.lang.Object.
-             */
-            String bad = null;
-            for (Method m : methods) {
-                String mname = m.getName();
-                Class[] mparams = m.getParameterTypes();
-                try {
-                    Method om = Object.class.getMethod(mname, mparams);
-                    if (!Modifier.isPublic(om.getModifiers()))
-                        bad = mname;
-                } catch (NoSuchMethodException e) {
-                    bad = mname;
-                }
-                /* We don't catch SecurityException since it shouldn't
-                 * happen for a method in Object and if it does we would
-                 * like to know about it rather than mysteriously complaining.
-                 */
-            }
-            if (bad != null)
-                return "contains methods other than getters (" + bad + ")";
-            return null; // success!
-        }
-
-        final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd,
-                                 String[] itemNames,
-                                 OpenConverter[] converters) {
-            final Class targetClass = getTargetClass();
-            return
-                Proxy.newProxyInstance(targetClass.getClassLoader(),
-                                       new Class[] {targetClass},
-                                       new CompositeDataInvocationHandler(cd));
-        }
-    }
-
-    static InvalidObjectException invalidObjectException(String msg,
-                                                         Throwable cause) {
-        return EnvHelp.initCause(new InvalidObjectException(msg), cause);
-    }
-
-    static InvalidObjectException invalidObjectException(Throwable cause) {
-        return invalidObjectException(cause.getMessage(), cause);
-    }
-
-    static OpenDataException openDataException(String msg, Throwable cause) {
-        return EnvHelp.initCause(new OpenDataException(msg), cause);
-    }
-
-    static OpenDataException openDataException(Throwable cause) {
-        return openDataException(cause.getMessage(), cause);
-    }
-
-    static void mustBeComparable(Class collection, Type element)
-            throws OpenDataException {
-        if (!(element instanceof Class)
-            || !Comparable.class.isAssignableFrom((Class<?>) element)) {
-            final String msg =
-                "Parameter class " + element + " of " +
-                collection.getName() + " does not implement " +
-                Comparable.class.getName();
-            throw new OpenDataException(msg);
-        }
-    }
-
-    /**
-     * Utility method to take a string and convert it to normal Java variable
-     * name capitalization.  This normally means converting the first
-     * character from upper case to lower case, but in the (unusual) special
-     * case when there is more than one character and both the first and
-     * second characters are upper case, we leave it alone.
-     * <p>
-     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
-     * as "URL".
-     *
-     * @param  name The string to be decapitalized.
-     * @return  The decapitalized version of the string.
-     */
-    public static String decapitalize(String name) {
-        if (name == null || name.length() == 0) {
-            return name;
-        }
-        int offset1 = Character.offsetByCodePoints(name, 0, 1);
-        // Should be name.offsetByCodePoints but 6242664 makes this fail
-        if (offset1 < name.length() &&
-                Character.isUpperCase(name.codePointAt(offset1)))
-            return name;
-        return name.substring(0, offset1).toLowerCase() +
-               name.substring(offset1);
-    }
-
-    /**
-     * Reverse operation for java.beans.Introspector.decapitalize.  For any s,
-     * capitalize(decapitalize(s)).equals(s).  The reverse is not true:
-     * e.g. capitalize("uRL") produces "URL" which is unchanged by
-     * decapitalize.
-     */
-    static String capitalize(String name) {
-        if (name == null || name.length() == 0)
-            return name;
-        int offset1 = name.offsetByCodePoints(0, 1);
-        return name.substring(0, offset1).toUpperCase() +
-               name.substring(offset1);
-    }
-
-    public static String propertyName(Method m) {
-        String rest = null;
-        String name = m.getName();
-        if (name.startsWith("get"))
-            rest = name.substring(3);
-        else if (name.startsWith("is") && m.getReturnType() == boolean.class)
-            rest = name.substring(2);
-        if (rest == null || rest.length() == 0
-            || m.getParameterTypes().length > 0
-            || m.getReturnType() == void.class
-            || name.equals("getClass"))
-            return null;
-        return rest;
-    }
-
-    private final static Map<Type, Type> inProgress = newIdentityHashMap();
-    // really an IdentityHashSet but that doesn't exist
-}
--- a/src/share/classes/com/sun/jmx/mbeanserver/PerInterface.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/PerInterface.java	Thu Jun 05 13:42:47 2008 +0200
@@ -231,7 +231,7 @@
     /**
      * Visitor that sets up the method maps (operations, getters, setters).
      */
-    private class InitMaps implements MBeanAnalyzer.MBeanVisitor<M> {
+    private class InitMaps implements MBeanAnalyzer.MBeanVisitor<M, RuntimeException> {
         public void visitAttribute(String attributeName,
                                    M getter,
                                    M setter) {
--- a/src/share/classes/com/sun/jmx/mbeanserver/StandardMBeanSupport.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/com/sun/jmx/mbeanserver/StandardMBeanSupport.java	Thu Jun 05 13:42:47 2008 +0200
@@ -25,14 +25,13 @@
 
 package com.sun.jmx.mbeanserver;
 
-import static com.sun.jmx.mbeanserver.Util.*;
-
 import java.lang.reflect.Method;
 
 import javax.management.MBeanInfo;
 import javax.management.MBeanServer;
 import javax.management.NotCompliantMBeanException;
 import javax.management.ObjectName;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * Base class for Standard MBeans.
@@ -61,11 +60,11 @@
     */
     public <T> StandardMBeanSupport(T resource, Class<T> mbeanInterface)
             throws NotCompliantMBeanException {
-        super(resource, mbeanInterface);
+        super(resource, mbeanInterface, (MXBeanMappingFactory) null);
     }
 
     @Override
-    MBeanIntrospector<Method> getMBeanIntrospector() {
+    MBeanIntrospector<Method> getMBeanIntrospector(MXBeanMappingFactory ignored) {
         return StandardMBeanIntrospector.getInstance();
     }
 
--- a/src/share/classes/javax/management/JMX.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/JMX.java	Thu Jun 05 13:42:47 2008 +0200
@@ -26,8 +26,17 @@
 package javax.management;
 
 import com.sun.jmx.mbeanserver.Introspector;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.beans.BeanInfo;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
 import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * Static methods from the JMX API.  There are no instances of this class.
@@ -39,6 +48,8 @@
      * this class.
      */
     static final JMX proof = new JMX();
+    private static final ClassLogger logger =
+        new ClassLogger("javax.management.misc", "JMX");
 
     private JMX() {}
 
@@ -85,6 +96,14 @@
     public static final String MXBEAN_FIELD = "mxbean";
 
     /**
+     * The name of the
+     * <a href="Descriptor.html#mxbeanMappingFactoryClass">{@code
+     * mxbeanMappingFactoryClass}</a> field.
+     */
+    public static final String MXBEAN_MAPPING_FACTORY_CLASS_FIELD =
+            "mxbeanMappingFactoryClass";
+
+    /**
      * The name of the <a href="Descriptor.html#openType">{@code
      * openType}</a> field.
      */
@@ -97,6 +116,264 @@
     public static final String ORIGINAL_TYPE_FIELD = "originalType";
 
     /**
+     * <p>Options to apply to an MBean proxy or to an instance of {@link
+     * StandardMBean}.</p>
+     *
+     * <p>For example, to specify a custom {@link MXBeanMappingFactory}
+     * for a {@code StandardMBean}, you might write this:</p>
+     *
+     * <pre>
+     * MXBeanMappingFactory factory = new MyMXBeanMappingFactory();
+     * JMX.MBeanOptions opts = new JMX.MBeanOptions();
+     * opts.setMXBeanMappingFactory(factory);
+     * StandardMBean mbean = new StandardMBean(impl, intf, opts);
+     * </pre>
+     *
+     * @see javax.management.JMX.ProxyOptions
+     */
+    public static class MBeanOptions implements Serializable, Cloneable {
+        private static final long serialVersionUID = -6380842449318177843L;
+
+        static final MBeanOptions MXBEAN = new MBeanOptions();
+        static {
+            MXBEAN.setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+        }
+
+        private MXBeanMappingFactory mappingFactory;
+
+        /**
+         * <p>Construct an {@code MBeanOptions} object where all options have
+         * their default values.</p>
+         */
+        public MBeanOptions() {}
+
+        @Override
+        public MBeanOptions clone() {
+            try {
+                return (MBeanOptions) super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        /**
+         * <p>True if this is an MXBean proxy or a StandardMBean instance
+         * that is an MXBean.  The default value is false.</p>
+         *
+         * <p>This method is equivalent to {@link #getMXBeanMappingFactory()
+         * this.getMXBeanMappingFactory()}{@code != null}.</p>
+         *
+         * @return true if this is an MXBean proxy or a StandardMBean instance
+         * that is an MXBean.
+         */
+        public boolean isMXBean() {
+            return (this.mappingFactory != null);
+        }
+
+        /**
+         * <p>The mappings between Java types and Open Types to be used in
+         * an MXBean proxy or a StandardMBean instance that is an MXBean,
+         * or null if this instance is not for an MXBean.
+         * The default value is null.</p>
+         *
+         * @return the mappings to be used in this proxy or StandardMBean,
+         * or null if this instance is not for an MXBean.
+         */
+        public MXBeanMappingFactory getMXBeanMappingFactory() {
+            return mappingFactory;
+        }
+
+        /**
+         * <p>Set the {@link #getMXBeanMappingFactory() MXBeanMappingFactory} to
+         * the given value.  The value should be null if this instance is not
+         * for an MXBean.  If this instance is for an MXBean, the value should
+         * usually be either a custom mapping factory, or
+         * {@link MXBeanMappingFactory#forInterface
+         * MXBeanMappingFactory.forInterface}{@code (mxbeanInterface)}
+         * which signifies
+         * that the {@linkplain MXBeanMappingFactory#DEFAULT default} mapping
+         * factory should be used unless an {@code @}{@link
+         * javax.management.openmbean.MXBeanMappingFactoryClass
+         * MXBeanMappingFactoryClass} annotation on {@code mxbeanInterface}
+         * specifies otherwise.</p>
+         *
+         * <p>Examples:</p>
+         * <pre>
+         * MBeanOptions opts = new MBeanOptions();
+         * opts.setMXBeanMappingFactory(myMappingFactory);
+         * MyMXBean proxy = JMX.newMBeanProxy(
+         *         mbeanServerConnection, objectName, MyMXBean.class, opts);
+         *
+         * // ...or...
+         *
+         * MBeanOptions opts = new MBeanOptions();
+         * MXBeanMappingFactory defaultFactoryForMyMXBean =
+         *         MXBeanMappingFactory.forInterface(MyMXBean.class);
+         * opts.setMXBeanMappingFactory(defaultFactoryForMyMXBean);
+         * MyMXBean proxy = JMX.newMBeanProxy(
+         *         mbeanServerConnection, objectName, MyMXBean.class, opts);
+         * </pre>
+         *
+         * @param f the new value.  If null, this instance is not for an
+         * MXBean.
+         */
+        public void setMXBeanMappingFactory(MXBeanMappingFactory f) {
+            this.mappingFactory = f;
+        }
+
+        /* To maximise object sharing, classes in this package can replace
+         * a private MBeanOptions with no MXBeanMappingFactory with one
+         * of these shared instances.  But they must be EXTREMELY careful
+         * never to give out the shared instances to user code, which could
+         * modify them.
+         */
+        private static final MBeanOptions[] CANONICALS = {
+            new MBeanOptions(), MXBEAN,
+        };
+        // Overridden in local subclasses:
+        MBeanOptions[] canonicals() {
+            return CANONICALS;
+        }
+
+        // This is only used by the logic for canonical instances.
+        // Overridden in local subclasses:
+        boolean same(MBeanOptions opt) {
+            return (opt.mappingFactory == mappingFactory);
+        }
+
+        final MBeanOptions canonical() {
+            for (MBeanOptions opt : canonicals()) {
+                if (opt.getClass() == this.getClass() && same(opt))
+                    return opt;
+            }
+            return this;
+        }
+
+        final MBeanOptions uncanonical() {
+            for (MBeanOptions opt : canonicals()) {
+                if (this == opt)
+                    return clone();
+            }
+            return this;
+        }
+
+        private Map<String, Object> toMap() {
+            Map<String, Object> map = new TreeMap<String, Object>();
+            try {
+                BeanInfo bi = java.beans.Introspector.getBeanInfo(getClass());
+                PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+                for (PropertyDescriptor pd : pds) {
+                    String name = pd.getName();
+                    if (name.equals("class"))
+                        continue;
+                    Method get = pd.getReadMethod();
+                    if (get != null)
+                        map.put(name, get.invoke(this));
+                }
+            } catch (Exception e) {
+                Throwable t = e;
+                if (t instanceof InvocationTargetException)
+                    t = t.getCause();
+                map.put("Exception", t);
+            }
+            return map;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + toMap();
+            // For example "MBeanOptions{MXBean=true, <etc>}".
+        }
+
+        /**
+         * <p>Indicates whether some other object is "equal to" this one. The
+         * result is true if and only if the other object is also an instance
+         * of MBeanOptions or a subclass, and has the same properties with
+         * the same values.</p>
+         * @return {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this)
+                return true;
+            if (obj == null || obj.getClass() != this.getClass())
+                return false;
+            return toMap().equals(((MBeanOptions) obj).toMap());
+        }
+
+        @Override
+        public int hashCode() {
+            return toMap().hashCode();
+        }
+    }
+
+    /**
+     * <p>Options to apply to an MBean proxy.</p>
+     *
+     * @see #newMBeanProxy
+     */
+    public static class ProxyOptions extends MBeanOptions {
+        private static final long serialVersionUID = 7238804866098386559L;
+
+        private boolean notificationEmitter;
+
+        /**
+         * <p>Construct a {@code ProxyOptions} object where all options have
+         * their default values.</p>
+         */
+        public ProxyOptions() {}
+
+        @Override
+        public ProxyOptions clone() {
+            return (ProxyOptions) super.clone();
+        }
+
+        /**
+         * <p>Defines whether the returned proxy should
+         * implement {@link NotificationEmitter}.  The default value is false.</p>
+         *
+         * @return true if this proxy will be a NotificationEmitter.
+         *
+         * @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class,
+         * MBeanOptions)
+         */
+        public boolean isNotificationEmitter() {
+            return this.notificationEmitter;
+        }
+
+        /**
+         * <p>Set the {@link #isNotificationEmitter NotificationEmitter} option to
+         * the given value.</p>
+         * @param emitter the new value.
+         */
+        public void setNotificationEmitter(boolean emitter) {
+            this.notificationEmitter = emitter;
+        }
+
+        // Canonical objects for each of (MXBean,!MXBean) x (Emitter,!Emitter)
+        private static final ProxyOptions[] CANONICALS = {
+            new ProxyOptions(), new ProxyOptions(),
+            new ProxyOptions(), new ProxyOptions(),
+        };
+        static {
+            CANONICALS[1].setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+            CANONICALS[2].setNotificationEmitter(true);
+            CANONICALS[3].setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+            CANONICALS[3].setNotificationEmitter(true);
+        }
+        @Override
+        MBeanOptions[] canonicals() {
+            return CANONICALS;
+        }
+
+        @Override
+        boolean same(MBeanOptions opt) {
+            return (super.same(opt) && opt instanceof ProxyOptions &&
+                    ((ProxyOptions) opt).notificationEmitter == notificationEmitter);
+        }
+    }
+
+    /**
      * <p>Make a proxy for a Standard MBean in a local or remote
      * MBean Server.</p>
      *
@@ -172,7 +449,7 @@
      *
      * <p>This method behaves the same as {@link
      * #newMBeanProxy(MBeanServerConnection, ObjectName, Class)}, but
-     * additionally, if {@code notificationBroadcaster} is {@code
+     * additionally, if {@code notificationEmitter} is {@code
      * true}, then the MBean is assumed to be a {@link
      * NotificationBroadcaster} or {@link NotificationEmitter} and the
      * returned proxy will implement {@link NotificationEmitter} as
@@ -189,25 +466,21 @@
      * {@code connection} to forward to.
      * @param interfaceClass the management interface that the MBean
      * exports, which will also be implemented by the returned proxy.
-     * @param notificationBroadcaster make the returned proxy
+     * @param notificationEmitter make the returned proxy
      * implement {@link NotificationEmitter} by forwarding its methods
      * via {@code connection}.
-     *
      * @param <T> allows the compiler to know that if the {@code
      * interfaceClass} parameter is {@code MyMBean.class}, for
      * example, then the return type is {@code MyMBean}.
-     *
      * @return the new proxy instance.
      */
     public static <T> T newMBeanProxy(MBeanServerConnection connection,
                                       ObjectName objectName,
                                       Class<T> interfaceClass,
-                                      boolean notificationBroadcaster) {
-        return MBeanServerInvocationHandler.newProxyInstance(
-                connection,
-                objectName,
-                interfaceClass,
-                notificationBroadcaster);
+                                      boolean notificationEmitter) {
+        ProxyOptions opts = new ProxyOptions();
+        opts.setNotificationEmitter(notificationEmitter);
+        return newMBeanProxy(connection, objectName, interfaceClass, opts);
     }
 
     /**
@@ -314,7 +587,7 @@
      *
      * <p>This method behaves the same as {@link
      * #newMXBeanProxy(MBeanServerConnection, ObjectName, Class)}, but
-     * additionally, if {@code notificationBroadcaster} is {@code
+     * additionally, if {@code notificationEmitter} is {@code
      * true}, then the MXBean is assumed to be a {@link
      * NotificationBroadcaster} or {@link NotificationEmitter} and the
      * returned proxy will implement {@link NotificationEmitter} as
@@ -331,31 +604,105 @@
      * {@code connection} to forward to.
      * @param interfaceClass the MXBean interface,
      * which will also be implemented by the returned proxy.
-     * @param notificationBroadcaster make the returned proxy
+     * @param notificationEmitter make the returned proxy
      * implement {@link NotificationEmitter} by forwarding its methods
      * via {@code connection}.
-     *
      * @param <T> allows the compiler to know that if the {@code
      * interfaceClass} parameter is {@code MyMXBean.class}, for
      * example, then the return type is {@code MyMXBean}.
-     *
      * @return the new proxy instance.
      */
     public static <T> T newMXBeanProxy(MBeanServerConnection connection,
                                        ObjectName objectName,
                                        Class<T> interfaceClass,
-                                       boolean notificationBroadcaster) {
-        // Check interface for MXBean compliance
-        //
+                                       boolean notificationEmitter) {
+        ProxyOptions opts = new ProxyOptions();
+        MXBeanMappingFactory f = MXBeanMappingFactory.forInterface(interfaceClass);
+        opts.setMXBeanMappingFactory(f);
+        opts.setNotificationEmitter(notificationEmitter);
+        return newMBeanProxy(connection, objectName, interfaceClass, opts);
+    }
+
+    /**
+     * <p>Make a proxy for a Standard MBean or MXBean in a local or remote MBean
+     * Server that may also support the methods of {@link
+     * NotificationEmitter} and (for an MXBean) that may define custom MXBean
+     * type mappings.</p>
+     *
+     * <p>This method behaves the same as
+     * {@link #newMBeanProxy(MBeanServerConnection, ObjectName, Class)} or
+     * {@link #newMXBeanProxy(MBeanServerConnection, ObjectName, Class)},
+     * according as {@code opts.isMXBean()} is respectively false or true; but
+     * with the following changes based on {@code opts}.</p>
+     *
+     * <ul>
+     *     <li>If {@code opts.isNotificationEmitter()} is {@code
+     *         true}, then the MBean is assumed to be a {@link
+     *         NotificationBroadcaster} or {@link NotificationEmitter} and the
+     *         returned proxy will implement {@link NotificationEmitter} as
+     *         well as {@code interfaceClass}.  A call to {@link
+     *         NotificationBroadcaster#addNotificationListener} on the proxy
+     *         will result in a call to {@link
+     *         MBeanServerConnection#addNotificationListener(ObjectName,
+     *         NotificationListener, NotificationFilter, Object)}, and
+     *         likewise for the other methods of {@link
+     *     NotificationBroadcaster} and {@link NotificationEmitter}.</li>
+     *
+     *     <li>If {@code opts.getMXBeanMappingFactory()} is not null,
+     *         then the mappings it defines will be applied to convert between
+     *     arbitrary Java types and Open Types.</li>
+     * </ul>
+     *
+     * @param connection the MBean server to forward to.
+     * @param objectName the name of the MBean within
+     * {@code connection} to forward to.
+     * @param interfaceClass the Standard MBean or MXBean interface,
+     * which will also be implemented by the returned proxy.
+     * @param opts the options to apply for this proxy.  Can be null,
+     * in which case default options are applied.
+     * @param <T> allows the compiler to know that if the {@code
+     * interfaceClass} parameter is {@code MyMXBean.class}, for
+     * example, then the return type is {@code MyMXBean}.
+     * @return the new proxy instance.
+     *
+     * @throws IllegalArgumentException if {@code interfaceClass} is not a
+     * valid MXBean interface.
+     */
+    public static <T> T newMBeanProxy(MBeanServerConnection connection,
+                                      ObjectName objectName,
+                                      Class<T> interfaceClass,
+                                      MBeanOptions opts) {
         try {
-            Introspector.testComplianceMXBeanInterface(interfaceClass);
+            return newMBeanProxy2(connection, objectName, interfaceClass, opts);
         } catch (NotCompliantMBeanException e) {
             throw new IllegalArgumentException(e);
         }
+    }
+
+    private static <T> T newMBeanProxy2(MBeanServerConnection connection,
+                                        ObjectName objectName,
+                                        Class<T> interfaceClass,
+                                        MBeanOptions opts)
+    throws NotCompliantMBeanException {
+
+        if (opts == null)
+            opts = new MBeanOptions();
+
+        boolean notificationEmitter = opts instanceof ProxyOptions &&
+                ((ProxyOptions) opts).isNotificationEmitter();
+
+        MXBeanMappingFactory mappingFactory = opts.getMXBeanMappingFactory();
+
+        if (mappingFactory != null) {
+            // Check interface for MXBean compliance
+            Introspector.testComplianceMXBeanInterface(interfaceClass,
+                    mappingFactory);
+        }
+
         InvocationHandler handler = new MBeanServerInvocationHandler(
-                connection, objectName, true);
+                connection, objectName, opts);
         final Class[] interfaces;
-        if (notificationBroadcaster) {
+        if (notificationEmitter) {
             interfaces =
                 new Class<?>[] {interfaceClass, NotificationEmitter.class};
         } else
--- a/src/share/classes/javax/management/MBeanServerInvocationHandler.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/MBeanServerInvocationHandler.java	Thu Jun 05 13:42:47 2008 +0200
@@ -33,6 +33,9 @@
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.WeakHashMap;
+import javax.management.openmbean.MXBeanMappingFactory;
+
+import static javax.management.JMX.MBeanOptions;
 
 /**
  * <p>{@link InvocationHandler} that forwards methods in an MBean's
@@ -111,7 +114,7 @@
     public MBeanServerInvocationHandler(MBeanServerConnection connection,
                                         ObjectName objectName) {
 
-        this(connection, objectName, false);
+        this(connection, objectName, null);
     }
 
     /**
@@ -138,6 +141,14 @@
     public MBeanServerInvocationHandler(MBeanServerConnection connection,
                                         ObjectName objectName,
                                         boolean isMXBean) {
+        this(connection, objectName, isMXBean ? MBeanOptions.MXBEAN : null);
+    }
+
+    public MBeanServerInvocationHandler(MBeanServerConnection connection,
+                                        ObjectName objectName,
+                                        MBeanOptions options) {
+        if (options == null)
+            options = new MBeanOptions();
         if (connection == null) {
             throw new IllegalArgumentException("Null connection");
         }
@@ -146,7 +157,7 @@
         }
         this.connection = connection;
         this.objectName = objectName;
-        this.isMXBean = isMXBean;
+        this.options = options.canonical();
     }
 
     /**
@@ -182,7 +193,16 @@
      * @since 1.6
      */
     public boolean isMXBean() {
-        return isMXBean;
+        return options.isMXBean();
+    }
+
+    /**
+     * <p>Return the {@link MBeanOptions} used for this proxy.</p>
+     *
+     * @return the MBeanOptions.
+     */
+    public MBeanOptions getMBeanOptions() {
+        return options.uncanonical();
     }
 
     /**
@@ -260,7 +280,7 @@
             return doLocally(proxy, method, args);
 
         try {
-            if (isMXBean) {
+            if (isMXBean()) {
                 MXBeanProxy p = findMXBeanProxy(methodClass);
                 return p.invoke(connection, objectName, method, args);
             } else {
@@ -326,21 +346,34 @@
          */
     }
 
-    private static MXBeanProxy findMXBeanProxy(Class<?> mxbeanInterface) {
+    private MXBeanProxy findMXBeanProxy(Class<?> mxbeanInterface) {
+        MXBeanMappingFactory mappingFactory = options.getMXBeanMappingFactory();
         synchronized (mxbeanProxies) {
-            WeakReference<MXBeanProxy> proxyRef =
-                    mxbeanProxies.get(mxbeanInterface);
-            MXBeanProxy p = (proxyRef == null) ? null : proxyRef.get();
-            if (p == null) {
-                p = new MXBeanProxy(mxbeanInterface);
-                mxbeanProxies.put(mxbeanInterface,
-                                  new WeakReference<MXBeanProxy>(p));
+            ClassToProxy classToProxy = mxbeanProxies.get(mappingFactory);
+            if (classToProxy == null) {
+                classToProxy = new ClassToProxy();
+                mxbeanProxies.put(mappingFactory, classToProxy);
             }
+            WeakReference<MXBeanProxy> wr = classToProxy.get(mxbeanInterface);
+            MXBeanProxy p;
+            if (wr != null) {
+                p = wr.get();
+                if (p != null)
+                    return p;
+            }
+            p = new MXBeanProxy(mxbeanInterface, mappingFactory);
+            classToProxy.put(mxbeanInterface, new WeakReference<MXBeanProxy>(p));
             return p;
         }
     }
-    private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>
-            mxbeanProxies = new WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>();
+    private static final WeakHashMap<MXBeanMappingFactory, ClassToProxy>
+            mxbeanProxies = newWeakHashMap();
+    private static class ClassToProxy
+            extends WeakHashMap<Class<?>, WeakReference<MXBeanProxy>> {}
+
+    private static <K, V> WeakHashMap<K, V> newWeakHashMap() {
+        return new WeakHashMap<K, V>();
+    }
 
     private Object invokeBroadcasterMethod(Object proxy, Method method,
                                            Object[] args) throws Exception {
@@ -453,7 +486,7 @@
                 objectName.equals(handler.objectName) &&
                 proxy.getClass().equals(args[0].getClass());
         } else if (methodName.equals("toString")) {
-            return (isMXBean ? "MX" : "M") + "BeanProxy(" +
+            return (isMXBean() ? "MX" : "M") + "BeanProxy(" +
                 connection + "[" + objectName + "])";
         } else if (methodName.equals("hashCode")) {
             return objectName.hashCode()+connection.hashCode();
@@ -484,5 +517,5 @@
 
     private final MBeanServerConnection connection;
     private final ObjectName objectName;
-    private final boolean isMXBean;
+    private final MBeanOptions options;
 }
--- a/src/share/classes/javax/management/MXBean.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/MXBean.java	Thu Jun 05 13:42:47 2008 +0200
@@ -44,6 +44,10 @@
 import javax.management.openmbean.CompositeDataSupport;
 import javax.management.openmbean.CompositeDataView;
 import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingClass;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.MXBeanMappingFactoryClass;
 import javax.management.openmbean.OpenDataException;
 import javax.management.openmbean.OpenMBeanInfo;
 import javax.management.openmbean.OpenType;
@@ -78,7 +82,7 @@
     public interface MisleadingMXBean {}
     </pre>
 
-    <h3><a name="MXBean-spec">MXBean specification</a></h3>
+    <h3 id="MXBean-spec">MXBean specification</h3>
 
     <p>The MXBean concept provides a simple way to code an MBean
       that only references a predefined set of types, the ones defined
@@ -314,7 +318,7 @@
     </table>
 
 
-    <h2><a name="mxbean-def">Definition of an MXBean</a></h2>
+    <h2 id="mxbean-def">Definition of an MXBean</h2>
 
     <p>An MXBean is a kind of MBean.  An MXBean object can be
       registered directly in the MBean Server, or it can be used as an
@@ -367,7 +371,7 @@
       above rules will produce an exception.</p>
 
 
-    <h2><a name="naming-conv">Naming conventions</a></h2>
+    <h2 id="naming-conv">Naming conventions</h2>
 
     <p>The same naming conventions are applied to the methods in an
       MXBean as in a Standard MBean:</p>
@@ -413,7 +417,7 @@
       read-only or write-only respectively.</p>
 
 
-    <h2><a name="mapping-rules">Type mapping rules</a></h2>
+    <h2 id="mapping-rules">Type mapping rules</h2>
 
     <p>An MXBean is a kind of Open MBean, as defined by the {@link
       javax.management.openmbean} package.  This means that the types of
@@ -475,7 +479,11 @@
       from type <em>opendata(J)</em> to type <em>J</em>, a null value is
       mapped to a null value.</p>
 
-    <p>The following table summarizes the type mapping rules.</p>
+    <p>In addition to the default type mapping rules, you can specify
+      custom type mappings, as described <a
+      href="#custom">below</a>.</p>
+
+    <p>The following table summarizes the default type mapping rules.</p>
 
     <table border="1" cellpadding="5">
       <tr>
@@ -658,7 +666,7 @@
       TabularData} that serializes as {@code TabularDataSupport}.</p>
 
 
-    <h3><a name="mxbean-map">Mappings for MXBean interfaces</a></h3>
+    <h3 id="mxbean-map">Mappings for MXBean interfaces</h3>
 
     <p>An MXBean interface, or a type referenced within an MXBean
       interface, can reference another MXBean interface, <em>J</em>.
@@ -747,7 +755,7 @@
       general, notably because it does not work well for MBeans that are
       {@link NotificationBroadcaster}s.</p>
 
-    <h3><a name="composite-map">Mappings for other types</a></h3>
+    <h3 id="composite-map">Mappings for other types</h3>
 
     <p>Given a Java class or interface <em>J</em> that does not match the other
       rules in the table above, the MXBean framework will attempt to map
@@ -1035,6 +1043,76 @@
 }
 </pre>
 
+    <p>Alternatively, you can define a custom mapping for your recursive
+      type; see the next section.</p>
+
+    <h3 id="custom">Custom MXBean type mappings</h3>
+
+    <p>You can augment or replace the default type mappings described
+      above with custom mappings.  An example appears in the
+      documentation for {@link MXBeanMapping}.</p>
+
+    <p>If an MXBean uses custom mappings, then an MXBean proxy for
+      that MXBean must use the same mappings for correct behavior.
+      This requires more careful synchronization between client and
+      server than is necessary with the default mappings.  For example
+      it typically requires the client to have the same implementation
+      of any {@link MXBeanMapping} subclasses as the server.  For this
+      reason, custom mappings should be avoided if possible.</p>
+
+    <p>Every MXBean has an associated {@link MXBeanMappingFactory}.
+      Call this <code><em>f</em></code>.  Then every type that appears
+      in that MXBean has an associated {@link MXBeanMapping}
+      determined by <code><em>f</em></code>.  If the type is
+      <code><em>J</em></code>, say, then the mapping is {@link
+      MXBeanMappingFactory#mappingForType
+      <em>f</em>.mappingForType}<code>(<em>J</em>,
+      <em>f</em>)</code>.</p>
+
+    <p>The {@code MXBeanMappingFactory} <code><em>f</em></code> for an
+      MXBean is determined as follows.</p>
+
+    <ul>
+      <li><p>If an {@link JMX.MBeanOptions} argument is supplied to
+          the {@link StandardMBean} constructor that makes an MXBean,
+          or to the {@link JMX#newMXBeanProxy JMX.newMXBeanProxy}
+          method, and the {@code MBeanOptions} object defines a non-null
+          {@code MXBeanMappingFactory}, then that is the value of
+          <code><em>f</em></code>.</p></li>
+
+      <li><p>Otherwise, if the MXBean interface has an {@link
+          MXBeanMappingFactoryClass} annotation, then that annotation
+          must identify a subclass of {@code MXBeanMappingFactory}
+          with a no-argument constructor.  Then
+          <code><em>f</em></code> is the result of calling this
+          constructor.  If the class does not have a no-argument
+          constructor, or if calling the constructor produces an
+          exception, then the MXBean is invalid and an attempt to
+          register it in the MBean Server will produce a {@link
+          NotCompliantMBeanException}.</p>
+
+        <p>This annotation is not inherited from any parent
+          interfaces.  If an MXBean interface has this annotation,
+          then usually any MXBean subinterfaces must repeat the same
+          annotation for correct behavior.</p></li>
+
+      <li><p>Otherwise, if the package in which the MXBean interface
+          appears has an {@code MXBeanMappingFactoryClass} annotation,
+          then <code><em>f</em></code> is determined as if that
+          annotation appeared on the MXBean interface.</p></li>
+
+      <li><p>Otherwise, <code><em>f</em></code> is the default mapping
+          factory, {@link MXBeanMappingFactory#DEFAULT}.</p></li>
+    </ul>
+
+    <p>The default mapping factory recognizes the {@link
+      MXBeanMappingClass} annotation on a class or interface.  If
+      <code><em>J</em></code> is a class or interface that has such an
+      annotation, then the {@code MXBeanMapping} for
+      <code><em>J</em></code> produced by the default mapping factory
+      will be determined by the value of the annotation as described
+      in its {@linkplain MXBeanMappingClass documentation}.</p>
+
     <h3>MBeanInfo contents for an MXBean</h3>
 
     <p>An MXBean is a type of Open MBean.  However, for compatibility
@@ -1091,7 +1169,7 @@
       {@code mxbean} whose value is the string "{@code true}".</p>
 
 
-    <h3><a name="type-names">Type Names</a></h3>
+    <h3 id="type-names">Type Names</h3>
 
     <p>Sometimes the unmapped type <em>T</em> of a method parameter or
     return value in an MXBean must be represented as a string.  If
@@ -1163,6 +1241,8 @@
       appropriate), or <em>C</em> is true of <em>e</em>.{@link
       Throwable#getCause() getCause()}".</p>
 
+   @see MXBeanMapping
+
    @since 1.6
 */
 
--- a/src/share/classes/javax/management/StandardMBean.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/StandardMBean.java	Thu Jun 05 13:42:47 2008 +0200
@@ -25,22 +25,19 @@
 
 package javax.management;
 
-import static com.sun.jmx.defaults.JmxProperties.MISC_LOGGER;
 import com.sun.jmx.mbeanserver.DescriptorCache;
 import com.sun.jmx.mbeanserver.Introspector;
 import com.sun.jmx.mbeanserver.MBeanSupport;
 import com.sun.jmx.mbeanserver.MXBeanSupport;
 import com.sun.jmx.mbeanserver.StandardMBeanSupport;
 import com.sun.jmx.mbeanserver.Util;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.WeakHashMap;
 import java.util.logging.Level;
+import javax.management.openmbean.MXBeanMappingFactory;
 import javax.management.openmbean.OpenMBeanAttributeInfo;
 import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
 import javax.management.openmbean.OpenMBeanConstructorInfo;
@@ -50,6 +47,9 @@
 import javax.management.openmbean.OpenMBeanParameterInfo;
 import javax.management.openmbean.OpenMBeanParameterInfoSupport;
 
+import static com.sun.jmx.defaults.JmxProperties.MISC_LOGGER;
+import static javax.management.JMX.MBeanOptions;
+
 /**
  * <p>An MBean whose management interface is determined by reflection
  * on a Java interface.</p>
@@ -141,6 +141,11 @@
     private volatile MBeanInfo cachedMBeanInfo;
 
     /**
+     * The MBeanOptions for this StandardMBean.
+     **/
+    private MBeanOptions options;
+
+    /**
      * Make a DynamicMBean out of <var>implementation</var>, using the
      * specified <var>mbeanInterface</var> class.
      * @param implementation The implementation of this MBean.
@@ -155,12 +160,14 @@
      *        implementation is allowed. If null implementation is allowed,
      *        and a null implementation is passed, then the implementation
      *        is assumed to be <var>this</var>.
+     * @param options MBeanOptions to apply to this instance.
      * @exception IllegalArgumentException if the given
      *    <var>implementation</var> is null, and null is not allowed.
      **/
+    @SuppressWarnings("unchecked")  // cast to T
     private <T> void construct(T implementation, Class<T> mbeanInterface,
                                boolean nullImplementationAllowed,
-                               boolean isMXBean)
+                               MBeanOptions options)
                                throws NotCompliantMBeanException {
         if (implementation == null) {
             // Have to use (T)this rather than mbeanInterface.cast(this)
@@ -169,20 +176,23 @@
                 implementation = Util.<T>cast(this);
             else throw new IllegalArgumentException("implementation is null");
         }
-        if (isMXBean) {
-            if (mbeanInterface == null) {
-                mbeanInterface = Util.cast(Introspector.getMXBeanInterface(
-                        implementation.getClass()));
-            }
-            this.mbean = new MXBeanSupport(implementation, mbeanInterface);
+        if (options == null)
+            options = new MBeanOptions();
+        MXBeanMappingFactory mappingFactory = options.getMXBeanMappingFactory();
+        boolean mx = (mappingFactory != null);
+        if (mbeanInterface == null) {
+            mbeanInterface = Util.cast(Introspector.getStandardOrMXBeanInterface(
+                                       implementation.getClass(), mx));
+        }
+        if (mx) {
+            this.mbean =
+                    new MXBeanSupport(implementation, mbeanInterface,
+                                      mappingFactory);
         } else {
-            if (mbeanInterface == null) {
-                mbeanInterface = Util.cast(Introspector.getStandardMBeanInterface(
-                        implementation.getClass()));
-            }
             this.mbean =
                     new StandardMBeanSupport(implementation, mbeanInterface);
         }
+        this.options = options.canonical();
     }
 
     /**
@@ -211,14 +221,14 @@
      **/
     public <T> StandardMBean(T implementation, Class<T> mbeanInterface)
         throws NotCompliantMBeanException {
-        construct(implementation, mbeanInterface, false, false);
+        construct(implementation, mbeanInterface, false, null);
     }
 
     /**
      * <p>Make a DynamicMBean out of <var>this</var>, using the specified
      * <var>mbeanInterface</var> class.</p>
      *
-     * <p>Call {@link #StandardMBean(java.lang.Object, java.lang.Class)
+     * <p>Calls {@link #StandardMBean(java.lang.Object, java.lang.Class)
      *       this(this,mbeanInterface)}.
      * This constructor is reserved to subclasses.</p>
      *
@@ -231,13 +241,14 @@
      **/
     protected StandardMBean(Class<?> mbeanInterface)
         throws NotCompliantMBeanException {
-        construct(null, mbeanInterface, true, false);
+        construct(null, mbeanInterface, true, null);
     }
 
     /**
      * <p>Make a DynamicMBean out of the object
      * <var>implementation</var>, using the specified
-     * <var>mbeanInterface</var> class.  This constructor can be used
+     * <var>mbeanInterface</var> class, and choosing whether the
+     * resultant MBean is an MXBean.  This constructor can be used
      * to make either Standard MBeans or MXBeans.  Unlike the
      * constructor {@link #StandardMBean(Object, Class)}, it
      * does not throw NotCompliantMBeanException.</p>
@@ -267,7 +278,17 @@
     public <T> StandardMBean(T implementation, Class<T> mbeanInterface,
                              boolean isMXBean) {
         try {
-            construct(implementation, mbeanInterface, false, isMXBean);
+            MBeanOptions opts = new MBeanOptions();
+            if (mbeanInterface == null) {
+                mbeanInterface = Util.cast(Introspector.getStandardOrMXBeanInterface(
+                        implementation.getClass(), isMXBean));
+            }
+            if (isMXBean) {
+                MXBeanMappingFactory f = MXBeanMappingFactory.forInterface(
+                        mbeanInterface);
+                opts.setMXBeanMappingFactory(f);
+            }
+            construct(implementation, mbeanInterface, false, opts);
         } catch (NotCompliantMBeanException e) {
             throw new IllegalArgumentException(e);
         }
@@ -275,12 +296,13 @@
 
     /**
      * <p>Make a DynamicMBean out of <var>this</var>, using the specified
-     * <var>mbeanInterface</var> class.  This constructor can be used
+     * <var>mbeanInterface</var> class, and choosing whether the resulting
+     * MBean is an MXBean.  This constructor can be used
      * to make either Standard MBeans or MXBeans.  Unlike the
      * constructor {@link #StandardMBean(Object, Class)}, it
      * does not throw NotCompliantMBeanException.</p>
      *
-     * <p>Call {@link #StandardMBean(java.lang.Object, java.lang.Class, boolean)
+     * <p>Calls {@link #StandardMBean(java.lang.Object, java.lang.Class, boolean)
      *       this(this, mbeanInterface, isMXBean)}.
      * This constructor is reserved to subclasses.</p>
      *
@@ -297,7 +319,77 @@
      **/
     protected StandardMBean(Class<?> mbeanInterface, boolean isMXBean) {
         try {
-            construct(null, mbeanInterface, true, isMXBean);
+            MBeanOptions opts = new MBeanOptions();
+            if (mbeanInterface == null) {
+                mbeanInterface = Introspector.getStandardOrMXBeanInterface(
+                        getClass(), isMXBean);
+            }
+            if (isMXBean) {
+                MXBeanMappingFactory f = MXBeanMappingFactory.forInterface(
+                        mbeanInterface);
+                opts.setMXBeanMappingFactory(f);
+            }
+            construct(null, mbeanInterface, true, opts);
+        } catch (NotCompliantMBeanException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * <p>Make a DynamicMBean out of the object
+     * <var>implementation</var>, using the specified
+     * <var>mbeanInterface</var> class and the specified options.</p>
+     *
+     * @param implementation The implementation of this MBean.
+     * @param mbeanInterface The Management Interface exported by this
+     *        MBean's implementation. If <code>null</code>, then this
+     *        object will use standard JMX design pattern to determine
+     *        the management interface associated with the given
+     *        implementation.
+     * @param options MBeanOptions that control the operation of the resulting
+     *        MBean, as documented in the {@link MBeanOptions} class.
+     * @param <T> Allows the compiler to check
+     * that {@code implementation} does indeed implement the class
+     * described by {@code mbeanInterface}.  The compiler can only
+     * check this if {@code mbeanInterface} is a class literal such
+     * as {@code MyMBean.class}.
+     *
+     * @exception IllegalArgumentException if the given
+     *    <var>implementation</var> is null, or if the <var>mbeanInterface</var>
+     *    does not follow JMX design patterns for Management Interfaces, or
+     *    if the given <var>implementation</var> does not implement the
+     *    specified interface.
+     **/
+    public <T> StandardMBean(T implementation,
+                             Class<T> mbeanInterface,
+                             MBeanOptions options) {
+        try {
+            construct(implementation, mbeanInterface, false, options);
+        } catch (NotCompliantMBeanException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * <p>Make a DynamicMBean out of <var>this</var>, using the specified
+     * <var>mbeanInterface</var> class and the specified options.</p>
+     *
+     * <p>Calls {@link #StandardMBean(Object, Class, JMX.MBeanOptions)
+     *       this(this,mbeanInterface,options)}.
+     * This constructor is reserved to subclasses.</p>
+     *
+     * @param mbeanInterface The Management Interface exported by this
+     *        MBean.
+     * @param options MBeanOptions that control the operation of the resulting
+     *        MBean, as documented in the {@link MBeanOptions} class.
+     *
+     * @exception IllegalArgumentException if the <var>mbeanInterface</var>
+     *    does not follow JMX design patterns for Management Interfaces, or
+     *    if <var>this</var> does not implement the specified interface.
+     **/
+    protected StandardMBean(Class<?> mbeanInterface, MBeanOptions options) {
+        try {
+            construct(null, mbeanInterface, true, options);
         } catch (NotCompliantMBeanException e) {
             throw new IllegalArgumentException(e);
         }
@@ -326,13 +418,19 @@
 
         if (implementation == null)
             throw new IllegalArgumentException("implementation is null");
+        setImplementation2(implementation);
+    }
 
-        if (isMXBean()) {
+    private <T> void setImplementation2(T implementation)
+    throws NotCompliantMBeanException {
+        Class<? super T> intf = Util.cast(getMBeanInterface());
+
+        if (this.mbean.isMXBean()) {
             this.mbean = new MXBeanSupport(implementation,
-                    Util.<Class<Object>>cast(getMBeanInterface()));
+                    intf,
+                    options.getMXBeanMappingFactory());
         } else {
-            this.mbean = new StandardMBeanSupport(implementation,
-                    Util.<Class<Object>>cast(getMBeanInterface()));
+            this.mbean = new StandardMBeanSupport(implementation, intf);
         }
     }
 
@@ -362,6 +460,19 @@
         return mbean.getResource().getClass();
     }
 
+    /**
+     * Return the MBeanOptions that were specified or implied for this StandardMBean
+     * instance.  If an MBeanOptions object was supplied when this StandardMBean
+     * instance was constructed, and if that object has not been modified in the
+     * meantime, then the returned object will be equal to that object, although
+     * it might not be the same object.
+     * @return The MBeanOptions that were specified or implied for this StandardMBean
+     * instance.
+     */
+    public MBeanOptions getOptions() {
+        return options.uncanonical();
+    }
+
     // ------------------------------------------------------------------
     // From the DynamicMBean interface.
     // ------------------------------------------------------------------
@@ -726,7 +837,7 @@
      * @return the MBeanNotificationInfo[] for the new MBeanInfo.
      **/
     MBeanNotificationInfo[] getNotifications(MBeanInfo info) {
-        return null;
+        return info.getNotifications();
     }
 
     /**
@@ -1234,5 +1345,4 @@
             return true;
         }
     }
-
 }
--- a/src/share/classes/javax/management/ToQueryString.java	Thu Jun 05 13:40:09 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/*
- * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Sun designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Sun in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
- * CA 95054 USA or visit www.sun.com if you need additional information or
- * have any questions.
- */
-
-package javax.management;
-
-/* QueryExp classes can extend this to get non-default treatment for
- * Query.toString(q).  We're reluctant to change the public toString()
- * methods of the classes because people might be parsing them, even
- * though that's rather fragile.  But Query.toString(q) has no such
- * constraint so it can use the new toQueryString() method defined here.
- */
-class ToQueryString {
-    String toQueryString() {
-        return toString();
-    }
-}
--- a/src/share/classes/javax/management/openmbean/CompositeDataInvocationHandler.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/openmbean/CompositeDataInvocationHandler.java	Thu Jun 05 13:42:47 2008 +0200
@@ -26,7 +26,7 @@
 package javax.management.openmbean;
 
 import com.sun.jmx.mbeanserver.MXBeanLookup;
-import com.sun.jmx.mbeanserver.OpenConverter;
+import com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
@@ -115,7 +115,12 @@
        is null.
     */
     public CompositeDataInvocationHandler(CompositeData compositeData) {
-        this(compositeData, null);
+        this(compositeData, MXBeanMappingFactory.DEFAULT);
+    }
+
+    public CompositeDataInvocationHandler(CompositeData compositeData,
+                                          MXBeanMappingFactory mappingFactory) {
+        this(compositeData, mappingFactory, null);
     }
 
     /**
@@ -134,11 +139,13 @@
        is null.
     */
     CompositeDataInvocationHandler(CompositeData compositeData,
+                                   MXBeanMappingFactory mappingFactory,
                                    MXBeanLookup lookup) {
         if (compositeData == null)
             throw new IllegalArgumentException("compositeData");
         this.compositeData = compositeData;
         this.lookup = lookup;
+        this.mappingFactory = mappingFactory;
     }
 
     /**
@@ -176,7 +183,7 @@
             }
         }
 
-        String propertyName = OpenConverter.propertyName(method);
+        String propertyName = DefaultMXBeanMappingFactory.propertyName(method);
         if (propertyName == null) {
             throw new IllegalArgumentException("Method is not getter: " +
                                                method.getName());
@@ -185,7 +192,7 @@
         if (compositeData.containsKey(propertyName))
             openValue = compositeData.get(propertyName);
         else {
-            String decap = OpenConverter.decapitalize(propertyName);
+            String decap = DefaultMXBeanMappingFactory.decapitalize(propertyName);
             if (compositeData.containsKey(decap))
                 openValue = compositeData.get(decap);
             else {
@@ -196,9 +203,10 @@
                 throw new IllegalArgumentException(msg);
             }
         }
-        OpenConverter converter =
-            OpenConverter.toConverter(method.getGenericReturnType());
-        return converter.fromOpenValue(lookup, openValue);
+        MXBeanMapping mapping =
+            mappingFactory.mappingForType(method.getGenericReturnType(),
+                                   MXBeanMappingFactory.DEFAULT);
+        return mapping.fromOpenValue(openValue);
     }
 
     /* This method is called when equals(Object) is
@@ -242,4 +250,5 @@
 
     private final CompositeData compositeData;
     private final MXBeanLookup lookup;
+    private final MXBeanMappingFactory mappingFactory;
 }
--- a/src/share/classes/javax/management/openmbean/CompositeType.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/openmbean/CompositeType.java	Thu Jun 05 13:42:47 2008 +0200
@@ -159,8 +159,8 @@
     }
 
     private static void checkForNullElement(Object[] arg, String argName) {
-        if ( (arg == null) || (arg.length == 0) ) {
-            throw new IllegalArgumentException("Argument "+ argName +"[] cannot be null or empty.");
+        if (arg == null) {
+            throw new IllegalArgumentException("Argument "+ argName +"[] cannot be null.");
         }
         for (int i=0; i<arg.length; i++) {
             if (arg[i] == null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/openmbean/MXBeanMapping.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.openmbean;
+
+import java.io.InvalidObjectException;
+import java.lang.reflect.Type;
+
+/**
+ * <p>A custom mapping between Java types and Open types for use in MXBeans.
+ * To define such a mapping, subclass this class and define at least the
+ * {@link #fromOpenValue fromOpenValue} and {@link #toOpenValue toOpenValue}
+ * methods, and optionally the {@link #checkReconstructible} method.
+ * Then either use an {@link MXBeanMappingClass} annotation on your custom
+ * Java types, or include this MXBeanMapping in an
+ * {@link MXBeanMappingFactory}.</p>
+ *
+ * <p>For example, suppose we have a class {@code MyLinkedList}, which looks
+ * like this:</p>
+ *
+ * <pre>
+ * public class MyLinkedList {
+ *     public MyLinkedList(String name, MyLinkedList next) {...}
+ *     public String getName() {...}
+ *     public MyLinkedList getNext() {...}
+ * }
+ * </pre>
+ *
+ * <p>This is not a valid type for MXBeans, because it contains a
+ * self-referential property "next" defined by the {@code getNext()}
+ * method.  MXBeans do not support recursive types.  So we would like
+ * to specify a mapping for {@code MyLinkedList} explicitly. When an
+ * MXBean interface contains {@code MyLinkedList}, that will be mapped
+ * into a {@code String[]}, which is a valid Open Type.</p>
+ *
+ * <p>To define this mapping, we first subclass {@code MXBeanMapping}:</p>
+ *
+ * <pre>
+ * public class MyLinkedListMapping extends MXBeanMapping {
+ *     public MyLinkedListMapping(Type type) throws OpenDataException {
+ *         super(MyLinkedList.class, ArrayType.getArrayType(SimpleType.STRING));
+ *         if (type != MyLinkedList.class)
+ *             throw new OpenDataException("Mapping only valid for MyLinkedList");
+ *     }
+ *
+ *     {@literal @Override}
+ *     public Object fromOpenValue(Object openValue) throws InvalidObjectException {
+ *         String[] array = (String[]) openValue;
+ *         MyLinkedList list = null;
+ *         for (int i = array.length - 1; i &gt;= 0; i--)
+ *             list = new MyLinkedList(array[i], list);
+ *         return list;
+ *     }
+ *
+ *     {@literal @Override}
+ *     public Object toOpenValue(Object javaValue) throws OpenDataException {
+ *         ArrayList&lt;String&gt; array = new ArrayList&lt;String&gt;();
+ *         for (MyLinkedList list = (MyLinkedList) javaValue; list != null;
+ *              list = list.getNext())
+ *             array.add(list.getName());
+ *         return array.toArray(new String[0]);
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>The call to the superclass constructor specifies what the
+ * original Java type is ({@code MyLinkedList.class}) and what Open
+ * Type it is mapped to ({@code
+ * ArrayType.getArrayType(SimpleType.STRING)}). The {@code
+ * fromOpenValue} method says how we go from the Open Type ({@code
+ * String[]}) to the Java type ({@code MyLinkedList}), and the {@code
+ * toOpenValue} method says how we go from the Java type to the Open
+ * Type.</p>
+ *
+ * <p>With this mapping defined, we can annotate the {@code MyLinkedList}
+ * class appropriately:</p>
+ *
+ * <pre>
+ * {@literal @MXBeanMappingClass}(MyLinkedListMapping.class)
+ * public class MyLinkedList {...}
+ * </pre>
+ *
+ * <p>Now we can use {@code MyLinkedList} in an MXBean interface and it
+ * will work.</p>
+ *
+ * <p>If we are unable to modify the {@code MyLinkedList} class,
+ * we can define an {@link MXBeanMappingFactory}.  See the documentation
+ * of that class for further details.</p>
+ */
+public abstract class MXBeanMapping {
+    private final Type javaType;
+    private final OpenType<?> openType;
+    private final Class<?> openClass;
+
+    /**
+     * <p>Construct a mapping between the given Java type and the given
+     * Open Type.</p>
+     *
+     * @param javaType the Java type (for example, {@code MyLinkedList}).
+     * @param openType the Open Type (for example, {@code
+     * ArrayType.getArrayType(SimpleType.STRING)})
+     *
+     * @throws NullPointerException if either argument is null.
+     */
+    protected MXBeanMapping(Type javaType, OpenType<?> openType) {
+        if (javaType == null || openType == null)
+            throw new NullPointerException("Null argument");
+        this.javaType = javaType;
+        this.openType = openType;
+        this.openClass = makeOpenClass(javaType, openType);
+    }
+
+    /**
+     * <p>The Java type that was supplied to the constructor.</p>
+     * @return the Java type that was supplied to the constructor.
+     */
+    public final Type getJavaType() {
+        return javaType;
+    }
+
+    /**
+     * <p>The Open Type that was supplied to the constructor.</p>
+     * @return the Open Type that was supplied to the constructor.
+     */
+    public final OpenType<?> getOpenType() {
+        return openType;
+    }
+
+    /**
+     * <p>The Java class that corresponds to instances of the
+     * {@linkplain #getOpenType() Open Type} for this mapping.</p>
+     * @return the Java class that corresponds to instances of the
+     * Open Type for this mapping.
+     * @see OpenType#getClassName
+     */
+    public final Class<?> getOpenClass() {
+        return openClass;
+    }
+
+    private static Class<?> makeOpenClass(Type javaType, OpenType<?> openType) {
+        if (javaType instanceof Class<?> && ((Class<?>) javaType).isPrimitive())
+            return (Class<?>) javaType;
+        try {
+            String className = OpenType.validClassName(openType.getClassName());
+            return Class.forName(className, false, null);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);  // should not happen
+        } catch (OpenDataException e) {
+            throw new IllegalArgumentException("Bad OpenType: " + openType, e);
+        }
+    }
+
+    /**
+     * <p>Convert an instance of the Open Type into the Java type.
+     * @param openValue the value to be converted.
+     * @return the converted value.
+     * @throws InvalidObjectException if the value cannot be converted.
+     */
+    public abstract Object fromOpenValue(Object openValue)
+    throws InvalidObjectException;
+
+    /**
+     * <p>Convert an instance of the Java type into the Open Type.
+     * @param javaValue the value to be converted.
+     * @return the converted value.
+     * @throws OpenDataException if the value cannot be converted.
+     */
+    public abstract Object toOpenValue(Object javaValue)
+    throws OpenDataException;
+
+
+    /**
+     * <p>Throw an appropriate InvalidObjectException if we will not
+     * be able to convert back from the open data to the original Java
+     * object.  The {@link #fromOpenValue fromOpenValue} throws an
+     * exception if a given open data value cannot be converted.  This
+     * method throws an exception if <em>no</em> open data values can
+     * be converted.  The default implementation of this method never
+     * throws an exception.  Subclasses can override it as
+     * appropriate.</p>
+     * @throws InvalidObjectException if {@code fromOpenValue} will throw
+     * an exception no matter what its argument is.
+     */
+    public void checkReconstructible() throws InvalidObjectException {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/openmbean/MXBeanMappingClass.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.openmbean;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.management.NotCompliantMBeanException;
+
+/**
+ * Specifies the MXBean mapping to be used for this Java type.
+ * @see MXBeanMapping
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented @Inherited
+public @interface MXBeanMappingClass {
+    /**
+     * <p>The {@link MXBeanMapping} class to be used to map the
+     * annotated type.  This class must have a public constructor with
+     * a single argument of type {@link java.lang.reflect.Type}.  The
+     * constructor will be called with the annotated type as an
+     * argument.  See the {@code MXBeanMapping} documentation
+     * for an example.</p>
+     *
+     * <p>If the {@code MXBeanMapping} cannot in fact handle that
+     * type, the constructor should throw an {@link
+     * OpenDataException}.  If the constructor throws this or any other
+     * exception then an MXBean in which the annotated type appears is
+     * invalid, and registering it in the MBean Server will produce a
+     * {@link NotCompliantMBeanException}.
+     */
+    public Class<? extends MXBeanMapping> value();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.openmbean;
+
+import com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory;
+import java.lang.reflect.Type;
+
+/**
+ * <p>Defines how types are mapped for a given MXBean or set of MXBeans.
+ * An {@code MXBeanMappingFactory} can be specified either through the
+ * {@link MXBeanMappingFactoryClass} annotation, or through the
+ * {@link javax.management.JMX.MBeanOptions JMX.MBeanOptions} argument to a
+ * {@link javax.management.StandardMBean StandardMBean} constructor or MXBean
+ * proxy.</p>
+ *
+ * <p>An {@code MXBeanMappingFactory} must return an {@code MXBeanMapping}
+ * for any Java type that appears in the MXBeans that the factory is being
+ * used for.  Usually it does that by handling any custom types, and
+ * forwarding everything else to the {@linkplain #DEFAULT default mapping
+ * factory}.</p>
+ *
+ * <p>Consider the {@code MyLinkedList} example from the {@link MXBeanMapping}
+ * documentation.  If we are unable to change the {@code MyLinkedList} class
+ * to add an {@link MXBeanMappingClass} annotation, we could achieve the same
+ * effect by defining {@code MyLinkedListMappingFactory} as follows:</p>
+ *
+ * <pre>
+ * public class MyLinkedListMappingFactory implements MXBeanMappingFactory {
+ *     public MyLinkedListMappingFactory() {}
+ *
+ *     public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
+ *     throws OpenDataException {
+ *         if (t == MyLinkedList.class)
+ *             return new MyLinkedListMapping(t);
+ *         else
+ *             return MXBeanMappingFactory.DEFAULT.mappingForType(t, f);
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>The mapping factory handles only the {@code MyLinkedList} class.
+ * Every other type is forwarded to the default mapping factory.
+ * This includes types such as {@code MyLinkedList[]} and
+ * {@code List<MyLinkedList>}; the default mapping factory will recursively
+ * invoke {@code MyLinkedListMappingFactory} to map the contained
+ * {@code MyLinkedList} type.</p>
+ *
+ * <p>Once we have defined {@code MyLinkedListMappingFactory}, we can use
+ * it in an MXBean interface like this:</p>
+ *
+ * <pre>
+ * {@literal @MXBeanMappingFactoryClass}(MyLinkedListMappingFactory.class)
+ * public interface SomethingMXBean {
+ *     public MyLinkedList getSomething();
+ * }
+ * </pre>
+ *
+ * <p>Alternatively we can annotate the package that {@code SomethingMXBean}
+ * appears in, or we can supply the factory to a {@link
+ * javax.management.StandardMBean StandardMBean} constructor or MXBean
+ * proxy.</p>
+ */
+public abstract class MXBeanMappingFactory {
+    /**
+     * <p>Construct an instance of this class.</p>
+     */
+    protected MXBeanMappingFactory() {}
+
+    /**
+     * <p>Mapping factory that applies the default rules for MXBean
+     * mappings, as described in the <a
+     * href="../MXBean.html#MXBean-spec">MXBean specification</a>.</p>
+     */
+    public static final MXBeanMappingFactory DEFAULT =
+            new DefaultMXBeanMappingFactory();
+
+    /**
+     * <p>Determine the appropriate MXBeanMappingFactory to use for the given
+     * MXBean interface, based on its annotations.  If the interface has an
+     * {@link MXBeanMappingFactoryClass @MXBeanMappingFactoryClass} annotation,
+     * that is used to determine the MXBeanMappingFactory.  Otherwise, if the
+     * package containing the interface has such an annotation, that is used.
+     * Otherwise the MXBeanMappingFactory is the {@linkplain #DEFAULT default}
+     * one.</p>
+     *
+     * @param intf the MXBean interface for which to determine the
+     * MXBeanMappingFactory.
+     *
+     * @return the MXBeanMappingFactory for the given MXBean interface.
+     *
+     * @throws IllegalArgumentException if {@code intf} is null, or if an
+     * exception occurs while trying constructing an MXBeanMappingFactory
+     * based on an annotation.  In the second case, the exception will appear
+     * in the {@linkplain Throwable#getCause() cause chain} of the
+     * {@code IllegalArgumentException}.
+     */
+    public static MXBeanMappingFactory forInterface(Class<?> intf) {
+        if (intf == null)
+            throw new IllegalArgumentException("Null interface");
+        MXBeanMappingFactoryClass annot =
+                intf.getAnnotation(MXBeanMappingFactoryClass.class);
+        if (annot == null) {
+            Package p = intf.getPackage();
+            if (p != null)
+                annot = p.getAnnotation(MXBeanMappingFactoryClass.class);
+        }
+        if (annot == null)
+            return MXBeanMappingFactory.DEFAULT;
+        Class<? extends MXBeanMappingFactory> factoryClass = annot.value();
+        try {
+            return annot.value().newInstance();
+        } catch (Exception e) {
+            throw new IllegalArgumentException(
+                    "Could not instantiate MXBeanMappingFactory " +
+                    factoryClass.getName() +
+                    " from @MXBeanMappingFactoryClass", e);
+        }
+    }
+
+    /**
+     * <p>Return the mapping for the given Java type.  Typically, a
+     * mapping factory will return mappings for types it handles, and
+     * forward other types to another mapping factory, most often
+     * the {@linkplain #DEFAULT default one}.</p>
+     * @param t the Java type to be mapped.
+     * @param f the original mapping factory that was consulted to do
+     * the mapping.  A mapping factory should pass this parameter intact
+     * if it forwards a type to another mapping factory.  In the example,
+     * this is how {@code MyLinkedListMappingFactory} works for types
+     * like {@code MyLinkedList[]} and {@code List<MyLinkedList>}.
+     * @return the mapping for the given type.
+     * @throws OpenDataException if this type cannot be mapped.  This
+     * exception is appropriate if the factory is supposed to handle
+     * all types of this sort (for example, all linked lists), but
+     * cannot handle this particular type.
+     */
+    public abstract MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
+    throws OpenDataException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/openmbean/MXBeanMappingFactoryClass.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.openmbean;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Specifies the MXBean mapping factory to be used for Java types
+ * in an MXBean interface, or in all MXBean interfaces in a package.</p>
+ *
+ * <p>Applying a mapping factory to all Java types in an MXBean interface
+ * looks like this:</p>
+ *
+ * <pre>
+ * {@literal @MXBeanMappingFactoryClass}(MyLinkedListMappingFactory.class)
+ * public interface SomethingMXBean {
+ *     public MyLinkedList getSomething();
+ * }
+ * </pre>
+ *
+ * <p>Applying a mapping factory to all Java types in all MXBean interfaces
+ * in a package, say {@code com.example.mxbeans}, looks like this.  In the
+ * package source directory, create a file called {@code package-info.java}
+ * with these contents:</p>
+ *
+ * <pre>
+ * {@literal @MXBeanMappingFactoryClass}(MyLinkedListMappingFactory.class)
+ * package com.example.mxbeans;
+ * </pre>
+ *
+ * @see MXBeanMappingFactory
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.PACKAGE})
+@Documented @Inherited
+public @interface MXBeanMappingFactoryClass {
+    /**
+     * <p>The {@link MXBeanMappingFactory} class to be used to map
+     * types in the annotated interface or package.  This class must
+     * have a public constructor with no arguments.  See the {@code
+     * MXBeanMappingFactory} documentation for an example.</p>
+     */
+    public Class<? extends MXBeanMappingFactory> value();
+}
--- a/src/share/classes/javax/management/openmbean/OpenType.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/src/share/classes/javax/management/openmbean/OpenType.java	Thu Jun 05 13:42:47 2008 +0200
@@ -219,7 +219,7 @@
         });
     }
 
-    private static String validClassName(String className) throws OpenDataException {
+    static String validClassName(String className) throws OpenDataException {
         className   = valid("className", className);
 
         // Check if className describes an array class, and determines its elements' class name.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/CustomTypeTest.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,590 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/* @test %M% %I%
+ * @bug 6562936
+ * @run compile customtypes/package-info.java
+ * @run main CustomTypeTest
+ */
+
+import java.io.InvalidObjectException;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+import javax.management.Descriptor;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingClass;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.MXBeanMappingFactoryClass;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+
+import static javax.management.JMX.MBeanOptions;
+
+import customtypes.*;
+
+public class CustomTypeTest {
+    @MXBeanMappingClass(LinkedListMapping.class)
+    public static class LinkedList {
+        private final String name;
+        private final LinkedList next;
+
+        public LinkedList(String name, LinkedList next) {
+            this.name = name;
+            this.next = next;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public LinkedList getNext() {
+            return next;
+        }
+
+        public String toString() {
+            if (next == null)
+                return "(" + name + ")";
+            else
+                return "(" + name + " " + next + ")";
+        }
+
+        public boolean equals(Object x) {
+            if (!(x instanceof LinkedList))
+                return false;
+            LinkedList other = (LinkedList) x;
+            return (this.name.equals(other.name) &&
+                    (this.next == null ? other.next == null :
+                        this.next.equals(other.next)));
+        }
+    }
+
+    public static class LinkedListMapping extends MXBeanMapping {
+        public LinkedListMapping(Type type) throws OpenDataException {
+            super(LinkedList.class, ArrayType.getArrayType(SimpleType.STRING));
+            if (type != LinkedList.class) {
+                throw new OpenDataException("Mapping only valid for " +
+                        LinkedList.class);
+            }
+        }
+
+        public Object fromOpenValue(Object openValue) throws InvalidObjectException {
+            String[] array = (String[]) openValue;
+            LinkedList list = null;
+            for (int i = array.length - 1; i >= 0; i--)
+                list = new LinkedList(array[i], list);
+            return list;
+        }
+
+        public Object toOpenValue(Object javaValue) throws OpenDataException {
+            ArrayList<String> array = new ArrayList<String>();
+            for (LinkedList list = (LinkedList) javaValue; list != null;
+                    list = list.getNext())
+                array.add(list.getName());
+            return array.toArray(new String[0]);
+        }
+    }
+
+    public static interface LinkedListMXBean {
+        public LinkedList getLinkedList();
+    }
+
+    public static class LinkedListImpl implements LinkedListMXBean {
+        public LinkedList getLinkedList() {
+            return new LinkedList("car", new LinkedList("cdr", null));
+        }
+    }
+
+    public static class ObjectMXBeanMapping extends MXBeanMapping {
+        private static final CompositeType wildcardType;
+
+        static {
+            try {
+                wildcardType =
+                    new CompositeType(Object.class.getName(),
+                                      "Wildcard type for Object",
+                                      new String[0],        // itemNames
+                                      new String[0],        // itemDescriptions
+                                      new OpenType<?>[0]);  // itemTypes
+            } catch (OpenDataException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public ObjectMXBeanMapping() {
+            super(Object.class, wildcardType);
+        }
+
+        public Object fromOpenValue(Object openValue) throws InvalidObjectException {
+            if (!(openValue instanceof CompositeData)) {
+                throw new InvalidObjectException("Not a CompositeData: " +
+                        openValue.getClass());
+            }
+            CompositeData cd = (CompositeData) openValue;
+            if (!cd.containsKey("value")) {
+                throw new InvalidObjectException("CompositeData does not " +
+                        "contain a \"value\" item: " + cd);
+            }
+            Object x = cd.get("value");
+            if (!(x instanceof CompositeData || x instanceof TabularData ||
+                    x instanceof Object[]))
+                return x;
+
+            String typeName = (String) cd.get("type");
+            if (typeName == null) {
+                throw new InvalidObjectException("CompositeData does not " +
+                        "contain a \"type\" item: " + cd);
+            }
+            Class<?> c;
+            try {
+                c = Class.forName(typeName);
+            } catch (ClassNotFoundException e) {
+                InvalidObjectException ioe =
+                        new InvalidObjectException("Could not find type");
+                ioe.initCause(e);
+                throw ioe;
+            }
+            MXBeanMapping mapping;
+            try {
+                mapping = objectMappingFactory.mappingForType(c, objectMappingFactory);
+            } catch (OpenDataException e) {
+                InvalidObjectException ioe =
+                        new InvalidObjectException("Could not map object's " +
+                            "type " + c.getName());
+                ioe.initCause(e);
+                throw ioe;
+            }
+            return mapping.fromOpenValue(x);
+        }
+
+        public Object toOpenValue(Object javaValue) throws OpenDataException {
+            OpenType<?> openType;
+            Object openValue;
+            String typeName;
+            if (javaValue == null) {
+                openType = SimpleType.VOID;
+                openValue = null;
+                typeName = null;
+            } else {
+                Class<?> c = javaValue.getClass();
+                if (c.equals(Object.class))
+                    throw new OpenDataException("Cannot map Object to an open value");
+                MXBeanMapping mapping =
+                        objectMappingFactory.mappingForType(c, objectMappingFactory);
+                openType = mapping.getOpenType();
+                openValue = mapping.toOpenValue(javaValue);
+                typeName = c.getName();
+            }
+            CompositeType ct = new CompositeType(
+                    (javaValue == null) ? "null" : openType.getClassName(),
+                    "Open Mapping for Object",
+                    new String[] {"type", "value"},
+                    new String[] {"type", "value"},
+                    new OpenType<?>[] {SimpleType.STRING, openType});
+            return new CompositeDataSupport(
+                    ct,
+                    new String[] {"type", "value"},
+                    new Object[] {typeName, openValue});
+        }
+    }
+
+    public static class ObjectMappingFactory extends MXBeanMappingFactory {
+        private static MXBeanMapping objectMapping =
+                new ObjectMXBeanMapping();
+
+        @Override
+        public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
+        throws OpenDataException {
+            if (t.equals(Object.class))
+                return objectMapping;
+            else
+                return MXBeanMappingFactory.DEFAULT.mappingForType(t, f);
+        }
+    }
+
+    private static MXBeanMappingFactory objectMappingFactory =
+            new ObjectMappingFactory();
+
+    public static interface ObjectMXBean {
+        public Object getObject();
+        public Object[] getObjects();
+        public List<Object> getObjectList();
+        public Object[][] getMoreObjects();
+    }
+
+    public static class ObjectImpl implements ObjectMXBean {
+        public Object getObject() {
+            return 123;
+        }
+
+        private static Object[] objects = {
+            "foo", 3, 3.14f, 3.14, 3L, new Date(), ObjectName.WILDCARD,
+            new byte[3], new char[3], new int[3][3],
+            new LinkedListImpl().getLinkedList(),
+        };
+
+        public Object[] getObjects() {
+            return objects;
+        }
+
+        public List<Object> getObjectList() {
+            return Arrays.asList(getObjects());
+        }
+
+        public Object[][] getMoreObjects() {
+            return new Object[][] {{getObjects()}};
+        }
+    }
+
+    @MXBeanMappingFactoryClass(ObjectMappingFactory.class)
+    public static interface AnnotatedObjectMXBean extends ObjectMXBean {}
+
+    public static class AnnotatedObjectImpl extends ObjectImpl
+            implements AnnotatedObjectMXBean {}
+
+    public static class BrokenMappingFactory extends MXBeanMappingFactory {
+        public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
+        throws OpenDataException {
+            throw new OpenDataException(t.toString());
+        }
+    }
+
+    public static class ReallyBrokenMappingFactory extends BrokenMappingFactory {
+        public ReallyBrokenMappingFactory() {
+            throw new RuntimeException("Oops");
+        }
+    }
+
+    @MXBeanMappingFactoryClass(BrokenMappingFactory.class)
+    public static interface BrokenMXBean {
+        public int getX();
+    }
+
+    public static class BrokenImpl implements BrokenMXBean {
+        public int getX() {return 0;}
+    }
+
+    @MXBeanMappingFactoryClass(ReallyBrokenMappingFactory.class)
+    public static interface ReallyBrokenMXBean {
+        public int getX();
+    }
+
+    public static class ReallyBrokenImpl implements ReallyBrokenMXBean {
+        public int getX() {return 0;}
+    }
+
+    public static class BrokenMapping extends MXBeanMapping {
+        public BrokenMapping(Type t) {
+            super(t, SimpleType.STRING);
+            throw new RuntimeException("Oops");
+        }
+
+        public Object fromOpenValue(Object openValue) throws InvalidObjectException {
+            throw new AssertionError();
+        }
+
+        public Object toOpenValue(Object javaValue) throws OpenDataException {
+            throw new AssertionError();
+        }
+    }
+
+    @MXBeanMappingClass(BrokenMapping.class)
+    public static class BrokenType {}
+
+    public static interface BrokenTypeMXBean {
+        BrokenType getBroken();
+    }
+
+    public static class BrokenTypeImpl implements BrokenTypeMXBean {
+        public BrokenType getBroken() {
+            throw new AssertionError();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+
+        System.out.println("Test @MXBeanMappingClass");
+        ObjectName linkedName = new ObjectName("d:type=LinkedList");
+        LinkedListMXBean linkedListMXBean = new LinkedListImpl();
+        LinkedList list1 = linkedListMXBean.getLinkedList();
+        mbs.registerMBean(linkedListMXBean, linkedName);
+        LinkedListMXBean linkedProxy =
+                JMX.newMXBeanProxy(mbs, linkedName, LinkedListMXBean.class);
+        MBeanServerInvocationHandler mbsih = (MBeanServerInvocationHandler)
+                Proxy.getInvocationHandler(linkedProxy);
+        if (!mbsih.isMXBean())
+            fail("not MXBean proxy");
+        LinkedList list2 = linkedProxy.getLinkedList();
+        if (list1 == list2)
+            fail("lists identical!");
+            // They should have gone through the mapping and back,
+            // and the mapping doesn't do anything that would allow it
+            // to restore the identical object.
+        if (!list1.equals(list2))
+            fail("lists different: " + list1 + " vs " + list2);
+        System.out.println("...success");
+
+        System.out.println("Test StandardMBean with MXBeanMappingFactory");
+        ObjectMXBean wildcardMBean = new ObjectImpl();
+        MBeanOptions options = new MBeanOptions();
+        options.setMXBeanMappingFactory(objectMappingFactory);
+        if (!options.isMXBean())
+            fail("Setting MXBeanMappingFactory should imply MXBean");
+        StandardMBean wildcardStandardMBean =
+                new StandardMBean(wildcardMBean, ObjectMXBean.class, options);
+        testWildcardMBean(mbs, wildcardMBean, wildcardStandardMBean,
+                          options, ObjectMXBean.class);
+
+        System.out.println("Test @MXBeanMappingFactoryClass on interface");
+        ObjectMXBean annotatedWildcardMBean = new AnnotatedObjectImpl();
+        testWildcardMBean(mbs, annotatedWildcardMBean, annotatedWildcardMBean,
+                          null, AnnotatedObjectMXBean.class);
+
+        System.out.println("Test @MXBeanMappingFactoryClass on package");
+        CustomMXBean custom = zeroProxy(CustomMXBean.class);
+        ObjectName customName = new ObjectName("d:type=Custom");
+        mbs.registerMBean(custom, customName);
+        Object x = mbs.getAttribute(customName, "X");
+        if (!(x instanceof String))
+            fail("Should be String: " + x + " (a " + x.getClass().getName() + ")");
+        CustomMXBean customProxy =
+                JMX.newMXBeanProxy(mbs, customName, CustomMXBean.class);
+        x = customProxy.getX();
+        if (!(x instanceof Integer) || (Integer) x != 0)
+            fail("Wrong return from proxy: " + x + " (a " + x.getClass().getName() + ")");
+
+        System.out.println("Test MXBeanMappingFactory exception");
+        try {
+            mbs.registerMBean(new BrokenImpl(), new ObjectName("d:type=Broken"));
+            fail("Register did not throw exception");
+        } catch (NotCompliantMBeanException e) {
+            System.out.println("...OK: threw: " + e);
+        }
+
+        System.out.println("Test MXBeanMappingFactory constructor exception");
+        try {
+            mbs.registerMBean(new ReallyBrokenImpl(), new ObjectName("d:type=Broken"));
+            fail("Register did not throw exception");
+        } catch (IllegalArgumentException e) {
+            System.out.println("...OK: threw: " + e);
+        }
+
+        System.out.println("Test MXBeanMappingFactory exception with StandardMBean");
+        MXBeanMappingFactory brokenF = new BrokenMappingFactory();
+        MBeanOptions brokenO = new MBeanOptions();
+        brokenO.setMXBeanMappingFactory(brokenF);
+        try {
+            new StandardMBean(wildcardMBean, ObjectMXBean.class, brokenO);
+            fail("StandardMBean with broken factory did not throw exception");
+        } catch (IllegalArgumentException e) {
+            if (!(e.getCause() instanceof NotCompliantMBeanException)) {
+                fail("StandardMBean with broken factory threw wrong exception: "
+                        + e.getCause());
+            }
+        }
+
+        System.out.println("Test MXBeanMappingClass exception");
+        try {
+            mbs.registerMBean(new BrokenTypeImpl(), new ObjectName("d:type=Broken"));
+            fail("Broken MXBeanMappingClass did not throw exception");
+        } catch (NotCompliantMBeanException e) {
+            System.out.println("...OK: threw: " + e);
+        }
+
+        if (failure == null)
+            System.out.println("TEST PASSED");
+        else
+            throw new Exception("TEST FAILED: " + failure);
+    }
+
+    private static void testWildcardMBean(MBeanServer mbs, ObjectMXBean impl,
+                                          Object mbean,
+                                          MBeanOptions proxyOptions,
+                                          Class<? extends ObjectMXBean> intf)
+    throws Exception {
+        ObjectName wildcardName = new ObjectName("d:type=Object");
+        mbs.registerMBean(mbean, wildcardName);
+        try {
+            testWildcardMBean2(mbs, impl, wildcardName, proxyOptions, intf);
+        } finally {
+            mbs.unregisterMBean(wildcardName);
+        }
+    }
+
+    private static void testWildcardMBean2(MBeanServer mbs, ObjectMXBean impl,
+                                           ObjectName wildcardName,
+                                           MBeanOptions proxyOptions,
+                                           Class<? extends ObjectMXBean> intf)
+    throws Exception {
+        if (proxyOptions == null) {
+            proxyOptions = new MBeanOptions();
+            MXBeanMappingFactory f = MXBeanMappingFactory.forInterface(intf);
+            proxyOptions.setMXBeanMappingFactory(f);
+        }
+        Descriptor d = mbs.getMBeanInfo(wildcardName).getDescriptor();
+        String factoryName = (String)
+                d.getFieldValue(JMX.MXBEAN_MAPPING_FACTORY_CLASS_FIELD);
+        if (!ObjectMappingFactory.class.getName().equals(factoryName)) {
+            fail("Descriptor has wrong MXBeanMappingFactory: " + factoryName +
+                    " should be " + ObjectMappingFactory.class.getName());
+        }
+        ObjectMXBean wildcardProxy =
+            JMX.newMBeanProxy(mbs, wildcardName, intf, proxyOptions);
+        MBeanServerInvocationHandler mbsih = (MBeanServerInvocationHandler)
+                Proxy.getInvocationHandler(wildcardProxy);
+        MBeanOptions opts = mbsih.getMBeanOptions();
+        if (!opts.equals(proxyOptions)) {
+            fail("Proxy options differ from request: " + opts + " vs " +
+                    proxyOptions);
+        }
+        Method[] wildcardMethods = ObjectMXBean.class.getMethods();
+        for (Method m : wildcardMethods) {
+            System.out.println("..." + m.getName());
+            Object orig = m.invoke(impl);
+            Object copy = m.invoke(wildcardProxy);
+            if (!deepEquals(orig, copy)) {
+                fail("objects differ: " + deepToString(orig) + " vs " +
+                        deepToString(copy));
+            }
+        }
+    }
+
+    private static <T> T zeroProxy(Class<T> intf) {
+        return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(),
+                new Class<?>[] {intf},
+                new ZeroInvocationHandler()));
+    }
+
+    private static class ZeroInvocationHandler implements InvocationHandler {
+        public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+            return 0;
+        }
+    }
+
+    private static boolean deepEquals(Object x, Object y) {
+        if (x == y)
+            return true;
+        if (x == null || y == null)
+            return false;
+
+        if (x instanceof Collection<?>) {
+            if (!(y instanceof Collection<?>))
+                return false;
+            Collection<?> xcoll = (Collection<?>) x;
+            Collection<?> ycoll = (Collection<?>) y;
+            if (xcoll.size() != ycoll.size())
+                return false;
+            Iterator<?> xit = xcoll.iterator();
+            Iterator<?> yit = ycoll.iterator();
+            while (xit.hasNext()) {
+                if (!deepEquals(xit.next(), yit.next()))
+                    return false;
+            }
+            return true;
+        }
+
+        Class<?> xclass = x.getClass();
+        Class<?> yclass = y.getClass();
+        if (xclass.isArray()) {
+            if (!yclass.isArray())
+                return false;
+            if (!xclass.getComponentType().equals(yclass.getComponentType()))
+                return false;
+            int len = Array.getLength(x);
+            if (Array.getLength(y) != len)
+                return false;
+            for (int i = 0; i < len; i++) {
+                if (!deepEquals(Array.get(x, i), Array.get(y, i)))
+                    return false;
+            }
+            return true;
+        }
+
+//        return x.equals(y);
+        if (x.equals(y))
+            return true;
+        System.out.println("Not equal: <" + x + "> and <" + y + ">");
+        return false;
+    }
+
+    private static String deepToString(Object x) {
+        if (x == null)
+            return "null";
+
+        if (x instanceof Collection<?>) {
+            Collection<?> xcoll = (Collection<?>) x;
+            StringBuilder sb = new StringBuilder("[");
+            for (Object e : xcoll) {
+                if (sb.length() > 1)
+                    sb.append(", ");
+                sb.append(deepToString(e));
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+
+        if (x instanceof Object[]) {
+            Object[] xarr = (Object[]) x;
+            return deepToString(Arrays.asList(xarr));
+        }
+
+        if (x.getClass().isArray()) { // primitive array
+            String s = Arrays.deepToString(new Object[] {x});
+            return s.substring(1, s.length() - 1);
+        }
+
+        return x.toString();
+    }
+
+    private static void fail(String msg) {
+        System.out.println("TEST FAILED: " + msg);
+        if (msg.length() > 100)
+            msg = msg.substring(0, 100) + "...";
+        failure = msg;
+    }
+
+    private static String failure;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/customtypes/CustomLongMXBean.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+// CustomLongMXBean.java - see CustomTypeTest
+
+package customtypes;
+
+import javax.management.openmbean.MXBeanMappingFactoryClass;
+
+@MXBeanMappingFactoryClass(IntegerIsLongFactory.class)
+public interface CustomLongMXBean extends CustomMXBean {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/customtypes/CustomMXBean.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+// CustomMXBean.java - see CustomTypeTest
+
+package customtypes;
+
+public interface CustomMXBean {
+    public Integer getX();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/customtypes/IntegerIsLongFactory.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+// IntegerIsLongFactory.java - see CustomTypeTest
+
+package customtypes;
+
+import java.io.InvalidObjectException;
+import java.lang.reflect.Type;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.SimpleType;
+
+public class IntegerIsLongFactory implements MXBeanMappingFactory {
+    public MXBeanMapping forType(Type t, MXBeanMappingFactory f)
+    throws OpenDataException {
+        if (t == Integer.class)
+            return IntegerIsLongMapping;
+        else
+            return MXBeanMappingFactory.DEFAULT.forType(t, f);
+    }
+
+    private static final MXBeanMapping IntegerIsLongMapping =
+            new IntegerIsLongMapping();
+
+    private static class IntegerIsLongMapping extends MXBeanMapping {
+        IntegerIsLongMapping() {
+            super(Integer.class, SimpleType.STRING);
+        }
+
+        public Object fromOpenValue(Object openValue)
+        throws InvalidObjectException {
+            try {
+                return (Long) openValue;
+            } catch (Exception e) {
+                InvalidObjectException ioe = new InvalidObjectException("oops");
+                ioe.initCause(e);
+                throw ioe;
+            }
+        }
+
+        public Object toOpenValue(Object javaValue) throws OpenDataException {
+            try {
+                Integer i = (Integer) javaValue;
+                return new Long((int) i);
+            } catch (Exception e) {
+                OpenDataException ode = new OpenDataException("oops");
+                ode.initCause(e);
+                throw ode;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/customtypes/IntegerIsStringFactory.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+// IntegerIsStringFactory.java - see CustomTypeTest
+
+package customtypes;
+
+import java.io.InvalidObjectException;
+import java.lang.reflect.Type;
+import javax.management.openmbean.MXBeanMapping;
+import javax.management.openmbean.MXBeanMappingFactory;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.SimpleType;
+
+public class IntegerIsStringFactory extends MXBeanMappingFactory {
+    @Override
+    public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
+    throws OpenDataException {
+        if (t == Integer.class)
+            return integerIsStringMapping;
+        else
+            return MXBeanMappingFactory.DEFAULT.mappingForType(t, f);
+    }
+
+    private static final MXBeanMapping integerIsStringMapping =
+            new IntegerIsStringMapping();
+
+    private static class IntegerIsStringMapping extends MXBeanMapping {
+        IntegerIsStringMapping() {
+            super(Integer.class, SimpleType.STRING);
+        }
+
+        public Object fromOpenValue(Object openValue)
+        throws InvalidObjectException {
+            try {
+                String s = (String) openValue;
+                return Integer.parseInt(s);
+            } catch (Exception e) {
+                InvalidObjectException ioe = new InvalidObjectException("oops");
+                ioe.initCause(e);
+                throw ioe;
+            }
+        }
+
+        public Object toOpenValue(Object javaValue) throws OpenDataException {
+            try {
+                Integer i = (Integer) javaValue;
+                return i.toString();
+            } catch (Exception e) {
+                OpenDataException ode = new OpenDataException("oops");
+                ode.initCause(e);
+                throw ode;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/mxbean/customtypes/package-info.java	Thu Jun 05 13:42:47 2008 +0200
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+// package-info.java - test package annotations for custom types
+
+@javax.management.openmbean.MXBeanMappingFactoryClass(IntegerIsStringFactory.class)
+package customtypes;