changeset 700:2410a0b48d06

6336968: Methods to convert AttributeList to/from Map 6750008: Add JMX.getSpecificationVersion(MBeanServerConnection) and document interop 6750472: Add a way to convert a CompositeData into a Map 6752563: Allow CompositeDataSupport to have zero items Summary: Small JMX RFEs Reviewed-by: dfuchs
author emcmanus
date Fri, 07 Nov 2008 19:19:08 +0100
parents 810a95940b99
children e81b47f0b40f
files src/share/classes/javax/management/AttributeList.java src/share/classes/javax/management/JMX.java src/share/classes/javax/management/MBeanServerConnection.java src/share/classes/javax/management/MBeanServerNotification.java src/share/classes/javax/management/QueryNotificationFilter.java src/share/classes/javax/management/openmbean/CompositeDataSupport.java src/share/classes/javax/management/package.html test/javax/management/MBeanServer/AttributeListMapTest.java test/javax/management/MBeanServer/AttributeListTypeSafeTest.java test/javax/management/openmbean/CompositeDataToMapTest.java test/javax/management/remote/mandatory/version/JMXSpecVersionTest.java
diffstat 11 files changed, 1316 insertions(+), 311 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/javax/management/AttributeList.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/AttributeList.java	Fri Nov 07 19:19:08 2008 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1999-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
@@ -27,17 +27,23 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
- * Represents a list of values for attributes of an MBean. The methods
- * used for the insertion of {@link javax.management.Attribute
- * Attribute} objects in the <CODE>AttributeList</CODE> overrides the
- * corresponding methods in the superclass
- * <CODE>ArrayList</CODE>. This is needed in order to insure that the
- * objects contained in the <CODE>AttributeList</CODE> are only
- * <CODE>Attribute</CODE> objects. This avoids getting an exception
- * when retrieving elements from the <CODE>AttributeList</CODE>.
+ * <p>Represents a list of values for attributes of an MBean.  See the
+ * {@link MBeanServerConnection#getAttributes getAttributes} and
+ * {@link MBeanServerConnection#setAttributes setAttributes} methods of
+ * {@link MBeanServer} and {@link MBeanServerConnection}.</p>
+ *
+ * <p id="type-safe">For compatibility reasons, it is possible, though
+ * highly discouraged, to add objects to an {@code AttributeList} that are
+ * not instances of {@code Attribute}.  However, an {@code AttributeList}
+ * can be made <em>type-safe</em>, which means that an attempt to add
+ * an object that is not an {@code Attribute} will produce an {@code
+ * IllegalArgumentException}.  An {@code AttributeList} becomes type-safe
+ * when the method {@link #asList()} is called on it.</p>
  *
  * @since 1.5
  */
@@ -58,8 +64,8 @@
 */
 public class AttributeList extends ArrayList<Object> {
 
-    private transient boolean typeSafe;
-    private transient boolean tainted;
+    private transient volatile boolean typeSafe;
+    private transient volatile boolean tainted;
 
     /* Serial version */
     private static final long serialVersionUID = -4077085769279709076L;
@@ -124,7 +130,7 @@
 
         // Check for non-Attribute objects
         //
-        checkTypeSafe(list);
+        adding(list);
 
         // Build the List<Attribute>
         //
@@ -132,6 +138,56 @@
     }
 
     /**
+     * <p>Constructs an {@code AttributeList} containing the elements of
+     * the {@code Map} specified, in the order in which they appear in the
+     * {@code Map}'s {@link Map#entrySet entrySet}.  For each <em>{@code
+     * key}</em> and <em>{@code value}</em> in the {@code Map}, the constructed
+     * {@code AttributeList} will contain {@link Attribute#Attribute
+     * Attribute(<em>key</em>, <em>value</em>)}.</p>
+     *
+     * @param map the {@code Map} defining the elements of the new
+     * {@code AttributeList}.
+     */
+    public AttributeList(Map<String, ?> map) {
+        for (Map.Entry<String, ?> entry : map.entrySet())
+            add(new Attribute(entry.getKey(), entry.getValue()));
+        typeSafe = true;
+    }
+
+    /**
+     * <p>Return a {@code Map} that is a snapshot of the values in this
+     * {@code AttributeList}.  Each key in the {@code Map} is the {@linkplain
+     * Attribute#getName() name} of an {@code Attribute} in the list, and each
+     * value is the corresponding {@linkplain Attribute#getValue() value} of
+     * that {@code Attribute}.  The {@code AttributeList} and the {@code Map}
+     * are unrelated after the call, that is, changes to one do not affect the
+     * other.</p>
+     *
+     * <p>If the {@code AttributeList} contains more than one {@code Attribute}
+     * with the same name, then the {@code Map} will contain an entry
+     * for that name where the value is that of the last of those {@code
+     * Attribute}s.</p>
+     *
+     * @return the new {@code Map}.
+     *
+     * @throws IllegalArgumentException if this {@code AttributeList} contains
+     * an element that is not an {@code Attribute}.
+     */
+    public Map<String, Object> toMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+
+        // We can't call adding(this) because we're not necessarily typeSafe
+        if (tainted)
+            throw new IllegalArgumentException("AttributeList contains non-Attribute");
+
+        for (Object x : this) {
+            Attribute a = (Attribute) x;
+            map.put(a.getName(), a.getValue());
+        }
+        return map;
+    }
+
+    /**
      * Return a view of this list as a {@code List<Attribute>}.
      * Changes to the returned value are reflected by changes
      * to the original {@code AttributeList} and vice versa.
@@ -154,11 +210,9 @@
      */
     @SuppressWarnings("unchecked")
     public List<Attribute> asList() {
-        if (!typeSafe) {
-            if (tainted)
-                checkTypeSafe(this);
-            typeSafe = true;
-        }
+        typeSafe = true;
+        if (tainted)
+            adding((Collection<?>) this);  // will throw IllegalArgumentException
         return (List<Attribute>) (List<?>) this;
     }
 
@@ -175,7 +229,7 @@
      * Inserts the attribute specified as an element at the position specified.
      * Elements with an index greater than or equal to the current position are
      * shifted up. If the index is out of range (index < 0 || index >
-     * size() a RuntimeOperationsException should be raised, wrapping the
+     * size()) a RuntimeOperationsException should be raised, wrapping the
      * java.lang.IndexOutOfBoundsException thrown.
      *
      * @param object  The <CODE>Attribute</CODE> object to be inserted.
@@ -245,8 +299,7 @@
     public boolean addAll(int index, AttributeList list)  {
         try {
             return super.addAll(index, list);
-        }
-        catch (IndexOutOfBoundsException e) {
+        } catch (IndexOutOfBoundsException e) {
             throw new RuntimeOperationsException(e,
                 "The specified index is out of range");
         }
@@ -258,96 +311,77 @@
      * been called on this instance.
      */
 
+    /**
+     * {@inheritDoc}
+     * @throws IllegalArgumentException if this {@code AttributeList} is
+     * <a href="#type-safe">type-safe</a> and {@code element} is not an
+     * {@code Attribute}.
+     */
     @Override
-    public boolean add(Object o) {
-        if (!tainted)
-            tainted = isTainted(o);
-        if (typeSafe)
-            checkTypeSafe(o);
-        return super.add(o);
+    public boolean add(Object element) {
+        adding(element);
+        return super.add(element);
     }
 
+    /**
+     * {@inheritDoc}
+     * @throws IllegalArgumentException if this {@code AttributeList} is
+     * <a href="#type-safe">type-safe</a> and {@code element} is not an
+     * {@code Attribute}.
+     */
     @Override
     public void add(int index, Object element) {
-        if (!tainted)
-            tainted = isTainted(element);
-        if (typeSafe)
-            checkTypeSafe(element);
+        adding(element);
         super.add(index, element);
     }
 
+    /**
+     * {@inheritDoc}
+     * @throws IllegalArgumentException if this {@code AttributeList} is
+     * <a href="#type-safe">type-safe</a> and {@code c} contains an
+     * element that is not an {@code Attribute}.
+     */
     @Override
     public boolean addAll(Collection<?> c) {
-        if (!tainted)
-            tainted = isTainted(c);
-        if (typeSafe)
-            checkTypeSafe(c);
+        adding(c);
         return super.addAll(c);
     }
 
+    /**
+     * {@inheritDoc}
+     * @throws IllegalArgumentException if this {@code AttributeList} is
+     * <a href="#type-safe">type-safe</a> and {@code c} contains an
+     * element that is not an {@code Attribute}.
+     */
     @Override
     public boolean addAll(int index, Collection<?> c) {
-        if (!tainted)
-            tainted = isTainted(c);
-        if (typeSafe)
-            checkTypeSafe(c);
+        adding(c);
         return super.addAll(index, c);
     }
 
+    /**
+     * {@inheritDoc}
+     * @throws IllegalArgumentException if this {@code AttributeList} is
+     * <a href="#type-safe">type-safe</a> and {@code element} is not an
+     * {@code Attribute}.
+     */
     @Override
     public Object set(int index, Object element) {
-        if (!tainted)
-            tainted = isTainted(element);
-        if (typeSafe)
-            checkTypeSafe(element);
+        adding(element);
         return super.set(index, element);
     }
 
-    /**
-     * IllegalArgumentException if o is a non-Attribute object.
-     */
-    private static void checkTypeSafe(Object o) {
-        try {
-            o = (Attribute) o;
-        } catch (ClassCastException e) {
-            throw new IllegalArgumentException(e);
-        }
+    private void adding(Object x) {
+        if (x == null || x instanceof Attribute)
+            return;
+        if (typeSafe)
+            throw new IllegalArgumentException("Not an Attribute: " + x);
+        else
+            tainted = true;
     }
 
-    /**
-     * IllegalArgumentException if c contains any non-Attribute objects.
-     */
-    private static void checkTypeSafe(Collection<?> c) {
-        try {
-            Attribute a;
-            for (Object o : c)
-                a = (Attribute) o;
-        } catch (ClassCastException e) {
-            throw new IllegalArgumentException(e);
-        }
-    }
-
-    /**
-     * Returns true if o is a non-Attribute object.
-     */
-    private static boolean isTainted(Object o) {
-        try {
-            checkTypeSafe(o);
-        } catch (IllegalArgumentException e) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if c contains any non-Attribute objects.
-     */
-    private static boolean isTainted(Collection<?> c) {
-        try {
-            checkTypeSafe(c);
-        } catch (IllegalArgumentException e) {
-            return true;
-        }
-        return false;
+    private void adding(Collection<?> c) {
+        for (Object x : c)
+            adding(x);
     }
 }
--- a/src/share/classes/javax/management/JMX.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/JMX.java	Fri Nov 07 19:19:08 2008 +0100
@@ -830,4 +830,80 @@
             ((DynamicWrapperMBean) mbean).getWrappedObject() : mbean;
         return (MBeanInjector.injectsSendNotification(resource));
     }
+
+    /**
+     * <p>Return the version of the JMX specification that a (possibly remote)
+     * MBean Server is using.  The JMX specification described in this
+     * documentation is version 2.0.  The earlier versions that might be
+     * reported by this method are 1.0, 1.1, 1.2, and 1.4.  (There is no 1.3.)
+     * All of these versions and all future versions can be compared using
+     * {@link String#compareTo(String)}.  So, for example, to tell if
+     * {@code mbsc} is running at least version 2.0 you can write:</p>
+     *
+     * <pre>
+     * String version = JMX.getSpecificationVersion(mbsc, null);
+     * boolean atLeast2dot0 = (version.compareTo("2.0") >= 0);
+     * </pre>
+     *
+     * <p>A remote MBean Server might be running an earlier version of the
+     * JMX API, and in that case <a href="package-summary.html#interop">certain
+     * features</a> might not be available in it.</p>
+     *
+     * <p>The version of the MBean Server {@code mbsc} is not necessarily
+     * the version of all namespaces within that MBean Server, for example
+     * if some of them use {@link javax.management.namespace.JMXRemoteNamespace
+     * JMXRemoteNamespace}.  To determine the version of the namespace
+     * that a particular MBean is in, give its name as the {@code mbeanName}
+     * parameter.</p>
+     *
+     * @param mbsc a connection to an MBean Server.
+     *
+     * @param mbeanName the name of an MBean within that MBean Server, or null.
+     * If non-null, the namespace of this name, as determined by
+     * {@link JMXNamespaces#getContainingNamespace
+     * JMXNamespaces.getContainingNamespace}, is the one whose specification
+     * version will be returned.
+     *
+     * @return the JMX specification version reported by that MBean Server.
+     *
+     * @throws IllegalArgumentException if {@code mbsc} is null, or if
+     * {@code mbeanName} includes a wildcard character ({@code *} or {@code ?})
+     * in its namespace.
+     *
+     * @throws IOException if the version cannot be obtained, either because
+     * there is a communication problem or because the remote MBean Server
+     * does not have the appropriate {@linkplain
+     * MBeanServerDelegateMBean#getSpecificationVersion() attribute}.
+     *
+     * @see <a href="package-summary.html#interop">Interoperability between
+     * versions of the JMX specification</a>
+     * @see MBeanServerDelegateMBean#getSpecificationVersion
+     */
+    public static String getSpecificationVersion(
+            MBeanServerConnection mbsc, ObjectName mbeanName)
+            throws IOException {
+        if (mbsc == null)
+            throw new IllegalArgumentException("Null MBeanServerConnection");
+
+        String namespace;
+        if (mbeanName == null)
+            namespace = "";
+        else
+            namespace = JMXNamespaces.getContainingNamespace(mbeanName);
+        if (namespace.contains("*") || namespace.contains("?")) {
+            throw new IllegalArgumentException(
+                    "ObjectName contains namespace wildcard: " + mbeanName);
+        }
+
+        try {
+            if (namespace.length() > 0)
+                mbsc = JMXNamespaces.narrowToNamespace(mbsc, namespace);
+            return (String) mbsc.getAttribute(
+                    MBeanServerDelegate.DELEGATE_NAME, "SpecificationVersion");
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
 }
--- a/src/share/classes/javax/management/MBeanServerConnection.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/MBeanServerConnection.java	Fri Nov 07 19:19:08 2008 +0100
@@ -532,8 +532,30 @@
 
 
     /**
-     * Enables the values of several attributes of a named MBean. The MBean
-     * is identified by its object name.
+     * <p>Retrieves the values of several attributes of a named MBean. The MBean
+     * is identified by its object name.</p>
+     *
+     * <p>If one or more attributes cannot be retrieved for some reason, they
+     * will be omitted from the returned {@code AttributeList}.  The caller
+     * should check that the list is the same size as the {@code attributes}
+     * array.  To discover what problem prevented a given attribute from being
+     * retrieved, call {@link #getAttribute getAttribute} for that attribute.</p>
+     *
+     * <p>Here is an example of calling this method and checking that it
+     * succeeded in retrieving all the requested attributes:</p>
+     *
+     * <pre>
+     * String[] attrNames = ...;
+     * AttributeList list = mbeanServerConnection.getAttributes(objectName, attrNames);
+     * if (list.size() == attrNames.length)
+     *     System.out.println("All attributes were retrieved successfully");
+     * else {
+     *     {@code List<String>} missing = new {@code ArrayList<String>}(<!--
+     * -->{@link java.util.Arrays#asList Arrays.asList}(attrNames));
+     *     missing.removeAll(list.toMap().keySet());
+     *     System.out.println("Did not retrieve: " + missing);
+     * }
+     * </pre>
      *
      * @param name The object name of the MBean from which the
      * attributes are retrieved.
@@ -557,6 +579,7 @@
             throws InstanceNotFoundException, ReflectionException,
                    IOException;
 
+
     /**
      * Sets the value of a specific attribute of a named MBean. The MBean
      * is identified by its object name.
@@ -592,10 +615,36 @@
                    ReflectionException, IOException;
 
 
-
     /**
-     * Sets the values of several attributes of a named MBean. The MBean is
-     * identified by its object name.
+     * <p>Sets the values of several attributes of a named MBean. The MBean is
+     * identified by its object name.</p>
+     *
+     * <p>If one or more attributes cannot be set for some reason, they will be
+     * omitted from the returned {@code AttributeList}.  The caller should check
+     * that the input {@code AttributeList} is the same size as the output one.
+     * To discover what problem prevented a given attribute from being retrieved,
+     * it will usually be possible to call {@link #setAttribute setAttribute}
+     * for that attribute, although this is not guaranteed to work.  (For
+     * example, the values of two attributes may have been rejected because
+     * they were inconsistent with each other.  Setting one of them alone might
+     * be allowed.)<p>
+     *
+     * <p>Here is an example of calling this method and checking that it
+     * succeeded in setting all the requested attributes:</p>
+     *
+     * <pre>
+     * AttributeList inputAttrs = ...;
+     * AttributeList outputAttrs = mbeanServerConnection.setAttributes(<!--
+     * -->objectName, inputAttrs);
+     * if (inputAttrs.size() == outputAttrs.size())
+     *     System.out.println("All attributes were set successfully");
+     * else {
+     *     {@code List<String>} missing = new {@code ArrayList<String>}(<!--
+     * -->inputAttrs.toMap().keySet());
+     *     missing.removeAll(outputAttrs.toMap().keySet());
+     *     System.out.println("Did not set: " + missing);
+     * }
+     * </pre>
      *
      * @param name The object name of the MBean within which the
      * attributes are to be set.
@@ -622,7 +671,39 @@
         throws InstanceNotFoundException, ReflectionException, IOException;
 
     /**
-     * Invokes an operation on an MBean.
+     * <p>Invokes an operation on an MBean.</p>
+     *
+     * <p>Because of the need for a {@code signature} to differentiate
+     * possibly-overloaded operations, it is much simpler to invoke operations
+     * through an {@linkplain JMX#newMBeanProxy(MBeanServerConnection, ObjectName,
+     * Class) MBean proxy} where possible.  For example, suppose you have a
+     * Standard MBean interface like this:</p>
+     *
+     * <pre>
+     * public interface FooMBean {
+     *     public int countMatches(String[] patterns, boolean ignoreCase);
+     * }
+     * </pre>
+     *
+     * <p>The {@code countMatches} operation can be invoked as follows:</p>
+     *
+     * <pre>
+     * String[] myPatterns = ...;
+     * int count = (Integer) mbeanServerConnection.invoke(
+     *         objectName,
+     *         "countMatches",
+     *         new Object[] {myPatterns, true},
+     *         new String[] {String[].class.getName(), boolean.class.getName()});
+     * </pre>
+     *
+     * <p>Alternatively, it can be invoked through a proxy as follows:</p>
+     *
+     * <pre>
+     * String[] myPatterns = ...;
+     * FooMBean fooProxy = JMX.newMBeanProxy(
+     *         mbeanServerConnection, objectName, FooMBean.class);
+     * int count = fooProxy.countMatches(myPatterns, true);
+     * </pre>
      *
      * @param name The object name of the MBean on which the method is
      * to be invoked.
@@ -630,7 +711,8 @@
      * @param params An array containing the parameters to be set when
      * the operation is invoked
      * @param signature An array containing the signature of the
-     * operation. The class objects will be loaded using the same
+     * operation, an array of class names in the format returned by
+     * {@link Class#getName()}. The class objects will be loaded using the same
      * class loader as the one used for loading the MBean on which the
      * operation was invoked.
      *
--- a/src/share/classes/javax/management/MBeanServerNotification.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/MBeanServerNotification.java	Fri Nov 07 19:19:08 2008 +0100
@@ -64,6 +64,33 @@
  *             MBeanServerDelegate.DELEGATE_NAME, printListener, null, null);
  * </pre>
  *
+ * <p>The following code prints a message every time an MBean is registered
+ * or unregistered in the MBean Server {@code mbeanServer}:</p>
+ *
+ * <pre>
+ * private static final NotificationListener printListener = new NotificationListener() {
+ *     public void handleNotification(Notification n, Object handback) {
+ *         if (!(n instanceof MBeanServerNotification)) {
+ *             System.out.println("Ignored notification of class " + n.getClass().getName());
+ *             return;
+ *         }
+ *         MBeanServerNotification mbsn = (MBeanServerNotification) n;
+ *         String what;
+ *         if (n.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
+ *             what = "MBean registered";
+ *         else if (n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION))
+ *             what = "MBean unregistered";
+ *         else
+ *             what = "Unknown type " + n.getType();
+ *         System.out.println("Received MBean Server notification: " + what + ": " +
+ *                 mbsn.getMBeanName());
+ * };
+ *
+ * ...
+ *     mbeanServer.addNotificationListener(
+ *             MBeanServerDelegate.DELEGATE_NAME, printListener, null, null);
+ * </pre>
+ *
  * @since 1.5
  */
 public class MBeanServerNotification extends Notification {
--- a/src/share/classes/javax/management/QueryNotificationFilter.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/QueryNotificationFilter.java	Fri Nov 07 19:19:08 2008 +0100
@@ -26,7 +26,6 @@
 package javax.management;
 
 import com.sun.jmx.mbeanserver.NotificationMBeanSupport;
-import com.sun.jmx.mbeanserver.Util;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -43,6 +42,11 @@
  * on both the client and the server in the remote case, so using this class
  * instead is recommended where possible.</p>
  *
+ * <p>Because this class was introduced in version 2.0 of the JMX API,
+ * it may not be present on a remote JMX agent that is running an earlier
+ * version.  The method {@link JMX#getSpecificationVersion
+ * JMX.getSpecificationVersion} can be used to determine the remote version.</p>
+ *
  * <p>This class uses the {@linkplain Query Query API} to specify the
  * filtering logic.  For example, to select only notifications where the
  * {@linkplain Notification#getType() type} is {@code "com.example.mytype"},
--- a/src/share/classes/javax/management/openmbean/CompositeDataSupport.java	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java	Fri Nov 07 19:19:08 2008 +0100
@@ -33,12 +33,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
 // jmx import
+import java.util.TreeSet;
 //
 
 
@@ -60,16 +62,15 @@
      * respective values.
      *         A {@link SortedMap} is used for faster retrieval of elements.
      */
-    private SortedMap<String, Object> contents = new TreeMap<String, Object>();
+    private final SortedMap<String, Object> contents;
 
     /**
      * @serial The <i>composite type </i> of this <i>composite data</i> instance.
      */
-    private CompositeType compositeType;
+    private final CompositeType compositeType;
 
     /**
-     * <p>
-     * Constructs a <tt>CompositeDataSupport</tt> instance with the specified
+     * <p>Constructs a <tt>CompositeDataSupport</tt> instance with the specified
      * <tt>compositeType</tt>, whose item values
      * are specified by <tt>itemValues[]</tt>, in the same order as in
      * <tt>itemNames[]</tt>.
@@ -79,41 +80,124 @@
      * The items contained in this <tt>CompositeDataSupport</tt> instance are
      * internally stored in a <tt>TreeMap</tt>,
      * thus sorted in ascending lexicographic order of their names, for faster
-     * retrieval of individual item values.
+     * retrieval of individual item values.</p>
+     *
+     * <p>The constructor checks that all the constraints listed below for each
+     * parameter are satisfied,
+     * and throws the appropriate exception if they are not.</p>
+     *
+     * @param compositeType the <i>composite type </i> of this <i>composite
+     * data</i> instance; must not be null.
+     *
+     * @param itemNames <tt>itemNames</tt> must list, in any order, all the
+     * item names defined in <tt>compositeType</tt>; the order in which the
+     * names are listed, is used to match values in <tt>itemValues[]</tt>; must
+     * not be null.
+     *
+     * @param itemValues the values of the items, listed in the same order as
+     * their respective names in <tt>itemNames</tt>; each item value can be
+     * null, but if it is non-null it must be a valid value for the open type
+     * defined in <tt>compositeType</tt> for the corresponding item; must be of
+     * the same size as <tt>itemNames</tt>; must not be null.
+     *
+     * @throws IllegalArgumentException <tt>compositeType</tt> is null, or
+     * <tt>itemNames[]</tt> or <tt>itemValues[]</tt> is null or empty, or one
+     * of the elements in <tt>itemNames[]</tt> is a null or empty string, or
+     * <tt>itemNames[]</tt> and <tt>itemValues[]</tt> are not of the same size.
+     *
+     * @throws OpenDataException <tt>itemNames[]</tt> or
+     * <tt>itemValues[]</tt>'s size differs from the number of items defined in
+     * <tt>compositeType</tt>, or one of the elements in <tt>itemNames[]</tt>
+     * does not exist as an item name defined in <tt>compositeType</tt>, or one
+     * of the elements in <tt>itemValues[]</tt> is not a valid value for the
+     * corresponding item as defined in <tt>compositeType</tt>.
+     */
+    public CompositeDataSupport(
+            CompositeType compositeType, String[] itemNames, Object[] itemValues)
+            throws OpenDataException {
+        this(makeMap(itemNames, itemValues), compositeType);
+    }
+
+    private static SortedMap<String, Object> makeMap(
+            String[] itemNames, Object[] itemValues)
+            throws OpenDataException {
+
+        if (itemNames == null || itemValues == null)
+            throw new IllegalArgumentException("Null itemNames or itemValues");
+        if (itemNames.length != itemValues.length) {
+            throw new IllegalArgumentException(
+                    "Different lengths: itemNames[" + itemNames.length +
+                    "], itemValues[" + itemValues.length + "]");
+        }
+
+        SortedMap<String, Object> map = new TreeMap<String, Object>();
+        for (int i = 0; i < itemNames.length; i++) {
+            String name = itemNames[i];
+            if (name == null || name.equals(""))
+                throw new IllegalArgumentException("Null or empty item name");
+            if (map.containsKey(name))
+                throw new OpenDataException("Duplicate item name " + name);
+            map.put(itemNames[i], itemValues[i]);
+        }
+
+        return map;
+    }
+
+    /**
      * <p>
-     * The constructor checks that all the constraints listed below for each
-     * parameter are satisfied,
-     * and throws the appropriate exception if they are not.
-     * <p>
-     * @param  compositeType  the <i>composite type </i> of this <i>composite
-     * data</i> instance;
+     * Constructs a <tt>CompositeDataSupport</tt> instance with the specified <tt>compositeType</tt>, whose item names and corresponding values
+     * are given by the mappings in the map <tt>items</tt>.
+     * This constructor converts the keys to a string array and the values to an object array and calls
+     * <tt>CompositeDataSupport(javax.management.openmbean.CompositeType, java.lang.String[], java.lang.Object[])</tt>.
+     *
+     * @param  compositeType  the <i>composite type </i> of this <i>composite data</i> instance;
      *                        must not be null.
-     * <p>
-     * @param  itemNames  <tt>itemNames</tt> must list, in any order, all the
-     * item names defined in <tt>compositeType</tt>;
-     *                    the order in which the names are listed, is used to
-     * match values in <tt>itemValues[]</tt>;
-     *                    must not be null or empty.
-     * <p>
-     * @param  itemValues  the values of the items, listed in the same order as
-     * their respective names in <tt>itemNames</tt>;
-     *                     each item value can be null, but if it is non-null it must be
-     *                     a valid value for the open type defined in <tt>compositeType</tt> for the corresponding item;
-     *                     must be of the same size as <tt>itemNames</tt>; must not be null or empty.
-     * <p>
-     * @throws  IllegalArgumentException  <tt>compositeType</tt> is null, or <tt>itemNames[]</tt> or <tt>itemValues[]</tt> is null or empty,
-     *                                    or one of the elements in <tt>itemNames[]</tt>  is a null or empty string,
-     *                                    or <tt>itemNames[]</tt> and <tt>itemValues[]</tt> are not of the same size.
-     * <p>
-     * @throws  OpenDataException  <tt>itemNames[]</tt> or <tt>itemValues[]</tt>'s size differs from
-     *                             the number of items defined in <tt>compositeType</tt>,
-     *                             or one of the elements in <tt>itemNames[]</tt> does not exist as an item name defined in <tt>compositeType</tt>,
-     *                             or one of the elements in <tt>itemValues[]</tt> is not a valid value for the corresponding item
-     *                             as defined in <tt>compositeType</tt>.
-     * <p>
+     * @param  items  the mappings of all the item names to their values;
+     *                <tt>items</tt> must contain all the item names defined in <tt>compositeType</tt>;
+     *                must not be null.
+     *
+     * @throws IllegalArgumentException <tt>compositeType</tt> is null, or
+     * <tt>items</tt> is null, or one of the keys in <tt>items</tt> is a null
+     * or empty string.
+     * @throws OpenDataException <tt>items</tt>' size differs from the
+     * number of items defined in <tt>compositeType</tt>, or one of the
+     * keys in <tt>items</tt> does not exist as an item name defined in
+     * <tt>compositeType</tt>, or one of the values in <tt>items</tt>
+     * is not a valid value for the corresponding item as defined in
+     * <tt>compositeType</tt>.
+     * @throws ArrayStoreException one or more keys in <tt>items</tt> is not of
+     * the class <tt>java.lang.String</tt>.
+     *
+     * @see #toMap
      */
-    public CompositeDataSupport(CompositeType compositeType, String[] itemNames, Object[] itemValues)
-        throws OpenDataException {
+    public CompositeDataSupport(CompositeType compositeType,
+                                Map<String,?> items)
+            throws OpenDataException {
+        this(makeMap(items), compositeType);
+    }
+
+    private static SortedMap<String, Object> makeMap(Map<String, ?> items) {
+        if (items == null)
+            throw new IllegalArgumentException("Null items map");
+        if (items.containsKey(null) || items.containsKey(""))
+            throw new IllegalArgumentException("Null or empty item name");
+
+        SortedMap<String, Object> map = new TreeMap<String, Object>();
+        for (Object key : items.keySet()) {
+            if (!(key instanceof String)) {
+                throw new ArrayStoreException("Item name is not string: " + key);
+                // This can happen because of erasure.  The particular
+                // exception is a historical artifact - an implementation
+                // detail that leaked into the API.
+            }
+            map.put((String) key, items.get(key));
+        }
+        return map;
+    }
+
+    private CompositeDataSupport(
+            SortedMap<String, Object> items, CompositeType compositeType)
+            throws OpenDataException {
 
         // Check compositeType is not null
         //
@@ -122,126 +206,42 @@
         }
 
         // item names defined in compositeType:
-        Set<String> namesSet = compositeType.keySet();
+        Set<String> namesFromType = compositeType.keySet();
+        Set<String> namesFromItems = items.keySet();
 
-        // Check the array itemNames is not null or empty (length!=0) and
-        // that there is no null element or empty string in it
-        //
-        checkForNullElement(itemNames, "itemNames");
-        checkForEmptyString(itemNames, "itemNames");
-
-        // Check the array itemValues is not null or empty (length!=0)
-        // (NOTE: we allow null values as array elements)
-        //
-        if ( (itemValues == null) || (itemValues.length == 0) ) {
-            throw new IllegalArgumentException("Argument itemValues[] cannot be null or empty.");
+        // This is just a comparison, but we do it this way for a better
+        // exception message.
+        if (!namesFromType.equals(namesFromItems)) {
+            Set<String> extraFromType = new TreeSet<String>(namesFromType);
+            extraFromType.removeAll(namesFromItems);
+            Set<String> extraFromItems = new TreeSet<String>(namesFromItems);
+            extraFromItems.removeAll(namesFromType);
+            if (!extraFromType.isEmpty() || !extraFromItems.isEmpty()) {
+                throw new OpenDataException(
+                        "Item names do not match CompositeType: " +
+                        "names in items but not in CompositeType: " + extraFromItems +
+                        "; names in CompositeType but not in items: " + extraFromType);
+            }
         }
 
-        // Check that the sizes of the 2 arrays itemNames and itemValues are the same
-        //
-        if (itemNames.length != itemValues.length) {
-            throw new IllegalArgumentException("Array arguments itemNames[] and itemValues[] "+
-                                               "should be of same length (got "+ itemNames.length +
-                                               " and "+ itemValues.length +").");
-        }
-
-        // Check the size of the 2 arrays is equal to the number of items defined in compositeType
-        //
-        if (itemNames.length != namesSet.size()) {
-            throw new OpenDataException("The size of array arguments itemNames[] and itemValues[] should be equal to the number of items defined"+
-                                        " in argument compositeType (found "+ itemNames.length +" elements in itemNames[] and itemValues[],"+
-                                        " expecting "+ namesSet.size() +" elements according to compositeType.");
-        }
-
-        // Check parameter itemNames[] contains all names defined in the compositeType of this instance
-        //
-        if ( ! Arrays.asList(itemNames).containsAll(namesSet) ) {
-            throw new OpenDataException("Argument itemNames[] does not contain all names defined in the compositeType of this instance.");
-        }
-
-        // Check each element of itemValues[], if not null, is of the open type defined for the corresponding item
-        //
-        OpenType<?> itemType;
-        for (int i=0; i<itemValues.length; i++) {
-            itemType = compositeType.getType(itemNames[i]);
-            if ( (itemValues[i] != null) && (! itemType.isValue(itemValues[i])) ) {
-                throw new OpenDataException("Argument's element itemValues["+ i +"]=\""+ itemValues[i] +"\" is not a valid value for"+
-                                            " this item (itemName="+ itemNames[i] +",itemType="+ itemType +").");
+        // Check each value, if not null, is of the open type defined for the
+        // corresponding item
+        for (String name : namesFromType) {
+            Object value = items.get(name);
+            if (value != null) {
+                OpenType<?> itemType = compositeType.getType(name);
+                if (!itemType.isValue(value)) {
+                    throw new OpenDataException(
+                            "Argument value of wrong type for item " + name +
+                            ": value " + value + ", type " + itemType);
+                }
             }
         }
 
         // Initialize internal fields: compositeType and contents
         //
         this.compositeType = compositeType;
-        for (int i=0; i<itemNames.length; i++) {
-            this.contents.put(itemNames[i], itemValues[i]);
-        }
-    }
-
-    /**
-     * <p>
-     * Constructs a <tt>CompositeDataSupport</tt> instance with the specified <tt>compositeType</tt>, whose item names and corresponding values
-     * are given by the mappings in the map <tt>items</tt>.
-     * This constructor converts the keys to a string array and the values to an object array and calls
-     * <tt>CompositeDataSupport(javax.management.openmbean.CompositeType, java.lang.String[], java.lang.Object[])</tt>.
-     * <p>
-     * @param  compositeType  the <i>composite type </i> of this <i>composite data</i> instance;
-     *                        must not be null.
-     * <p>
-     * @param  items  the mappings of all the item names to their values;
-     *                <tt>items</tt> must contain all the item names defined in <tt>compositeType</tt>;
-     *                must not be null or empty.
-     * <p>
-     * @throws  IllegalArgumentException  <tt>compositeType</tt> is null, or <tt>items</tt> is null or empty,
-     *                                    or one of the keys in <tt>items</tt>  is a null or empty string,
-     *                                    or one of the values in <tt>items</tt>  is null.
-     * <p>
-     * @throws  OpenDataException  <tt>items</tt>' size differs from the number of items defined in <tt>compositeType</tt>,
-     *                             or one of the keys in <tt>items</tt> does not exist as an item name defined in <tt>compositeType</tt>,
-     *                             or one of the values in <tt>items</tt> is not a valid value for the corresponding item
-     *                             as defined in <tt>compositeType</tt>.
-     * <p>
-     * @throws ArrayStoreException  one or more keys in <tt>items</tt> is not of the class <tt>java.lang.String</tt>.
-     * <p>
-     */
-    public CompositeDataSupport(CompositeType compositeType,
-                                Map<String,?> items)
-            throws OpenDataException {
-
-
-        // Let the other constructor do the job, as the call to another constructor must be the first call
-        //
-        this( compositeType,
-              (items==null  ?  null  :  items.keySet().toArray(new String[items.size()])), // may raise an ArrayStoreException
-              (items==null  ?  null  :  items.values().toArray()) );
-    }
-
-    /**
-     *
-     */
-    private static void checkForNullElement(Object[] arg, String argName) {
-        if ( (arg == null) || (arg.length == 0) ) {
-            throw new IllegalArgumentException(
-                       "Argument "+ argName +"[] cannot be null or empty.");
-        }
-        for (int i=0; i<arg.length; i++) {
-            if (arg[i] == null) {
-                throw new IllegalArgumentException(
-                       "Argument's element "+ argName +"["+ i +"] cannot be null.");
-            }
-        }
-    }
-
-    /**
-     *
-     */
-    private static void checkForEmptyString(String[] arg, String argName) {
-        for (int i=0; i<arg.length; i++) {
-            if (arg[i].trim().equals("")) {
-                throw new IllegalArgumentException(
-                  "Argument's element "+ argName +"["+ i +"] cannot be an empty string.");
-            }
-        }
+        this.contents = items;
     }
 
     /**
@@ -329,6 +329,54 @@
     }
 
     /**
+     * <p>Returns a Map representing the contents of the given CompositeData.
+     * Each item in the CompositeData is represented by an entry in the map,
+     * where the name and value of the item are the key and value of the entry.
+     * The returned value is modifiable but modifications to it have no effect
+     * on the original CompositeData.</p>
+     *
+     * <p>For example, if you have a CompositeData {@code cd1} and you want
+     * to produce another CompositeData {@code cd2} which is the same except
+     * that the value of its {@code id} item has been changed to 253, you
+     * could write:</p>
+     *
+     * <pre>
+     * CompositeData cd1 = ...;
+     * {@code Map<String, Object>} map = CompositeDataSupport.toMap(cd1);
+     * assert(map.get("id") instanceof Integer);
+     * map.put("id", 253);
+     * CompositeData cd2 = {@link #CompositeDataSupport(CompositeType, Map)
+     * new CompositeDataSupport}(cd1.getCompositeType(), map);
+     * </pre>
+     *
+     * <p>Logically, this method would be a method in the {@link CompositeData}
+     * interface, but cannot be for compatibility reasons.</p>
+     *
+     * @param cd the CompositeData to convert to a Map.
+     *
+     * @return a Map that is a copy of the contents of {@code cd}.
+     *
+     * @throws IllegalArgumentException if {@code cd} is null.
+     *
+     * @see #CompositeDataSupport(CompositeType, Map)
+     */
+    public static Map<String, Object> toMap(CompositeData cd) {
+        if (cd == null)
+            throw new IllegalArgumentException("Null argument");
+
+        // If we really wanted, we could check whether cd is a
+        // CompositeDataSupport and return a copy of cd.contents if so,
+        // but I don't think that would be substantially faster.
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        CompositeType ct = cd.getCompositeType();
+        for (String key : ct.keySet()) {
+            Object value = cd.get(key);
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    /**
      * Compares the specified <var>obj</var> parameter with this
      * <code>CompositeDataSupport</code> instance for equality.
      * <p>
--- a/src/share/classes/javax/management/package.html	Fri Nov 07 11:48:07 2008 +0100
+++ b/src/share/classes/javax/management/package.html	Fri Nov 07 19:19:08 2008 +0100
@@ -1,7 +1,7 @@
 <html>
-<head>
-<title>javax.management package</title>
-<!--
+    <head>
+        <title>javax.management package</title>
+        <!--
 Copyright 1999-2006 Sun Microsystems, Inc.  All Rights Reserved.
 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 
@@ -24,37 +24,37 @@
 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.
--->
-</head>
-<body bgcolor="white">
-      <p>Provides the core classes for the Java Management Extensions.</p>
+        -->
+    </head>
+    <body bgcolor="white">
+        <p>Provides the core classes for the Java Management Extensions.</p>
 
-      <p>The Java Management Extensions
-	(JMX<sup><font size="-1">TM</font></sup>) API is a standard
-	API for management and monitoring.  Typical uses include:</p>
+        <p>The Java Management Extensions
+            (JMX<sup><font size="-1">TM</font></sup>) API is a standard
+        API for management and monitoring.  Typical uses include:</p>
 
-      <ul>
-	<li>consulting and changing application configuration</li>
+        <ul>
+            <li>consulting and changing application configuration</li>
 
-	<li>accumulating statistics about application behavior and
-	  making them available</li>
+            <li>accumulating statistics about application behavior and
+            making them available</li>
 
-	<li>notifying of state changes and erroneous conditions.</li>
-      </ul>
+            <li>notifying of state changes and erroneous conditions.</li>
+        </ul>
 
-      <p>The JMX API can also be used as part of a solution for
-	managing systems, networks, and so on.</p>
+        <p>The JMX API can also be used as part of a solution for
+        managing systems, networks, and so on.</p>
 
-      <p>The API includes remote access, so a remote management
-	program can interact with a running application for these
-	purposes.</p>
+        <p>The API includes remote access, so a remote management
+            program can interact with a running application for these
+        purposes.</p>
 
-      <h2>MBeans</h2>
+        <h2>MBeans</h2>
 
-      <p>The fundamental notion of the JMX API is the <em>MBean</em>.
-	An MBean is a named <em>managed object</em> representing a
-	resource.  It has a <em>management interface</em> consisting
-	of:</p>
+        <p>The fundamental notion of the JMX API is the <em>MBean</em>.
+            An MBean is a named <em>managed object</em> representing a
+            resource.  It has a <em>management interface</em> consisting
+        of:</p>
 
         <ul>
             <li>named and typed attributes that can be read and/or
@@ -92,40 +92,40 @@
 
         <pre>
     public interface ConfigurationMBean {
-	public int getCacheSize();
-	public void setCacheSize(int size);
-	public long getLastChangedTime();
-	public void save();
+         public int getCacheSize();
+         public void setCacheSize(int size);
+         public long getLastChangedTime();
+         public void save();
     }
-      </pre>
+        </pre>
 
-      <p>The methods <code>getCacheSize</code> and
-	<code>setCacheSize</code> define a read-write attribute of
-	type <code>int</code> called <code>CacheSize</code> (with an
-	initial capital, unlike the JavaBeans convention).</p>
+        <p>The methods <code>getCacheSize</code> and
+            <code>setCacheSize</code> define a read-write attribute of
+            type <code>int</code> called <code>CacheSize</code> (with an
+        initial capital, unlike the JavaBeans convention).</p>
 
-      <p>The method <code>getLastChangedTime</code> defines an
-	attribute of type <code>long</code> called
-	<code>LastChangedTime</code>.  This is a read-only attribute,
-	since there is no method <code>setLastChangedTime</code>.</p>
+        <p>The method <code>getLastChangedTime</code> defines an
+            attribute of type <code>long</code> called
+            <code>LastChangedTime</code>.  This is a read-only attribute,
+        since there is no method <code>setLastChangedTime</code>.</p>
 
-      <p>The method <code>save</code> defines an operation called
-	<code>save</code>.  It is not an attribute, since its name
-	does not begin with <code>get</code>, <code>set</code>, or
-	<code>is</code>.</p>
+        <p>The method <code>save</code> defines an operation called
+            <code>save</code>.  It is not an attribute, since its name
+            does not begin with <code>get</code>, <code>set</code>, or
+        <code>is</code>.</p>
 
-      <p>The exact naming patterns for Standard MBeans are detailed in
-	the <a href="#spec">JMX Specification</a>.</p>
+        <p>The exact naming patterns for Standard MBeans are detailed in
+        the <a href="#spec">JMX Specification</a>.</p>
 
-      <p>There are two ways to make a Java object that is an MBean
-	with this management interface.  One is for the object to be
-	of a class that has exactly the same name as the Java
-	interface but without the <code>MBean</code> suffix.  So in
-	the example the object would be of the class
-	<code>Configuration</code>, in the same Java package as
-	<code>ConfigurationMBean</code>.  The second way is to use the
-	{@link javax.management.StandardMBean StandardMBean}
-	class.</p>
+        <p>There are two ways to make a Java object that is an MBean
+            with this management interface.  One is for the object to be
+            of a class that has exactly the same name as the Java
+            interface but without the <code>MBean</code> suffix.  So in
+            the example the object would be of the class
+            <code>Configuration</code>, in the same Java package as
+            <code>ConfigurationMBean</code>.  The second way is to use the
+            {@link javax.management.StandardMBean StandardMBean}
+        class.</p>
 
 
         <h3 id="stdannot">Defining Standard MBeans with annotations</h3>
@@ -272,37 +272,37 @@
         <pre>
     int cacheSize = mbs.getAttribute(name, "CacheSize");
     {@link javax.management.Attribute Attribute} newCacheSize =
-    	new Attribute("CacheSize", new Integer(2000));
+         new Attribute("CacheSize", new Integer(2000));
     mbs.setAttribute(name, newCacheSize);
     mbs.invoke(name, "save", new Object[0], new Class[0]);
-      </pre>
+        </pre>
 
         <p id="proxy">Alternatively, if you have a Java interface that
             corresponds to the management interface for the MBean, you can use an
         <em>MBean proxy</em> like this:</p>
 
-      <pre>
+        <pre>
     ConfigurationMBean conf =
         {@link javax.management.JMX#newMBeanProxy
             JMX.newMBeanProxy}(mbs, name, ConfigurationMBean.class);
     int cacheSize = conf.getCacheSize();
     conf.setCacheSize(2000);
     conf.save();
-      </pre>
+        </pre>
 
-      <p>Using an MBean proxy is just a convenience.  The second
-	example ends up calling the same <code>MBeanServer</code>
-	operations as the first one.</p>
+        <p>Using an MBean proxy is just a convenience.  The second
+            example ends up calling the same <code>MBeanServer</code>
+        operations as the first one.</p>
 
-      <p>An MBean Server can be queried for MBeans whose names match
-	certain patterns and/or whose attributes meet certain
-	constraints.  Name patterns are constructed using the {@link
-	javax.management.ObjectName ObjectName} class and constraints
-	are constructed using the {@link javax.management.Query Query}
-	class.  The methods {@link
-	javax.management.MBeanServer#queryNames queryNames} and {@link
-	javax.management.MBeanServer#queryMBeans queryMBeans} then
-	perform the query.</p>
+        <p>An MBean Server can be queried for MBeans whose names match
+            certain patterns and/or whose attributes meet certain
+            constraints.  Name patterns are constructed using the {@link
+            javax.management.ObjectName ObjectName} class and constraints
+            are constructed using the {@link javax.management.Query Query}
+            class.  The methods {@link
+            javax.management.MBeanServer#queryNames queryNames} and {@link
+            javax.management.MBeanServer#queryMBeans queryMBeans} then
+        perform the query.</p>
 
 
         <h3>MBean lifecycle and resource injection</h3>
@@ -407,6 +407,92 @@
             So for example an SNMP GET operation might result in a
         <code>getAttribute</code> on the MBean Server.</p>
 
+	<h3 id="interop">Interoperability between versions of the JMX
+	  specification</h3>
+
+	<p>When a client connects to a server using the JMX Remote
+	  API, it is possible that they do not have the same version
+	  of the JMX specification.  The version of the JMX
+	  specification described here is version 2.0.  Previous
+	  versions were 1.0, 1.1, 1.2, and 1.4.  (There was no 1.3.)
+	  The standard JMX Remote API is defined to work with version
+	  1.2 onwards, so in standards-based deployment the only
+	  interoperability questions that arise concern version 1.2
+	  onwards.</p>
+
+	<p>Every version of the JMX specification continues to
+	  implement the features of previous versions.  So when the
+	  client is running an earlier version than the server, there
+	  should not be any interoperability concerns.  The only
+	  exception is the unlikely one where a pre-2.0 client used
+	  the string {@code //} in the domain part of an {@link
+	  javax.management.ObjectName ObjectName}.</p>
+
+	<p>When the client is running a later version than the server,
+	  certain newer features may not be available, as detailed in
+	  the next sections.  The method {@link
+	  javax.management.JMX#getSpecificationVersion
+	  JMX.getSpecificationVersion} can be used to determine the
+	  server version to check if required features are
+	  available.</p>
+
+	<h4 id="interop-1.4">If the remote MBean Server is 1.4</h4>
+
+	<ul>
+
+	  <li><p>You cannot use {@link
+	      javax.management.QueryNotificationFilter
+	      QueryNotificationFilter} in {@link
+	      javax.management.MBeanServerConnection#addNotificationListener
+	      addNotificationListener} since this class did not exist
+	      in 1.4.</p>
+
+	  <li><p>In an attribute in a query, you cannot access values
+	      inside complex types using dot syntax, for example
+	      {@link javax.management.Query#attr Query.attr}{@code
+	      ("HeapMemoryUsage.used")}.</p>
+
+	  <li><p>The packages {@link javax.management.event} and
+	      {@link javax.management.namespace} did not exist in 1.4,
+	      so you cannot remotely create instances of the MBeans
+	      they define.</p>
+
+	  <li><p>Even if the remote MBean Server is 2.0, you cannot in
+	      general suppose that {@link
+	      javax.management.event.EventClient EventClient} or
+	      {@link javax.management.ClientContext ClientContext}
+	      will work there without first checking. If the remote
+	      MBean Server is 1.4 then those checks will return false.
+	      An attempt to use these features without checking will
+	      fail in the same way as for a remote 2.0 that is not
+	      configured to support them.</p>
+	</ul>
+
+	<h4 id="interop-1.2">If the remote MBean Server is 1.2</h4>
+
+	<p><b>In addition to the above</b>,</p>
+
+	<ul>
+
+	  <li><p>You cannot use wildcards in a key property of an
+	      {@link javax.management.ObjectName ObjectName}, for
+	      example {@code domain:type=Foo,name=*}. Wildcards that
+	      match whole properties are still allowed, for example
+	      {@code *:*} or {@code *:type=Foo,*}.</p>
+
+	  <li><p>You cannot use {@link
+	      javax.management.Query#isInstanceOf Query.isInstanceOf}
+	      in a query.</p>
+
+	  <li><p>You cannot use dot syntax such as {@code
+	      HeapMemoryUsage.used} in the {@linkplain
+	      javax.management.monitor.Monitor#setObservedAttribute
+	      observed attribute} of a monitor, as described in the
+	      documentation for the {@link javax.management.monitor}
+	      package.</p>
+
+	</ul>
+
         <p id="spec">
         @see <a href="{@docRoot}/../technotes/guides/jmx/index.html">
         Java SE 6 Platform documentation on JMX technology</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/MBeanServer/AttributeListMapTest.java	Fri Nov 07 19:19:08 2008 +0100
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 6336968
+ * @summary Test AttributeList.toMap
+ * @author Eamonn McManus
+ */
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+
+public class AttributeListMapTest {
+
+    private static String failure;
+
+    public static void main(String[] args) throws Exception {
+        AttributeList attrs = new AttributeList(Arrays.asList(
+            new Attribute("Str", "Five"),
+            new Attribute("Int", 5),
+            new Attribute("Flt", 5.0)));
+
+        Map<String, Object> map = attrs.toMap();
+        final Map<String, Object> expectMap = new HashMap<String, Object>();
+        for (Attribute attr : attrs.asList())
+            expectMap.put(attr.getName(), attr.getValue());
+        assertEquals("Initial map", expectMap, map);
+        assertEquals("Initial map size", 3, map.size());
+        assertEquals("Name set", expectMap.keySet(), map.keySet());
+        assertEquals("Values", new HashSet<Object>(expectMap.values()),
+                               new HashSet<Object>(map.values()));
+        assertEquals("Entry set", expectMap.entrySet(), map.entrySet());
+
+        AttributeList attrs2 = new AttributeList(map);
+        assertEquals("AttributeList from Map", attrs, attrs2);
+        // This assumes that the Map conserves the order of the attributes,
+        // which is not specified but true because we use LinkedHashMap.
+
+        // Check that toMap fails if the list contains non-Attribute elements.
+        AttributeList attrs3 = new AttributeList(attrs);
+        attrs3.add("Hello");  // allowed but curious
+        try {
+            map = attrs3.toMap();
+            fail("toMap succeeded on list with non-Attribute elements");
+        } catch (Exception e) {
+            assertEquals("Exception for toMap with non-Atttribute elements",
+                    IllegalArgumentException.class, e.getClass());
+        }
+
+        // Check that the Map does not reflect changes made to the list after
+        // the Map was obtained.
+        AttributeList attrs4 = new AttributeList(attrs);
+        map = attrs4.toMap();
+        attrs4.add(new Attribute("Big", new BigInteger("5")));
+        assertEquals("Map after adding element to list", expectMap, map);
+
+        // Check that if there is more than one Attribute with the same name
+        // then toMap() chooses the last of them.
+        AttributeList attrs5 = new AttributeList(attrs);
+        attrs5.add(new Attribute("Str", "Cinq"));
+        map = attrs5.toMap();
+        assertEquals("Size of Map for list with duplicate attribute name",
+                3, map.size());
+        Object value = map.get("Str");
+        assertEquals("Value of Str in Map for list with two values for it",
+                "Cinq", value);
+
+        if (failure == null)
+            System.out.println("TEST PASSED");
+        else
+            throw new Exception("TEST FAILED: " + failure);
+    }
+
+    private static void assertEquals(String what, Object expect, Object actual) {
+        if (eq(expect, actual))
+            System.out.println("OK: " + what);
+        else
+            fail(what + ": expected " + expect + ", got " + actual);
+    }
+
+    private static boolean eq(Object x, Object y) {
+        return (x == null) ? (y == null) : x.equals(y);
+    }
+
+    private static void fail(String why) {
+        System.out.println("FAIL: " + why);
+        failure = why;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/MBeanServer/AttributeListTypeSafeTest.java	Fri Nov 07 19:19:08 2008 +0100
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 6336968
+ * @summary Test adding non-Attribute values to an AttributeList.
+ * @author Eamonn McManus
+ */
+
+import java.util.Collections;
+import java.util.List;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+
+public class AttributeListTypeSafeTest {
+
+    private static String failure;
+
+    public static void main(String[] args) throws Exception {
+        // Test calling asList after adding non-Attribute by various means
+        for (Op op : Op.values()) {
+            AttributeList alist = new AttributeList();
+            alist.add(new Attribute("foo", "bar"));
+            doOp(alist, op);
+            String what = "asList() after calling " + op + " with non-Attribute";
+            try {
+                List<Attribute> lista = alist.asList();
+                fail(what + ": succeeded but should not have");
+            } catch (IllegalArgumentException e) {
+                System.out.println("OK: " + what + ": got IllegalArgumentException");
+            }
+        }
+
+        // Test adding non-Attribute by various means after calling asList
+        for (Op op : Op.values()) {
+            AttributeList alist = new AttributeList();
+            List<Attribute> lista = alist.asList();
+            lista.add(new Attribute("foo", "bar"));
+            String what = op + " with non-Attribute after calling asList()";
+            try {
+                doOp(alist, op);
+                fail(what + ": succeeded but should not have");
+            } catch (IllegalArgumentException e) {
+                System.out.println("OK: " + what + ": got IllegalArgumentException");
+            }
+        }
+
+        if (failure == null)
+            System.out.println("TEST PASSED");
+        else
+            throw new Exception("TEST FAILED: " + failure);
+    }
+
+    private static enum Op {
+        ADD("add(Object)"), ADD_AT("add(int, Object)"),
+        ADD_ALL("add(Collection)"), ADD_ALL_AT("add(int, Collection)"),
+        SET("set(int, Object)");
+
+        private Op(String what) {
+            this.what = what;
+        }
+
+        @Override
+        public String toString() {
+            return what;
+        }
+
+        private final String what;
+    }
+
+    private static void doOp(AttributeList alist, Op op) {
+        Object x = "oops";
+        switch (op) {
+            case ADD: alist.add(x); break;
+            case ADD_AT: alist.add(0, x); break;
+            case ADD_ALL: alist.add(Collections.singleton(x)); break;
+            case ADD_ALL_AT: alist.add(0, Collections.singleton(x)); break;
+            case SET: alist.set(0, x); break;
+            default: throw new AssertionError("Case not covered");
+        }
+    }
+
+    private static void fail(String why) {
+        System.out.println("FAIL: " + why);
+        failure = why;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/openmbean/CompositeDataToMapTest.java	Fri Nov 07 19:19:08 2008 +0100
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 6750472 6752563
+ * @summary Test CompositeDataSupport.toMap.
+ * @author Eamonn McManus
+ * @run main/othervm -ea CompositeDataToMapTest
+ */
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+public class CompositeDataToMapTest {
+    private static class IdentityInvocationHandler implements InvocationHandler {
+        private final Object wrapped;
+
+        public IdentityInvocationHandler(Object wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        public Object invoke(Object proxy, Method m, Object[] args)
+                throws Throwable {
+            try {
+                return m.invoke(wrapped, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    private static <T> T wrap(T x, Class<T> intf) {
+        InvocationHandler ih = new IdentityInvocationHandler(x);
+        return intf.cast(Proxy.newProxyInstance(
+                intf.getClassLoader(), new Class<?>[] {intf}, ih));
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (!CompositeDataToMapTest.class.desiredAssertionStatus())
+            throw new AssertionError("Must be run with -ea");
+
+        CompositeType emptyCT = new CompositeType(
+                "empty", "empty", new String[0], new String[0], new OpenType<?>[0]);
+        CompositeData emptyCD = new CompositeDataSupport(
+                emptyCT, Collections.<String, Object>emptyMap());
+        assert CompositeDataSupport.toMap(emptyCD).isEmpty() :
+            "Empty CD produces empty Map";
+
+        CompositeData emptyCD2 = new CompositeDataSupport(
+                emptyCT, new String[0], new Object[0]);
+        assert emptyCD.equals(emptyCD2) : "Empty CD can be constructed two ways";
+
+        CompositeType namedNumberCT = new CompositeType(
+                "NamedNumber", "NamedNumber",
+                new String[] {"name", "number"},
+                new String[] {"name", "number"},
+                new OpenType<?>[] {SimpleType.STRING, SimpleType.INTEGER});
+        Map<String, Object> namedNumberMap = new HashMap<String, Object>();
+        namedNumberMap.put("name", "Deich");
+        namedNumberMap.put("number", 10);
+        CompositeData namedNumberCD = new CompositeDataSupport(
+                namedNumberCT, namedNumberMap);
+        assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) :
+            "Map survives passage through CompositeData";
+
+        namedNumberCD = wrap(namedNumberCD, CompositeData.class);
+        assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) :
+            "Map survives passage through wrapped CompositeData";
+
+        namedNumberMap = CompositeDataSupport.toMap(namedNumberCD);
+        namedNumberMap.put("name", "Ceathar");
+        namedNumberMap.put("number", 4);
+        namedNumberCD = new CompositeDataSupport(namedNumberCT, namedNumberMap);
+        assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) :
+            "Modified Map survives passage through CompositeData";
+
+        try {
+            namedNumberMap = CompositeDataSupport.toMap(null);
+            assert false : "Null toMap arg provokes exception";
+        } catch (Exception e) {
+            assert e instanceof IllegalArgumentException :
+                "Exception for null toMap arg is IllegalArgumentException";
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/remote/mandatory/version/JMXSpecVersionTest.java	Fri Nov 07 19:19:08 2008 +0100
@@ -0,0 +1,308 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 6750008
+ * @summary Test JMX.getSpecificationVersion
+ * @author Eamonn McManus
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.ListIterator;
+import java.util.Set;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMX;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerDelegateMBean;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.StandardMBean;
+import javax.management.namespace.JMXNamespace;
+import javax.management.namespace.JMXNamespaces;
+import javax.management.namespace.MBeanServerSupport;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+public class JMXSpecVersionTest {
+    private static String failure;
+    private static final Object POISON_PILL = new Object();
+
+    private static class FakeDelegate implements DynamicMBean {
+        private final Object specVersion;
+        private final DynamicMBean delegate = new StandardMBean(
+                new MBeanServerDelegate(), MBeanServerDelegateMBean.class, false);
+
+        FakeDelegate(Object specVersion) {
+            this.specVersion = specVersion;
+        }
+
+        public Object getAttribute(String attribute)
+                throws AttributeNotFoundException, MBeanException,
+                ReflectionException {
+            if ("SpecificationVersion".equals(attribute)) {
+                if (specVersion == POISON_PILL)
+                    throw new AttributeNotFoundException(attribute);
+                else
+                    return specVersion;
+            } else
+                return delegate.getAttribute(attribute);
+        }
+
+        public void setAttribute(Attribute attribute)
+                throws AttributeNotFoundException, InvalidAttributeValueException,
+                MBeanException, ReflectionException {
+            delegate.setAttribute(attribute);
+        }
+
+        public AttributeList getAttributes(String[] attributes) {
+            AttributeList list = delegate.getAttributes(attributes);
+            for (ListIterator<Attribute> it = list.asList().listIterator();
+                 it.hasNext(); ) {
+                Attribute attr = it.next();
+                if (attr.getName().equals("SpecificationVersion")) {
+                    it.remove();
+                    if (specVersion != POISON_PILL) {
+                        attr = new Attribute(attr.getName(), specVersion);
+                        it.add(attr);
+                    }
+                }
+            }
+            return list;
+        }
+
+        public AttributeList setAttributes(AttributeList attributes) {
+            return delegate.setAttributes(attributes);
+        }
+
+        public Object invoke(String actionName, Object[] params,
+                             String[] signature) throws MBeanException,
+                                                        ReflectionException {
+            return delegate.invoke(actionName, params, signature);
+        }
+
+        public MBeanInfo getMBeanInfo() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+
+    private static class MBeanServerWithVersion extends MBeanServerSupport {
+        private final DynamicMBean delegate;
+
+        public MBeanServerWithVersion(Object specVersion) {
+            this.delegate = new FakeDelegate(specVersion);
+        }
+
+        @Override
+        public DynamicMBean getDynamicMBeanFor(ObjectName name)
+                throws InstanceNotFoundException {
+            if (MBeanServerDelegate.DELEGATE_NAME.equals(name))
+                return delegate;
+            else
+                throw new InstanceNotFoundException(name);
+        }
+
+        @Override
+        protected Set<ObjectName> getNames() {
+            return Collections.singleton(MBeanServerDelegate.DELEGATE_NAME);
+        }
+    }
+
+    private static class EmptyMBeanServer extends MBeanServerSupport {
+        @Override
+        public DynamicMBean getDynamicMBeanFor(ObjectName name) throws InstanceNotFoundException {
+            throw new InstanceNotFoundException(name);
+        }
+
+        @Override
+        protected Set<ObjectName> getNames() {
+            return Collections.emptySet();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
+        JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
+                url, null, mbs);
+        cs.start();
+
+        String realVersion = (String) mbs.getAttribute(
+                MBeanServerDelegate.DELEGATE_NAME, "SpecificationVersion");
+        assertEquals("Reported local version",
+                realVersion, JMX.getSpecificationVersion(mbs, null));
+        assertEquals("Reported local version >= \"2.0\"",
+                true, (realVersion.compareTo("2.0") >= 0));
+
+        JMXConnector cc = JMXConnectorFactory.connect(cs.getAddress());
+        MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+        assertEquals("Reported remote version",
+                realVersion, JMX.getSpecificationVersion(mbsc, null));
+
+        cc.close();
+        try {
+            String brokenVersion = JMX.getSpecificationVersion(mbsc, null);
+            fail("JMX.getSpecificationVersion succeded over closed connection" +
+                    " (returned " + brokenVersion + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for closed connection",
+                    IOException.class, e.getClass());
+        }
+
+        try {
+            String brokenVersion = JMX.getSpecificationVersion(
+                    new EmptyMBeanServer(), null);
+            fail("JMX.getSpecificationVersion succeded with empty MBean Server" +
+                    " (returned " + brokenVersion + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for empty MBean Server",
+                    IOException.class, e.getClass());
+        }
+
+        try {
+            String brokenVersion = JMX.getSpecificationVersion(null, null);
+            fail("JMX.getSpecificationVersion succeded with null MBean Server" +
+                    " (returned " + brokenVersion + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for null MBean Server",
+                    IllegalArgumentException.class, e.getClass());
+        }
+
+        MBeanServer mbs1_2 = new MBeanServerWithVersion("1.2");
+        String version1_2 = JMX.getSpecificationVersion(mbs1_2, null);
+        assertEquals("Version for 1.2 MBean Server", "1.2", version1_2);
+
+        // It's completely nutty for an MBean Server to return null as the
+        // value of its spec version, and we don't actually say what happens
+        // in that case, but in fact we return the null to the caller.
+        MBeanServer mbs_null = new MBeanServerWithVersion(null);
+        String version_null = JMX.getSpecificationVersion(mbs_null, null);
+        assertEquals("Version for MBean Server that declares null spec version",
+                null, version_null);
+
+        try {
+            MBeanServer mbs1_2_float = new MBeanServerWithVersion(1.2f);
+            String version1_2_float =
+                    JMX.getSpecificationVersion(mbs1_2_float, null);
+            fail("JMX.getSpecificationVersion succeeded with version 1.2f" +
+                    " (returned " + version1_2_float + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for non-string version (1.2f)",
+                    IOException.class, e.getClass());
+        }
+
+        try {
+            MBeanServer mbs_missing = new MBeanServerWithVersion(POISON_PILL);
+            String version_missing =
+                    JMX.getSpecificationVersion(mbs_missing, null);
+            fail("JMX.getSpecificationVersion succeeded with null version" +
+                    " (returned " + version_missing + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for missing version",
+                    IOException.class, e.getClass());
+        }
+
+        ObjectName wildcardNamespaceName = new ObjectName("foo//*//bar//baz:k=v");
+        try {
+            String brokenVersion =
+                    JMX.getSpecificationVersion(mbsc, wildcardNamespaceName);
+            fail("JMX.getSpecificationVersion succeeded with wildcard namespace" +
+                    " (returned " + brokenVersion + ")");
+        } catch (Exception e) {
+            assertEquals("Exception for wildcard namespace",
+                    IllegalArgumentException.class, e.getClass());
+        }
+
+        String sub1_2namespace = "blibby";
+        JMXNamespace sub1_2 = new JMXNamespace(mbs1_2);
+        ObjectName sub1_2name =
+                JMXNamespaces.getNamespaceObjectName(sub1_2namespace);
+        mbs.registerMBean(sub1_2, sub1_2name);
+        String sub1_2namespaceHandlerVersion =
+                JMX.getSpecificationVersion(mbs, sub1_2name);
+        assertEquals("Spec version of namespace handler",
+                realVersion, sub1_2namespaceHandlerVersion);
+        // The namespace handler is in the top-level namespace so its
+        // version should not be 1.2.
+
+        for (String nameInSub : new String[] {"*:*", "d:k=v"}) {
+            ObjectName subName = new ObjectName(sub1_2namespace + "//" + nameInSub);
+            String subVersion = JMX.getSpecificationVersion(mbs, subName);
+            assertEquals("Spec version in 1.2 namespace (" + nameInSub + ")",
+                    "1.2", subVersion);
+        }
+
+        mbs.unregisterMBean(sub1_2name);
+        for (String noSuchNamespace : new String[] {
+            sub1_2namespace + "//*:*", sub1_2namespace + "//d:k=v",
+        }) {
+            try {
+                String brokenVersion = JMX.getSpecificationVersion(
+                        mbs, new ObjectName(noSuchNamespace));
+                fail("JMX.getSpecificationVersion succeeded with missing " +
+                        "namespace (" + noSuchNamespace + " -> " +
+                        brokenVersion);
+            } catch (Exception e) {
+                assertEquals("Exception for missing namespace",
+                        IOException.class, e.getClass());
+            }
+        }
+
+        if (failure != null)
+            throw new Exception("TEST FAILED: " + failure);
+        System.out.println("TEST PASSED");
+    }
+
+    private static void assertEquals(String what, Object expect, Object actual) {
+        if (equal(expect, actual))
+            System.out.println("OK: " + what + ": " + expect);
+        else
+            fail(what + ": expected " + expect + ", got " + actual);
+    }
+
+    private static  boolean equal(Object x, Object y) {
+        if (x == null)
+            return (y == null);
+        else
+            return x.equals(y);
+    }
+
+    private static void fail(String why) {
+        System.out.println("FAILED: " + why);
+        failure = why;
+    }
+}