changeset 707:5c1a8571946f

Merge
author tbell
date Thu, 13 Nov 2008 11:15:01 -0800
parents 2201dad60231 84bd7fd5fb65
children 16efbe49c725
files src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java
diffstat 84 files changed, 6775 insertions(+), 2001 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/jmx/defaults/ServiceName.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/defaults/ServiceName.java	Thu Nov 13 11:15:01 2008 -0800
@@ -69,9 +69,9 @@
     /**
      * The version of the JMX specification implemented by this product.
      * <BR>
-     * The value is <CODE>1.4</CODE>.
+     * The value is <CODE>2.0</CODE>.
      */
-    public static final String JMX_SPEC_VERSION = "1.4";
+    public static final String JMX_SPEC_VERSION = "2.0";
 
     /**
      * The vendor of the JMX specification implemented by this product.
--- a/src/share/classes/com/sun/jmx/event/EventParams.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/event/EventParams.java	Thu Nov 13 11:15:01 2008 -0800
@@ -41,7 +41,7 @@
 
     @SuppressWarnings("cast") // cast for jdk 1.5
     public static long getLeaseTimeout() {
-        long timeout = EventClient.DEFAULT_LEASE_TIMEOUT;
+        long timeout = EventClient.DEFAULT_REQUESTED_LEASE_TIME;
         try {
             final GetPropertyAction act =
                   new GetPropertyAction(DEFAULT_LEASE_TIMEOUT);
--- a/src/share/classes/com/sun/jmx/event/LeaseManager.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/event/LeaseManager.java	Thu Nov 13 11:15:01 2008 -0800
@@ -29,6 +29,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -143,9 +144,10 @@
     private final Runnable callback;
     private ScheduledFuture<?> scheduled;  // If null, the lease has expired.
 
+    private static final ThreadFactory threadFactory =
+            new DaemonThreadFactory("JMX LeaseManager %d");
     private final ScheduledExecutorService executor
-            = Executors.newScheduledThreadPool(1,
-            new DaemonThreadFactory("JMX LeaseManager %d"));
+            = Executors.newScheduledThreadPool(1, threadFactory);
 
     private static final ClassLogger logger =
             new ClassLogger("javax.management.event", "LeaseManager");
--- a/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java	Thu Nov 13 11:15:01 2008 -0800
@@ -55,9 +55,19 @@
 import javax.management.namespace.MBeanServerSupport;
 import javax.management.remote.IdentityMBeanServerForwarder;
 
+/**
+ * <p>An {@link MBeanServerForwarder} that simulates the existence of a
+ * given MBean.  Requests for that MBean, call it X, are intercepted by the
+ * forwarder, and requests for any other MBean are forwarded to the next
+ * forwarder in the chain.  Requests such as queryNames which can span both the
+ * X and other MBeans are handled by merging the results for X with the results
+ * from the next forwarder, unless the "visible" parameter is false, in which
+ * case X is invisible to such requests.</p>
+ */
 public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
 
     private final ObjectName mbeanName;
+    private final boolean visible;
     private DynamicMBean mbean;
 
     private MBeanServer mbeanMBS = new MBeanServerSupport() {
@@ -85,10 +95,20 @@
             return null;
         }
 
+        // This will only be called if mbeanName has an empty domain.
+        // In that case a getAttribute (e.g.) of that name will have the
+        // domain replaced by MBeanServerSupport with the default domain,
+        // so we must be sure that the default domain is empty too.
+        @Override
+        public String getDefaultDomain() {
+            return mbeanName.getDomain();
+        }
     };
 
-    public SingleMBeanForwarder(ObjectName mbeanName, DynamicMBean mbean) {
+    public SingleMBeanForwarder(
+            ObjectName mbeanName, DynamicMBean mbean, boolean visible) {
         this.mbeanName = mbeanName;
+        this.visible = visible;
         setSingleMBean(mbean);
     }
 
@@ -213,8 +233,10 @@
 
     @Override
     public String[] getDomains() {
-        TreeSet<String> domainSet =
-                new TreeSet<String>(Arrays.asList(super.getDomains()));
+        String[] domains = super.getDomains();
+        if (!visible)
+            return domains;
+        TreeSet<String> domainSet = new TreeSet<String>(Arrays.asList(domains));
         domainSet.add(mbeanName.getDomain());
         return domainSet.toArray(new String[domainSet.size()]);
     }
@@ -222,7 +244,7 @@
     @Override
     public Integer getMBeanCount() {
         Integer count = super.getMBeanCount();
-        if (!super.isRegistered(mbeanName))
+        if (visible && !super.isRegistered(mbeanName))
             count++;
         return count;
     }
@@ -284,7 +306,7 @@
      */
     private boolean applies(ObjectName pattern) {
         // we know pattern is not null.
-        if (!pattern.apply(mbeanName))
+        if (!visible || !pattern.apply(mbeanName))
             return false;
 
         final String dompat = pattern.getDomain();
@@ -306,10 +328,12 @@
     @Override
     public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
         Set<ObjectInstance> names = super.queryMBeans(name, query);
-        if (name == null || applies(name) ) {
-            // Don't assume mbs.queryNames returns a writable set.
-            names = Util.cloneSet(names);
-            names.addAll(mbeanMBS.queryMBeans(name, query));
+        if (visible) {
+            if (name == null || applies(name) ) {
+                // Don't assume mbs.queryNames returns a writable set.
+                names = Util.cloneSet(names);
+                names.addAll(mbeanMBS.queryMBeans(name, query));
+            }
         }
         return names;
     }
@@ -317,10 +341,12 @@
     @Override
     public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
         Set<ObjectName> names = super.queryNames(name, query);
-        if (name == null || applies(name)) {
-            // Don't assume mbs.queryNames returns a writable set.
-            names = Util.cloneSet(names);
-            names.addAll(mbeanMBS.queryNames(name, query));
+        if (visible) {
+            if (name == null || applies(name)) {
+                // Don't assume mbs.queryNames returns a writable set.
+                names = Util.cloneSet(names);
+                names.addAll(mbeanMBS.queryNames(name, query));
+            }
         }
         return names;
     }
--- a/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java	Thu Nov 13 11:15:01 2008 -0800
@@ -122,7 +122,7 @@
      *     {@link javax.management.MBeanServerFactory#newMBeanServer(java.lang.String)}
      *     instead.
      *     <p>
-     *     By default, {@link MBeanServerInterceptor} are disabled. Use
+     *     By default, interceptors are disabled. Use
      *     {@link #JmxMBeanServer(java.lang.String,javax.management.MBeanServer,javax.management.MBeanServerDelegate,boolean)} to enable them.
      * </ul>
      * @param domain The default domain name used by this MBeanServer.
@@ -239,7 +239,7 @@
         this.mBeanServerDelegateObject = delegate;
         this.outerShell   = outer;
 
-        final Repository repository = new Repository(domain,fairLock);
+        final Repository repository = new Repository(domain);
         this.mbsInterceptor =
             new NamespaceDispatchInterceptor(outer, delegate, instantiator,
                                               repository);
--- a/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java	Fri Nov 07 11:45:22 2008 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,354 +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 com.sun.jmx.namespace;
-
-import com.sun.jmx.defaults.JmxProperties;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.management.ListenerNotFoundException;
-import javax.management.MBeanServerConnection;
-import javax.management.NotificationFilter;
-import javax.management.NotificationListener;
-import javax.management.event.EventClient;
-import javax.management.event.EventClientDelegateMBean;
-import javax.management.namespace.JMXNamespace;
-import javax.management.namespace.JMXNamespaces;
-import javax.management.remote.JMXAddressable;
-import javax.management.remote.JMXConnector;
-import javax.management.remote.JMXServiceURL;
-import javax.security.auth.Subject;
-
-/**
- * A collection of methods that provide JMXConnector wrappers for
- * JMXRemoteNamepaces underlying connectors.
- * <p><b>
- * This API is a Sun internal API and is subject to changes without notice.
- * </b></p>
- * @since 1.7
- */
-public final class JMXNamespaceUtils {
-
-    /**
-     * A logger for this class.
-     **/
-    private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
-
-
-    private static <K,V> Map<K,V> newWeakHashMap() {
-        return new WeakHashMap<K,V>();
-    }
-
-    /** There are no instances of this class */
-    private JMXNamespaceUtils() {
-    }
-
-    // returns un unmodifiable view of a map.
-    public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
-        if (aMap == null || aMap.isEmpty())
-            return Collections.emptyMap();
-        return Collections.unmodifiableMap(aMap);
-    }
-
-
-    /**
-     * A base class that helps writing JMXConnectors that return
-     * MBeanServerConnection wrappers.
-     * This base class wraps an inner JMXConnector (the source), and preserve
-     * its caching policy. If a connection is cached in the source, its wrapper
-     * will be cached in this connector too.
-     * Author's note: rewriting this with java.lang.reflect.Proxy could be
-     * envisaged. It would avoid the combinatory sub-classing introduced by
-     * JMXAddressable.
-     * <p>
-     * Note: all the standard JMXConnector implementations are serializable.
-     *       This implementation here is not. Should it be?
-     *       I believe it must not be serializable unless it becomes
-     *       part of a public API (either standard or officially exposed
-     *       and supported in a documented com.sun package)
-     **/
-     static class JMXCachingConnector
-            implements JMXConnector  {
-
-        // private static final long serialVersionUID = -2279076110599707875L;
-
-        final JMXConnector source;
-
-        // if this object is made serializable, then the variable below
-        // needs to become volatile transient and be lazyly-created...
-        private final
-                Map<MBeanServerConnection,MBeanServerConnection> connectionMap;
-
-
-        public JMXCachingConnector(JMXConnector source) {
-            this.source = checkNonNull(source, "source");
-            connectionMap = newWeakHashMap();
-        }
-
-        private MBeanServerConnection
-                getCached(MBeanServerConnection inner) {
-            return connectionMap.get(inner);
-        }
-
-        private MBeanServerConnection putCached(final MBeanServerConnection inner,
-                final MBeanServerConnection wrapper) {
-            if (inner == wrapper) return wrapper;
-            synchronized (this) {
-                final MBeanServerConnection concurrent =
-                        connectionMap.get(inner);
-                if (concurrent != null) return concurrent;
-                connectionMap.put(inner,wrapper);
-            }
-            return wrapper;
-        }
-
-        public void addConnectionNotificationListener(NotificationListener
-                listener, NotificationFilter filter, Object handback) {
-            source.addConnectionNotificationListener(listener,filter,handback);
-        }
-
-        public void close() throws IOException {
-            source.close();
-        }
-
-        public void connect() throws IOException {
-            source.connect();
-        }
-
-        public void connect(Map<String,?> env) throws IOException {
-            source.connect(env);
-        }
-
-        public String getConnectionId() throws IOException {
-            return source.getConnectionId();
-        }
-
-        /**
-         * Preserve caching policy of the underlying connector.
-         **/
-        public MBeanServerConnection
-                getMBeanServerConnection() throws IOException {
-            final MBeanServerConnection inner =
-                    source.getMBeanServerConnection();
-            final MBeanServerConnection cached = getCached(inner);
-            if (cached != null) return cached;
-            final MBeanServerConnection wrapper = wrap(inner);
-            return putCached(inner,wrapper);
-        }
-
-        public MBeanServerConnection
-                getMBeanServerConnection(Subject delegationSubject)
-                throws IOException {
-            final MBeanServerConnection wrapped =
-                    source.getMBeanServerConnection(delegationSubject);
-            synchronized (this) {
-                final MBeanServerConnection cached = getCached(wrapped);
-                if (cached != null) return cached;
-                final MBeanServerConnection wrapper =
-                    wrapWithSubject(wrapped,delegationSubject);
-                return putCached(wrapped,wrapper);
-            }
-        }
-
-        public void removeConnectionNotificationListener(
-                NotificationListener listener)
-                throws ListenerNotFoundException {
-            source.removeConnectionNotificationListener(listener);
-        }
-
-        public void removeConnectionNotificationListener(
-                NotificationListener l, NotificationFilter f,
-                Object handback) throws ListenerNotFoundException {
-            source.removeConnectionNotificationListener(l,f,handback);
-        }
-
-        /**
-         * This is the method that subclass will redefine. This method
-         * is called by {@code this.getMBeanServerConnection()}.
-         * {@code inner} is the connection returned by
-         * {@code source.getMBeanServerConnection()}.
-         **/
-        protected MBeanServerConnection wrap(MBeanServerConnection inner)
-            throws IOException {
-            return inner;
-        }
-
-        /**
-         * Subclass may also want to redefine this method.
-         * By default it calls wrap(inner). This method
-         * is called by {@code this.getMBeanServerConnection(Subject)}.
-         * {@code inner} is the connection returned by
-         * {@code source.getMBeanServerConnection(Subject)}.
-         **/
-        protected MBeanServerConnection wrapWithSubject(
-                MBeanServerConnection inner, Subject delegationSubject)
-            throws IOException {
-                return wrap(inner);
-        }
-
-        @Override
-        public String toString() {
-            if (source instanceof JMXAddressable) {
-                final JMXServiceURL address =
-                        ((JMXAddressable)source).getAddress();
-                if (address != null)
-                    return address.toString();
-            }
-            return source.toString();
-        }
-
-    }
-
-
-    /**
-     * The name space connector can do 'cd'
-     **/
-    static class JMXNamespaceConnector extends JMXCachingConnector {
-
-        // private static final long serialVersionUID = -4813611540843020867L;
-
-        private final String toDir;
-        private final boolean closeable;
-
-        public JMXNamespaceConnector(JMXConnector source, String toDir,
-                boolean closeable) {
-            super(source);
-            this.toDir = toDir;
-            this.closeable = closeable;
-        }
-
-        @Override
-        public void close() throws IOException {
-            if (!closeable)
-                throw new UnsupportedOperationException("close");
-            else super.close();
-        }
-
-        @Override
-        protected MBeanServerConnection wrap(MBeanServerConnection wrapped)
-               throws IOException {
-            if (LOG.isLoggable(Level.FINER))
-                LOG.finer("Creating name space proxy connection for source: "+
-                        "namespace="+toDir);
-            return JMXNamespaces.narrowToNamespace(wrapped,toDir);
-        }
-
-        @Override
-        public String toString() {
-            return "JMXNamespaces.narrowToNamespace("+
-                    super.toString()+
-                    ", \""+toDir+"\")";
-        }
-
-    }
-
-    static class JMXEventConnector extends JMXCachingConnector {
-
-        // private static final long serialVersionUID = 4742659236340242785L;
-
-        JMXEventConnector(JMXConnector wrapped) {
-            super(wrapped);
-        }
-
-        @Override
-        protected MBeanServerConnection wrap(MBeanServerConnection inner)
-                throws IOException {
-            return EventClient.getEventClientConnection(inner);
-        }
-
-
-        @Override
-        public String toString() {
-            return "EventClient.withEventClient("+super.toString()+")";
-        }
-    }
-
-    static class JMXAddressableEventConnector extends JMXEventConnector
-        implements JMXAddressable {
-
-        // private static final long serialVersionUID = -9128520234812124712L;
-
-        JMXAddressableEventConnector(JMXConnector wrapped) {
-            super(wrapped);
-        }
-
-        public JMXServiceURL getAddress() {
-            return ((JMXAddressable)source).getAddress();
-        }
-    }
-
-    /**
-     * Creates a connector whose MBeamServerConnection will point to the
-     * given sub name space inside the source connector.
-     * @see JMXNamespace
-     **/
-    public static JMXConnector cd(final JMXConnector source,
-                                  final String toNamespace,
-                                  final boolean closeable)
-        throws IOException {
-
-        checkNonNull(source, "JMXConnector");
-
-        if (toNamespace == null || toNamespace.equals(""))
-            return source;
-
-        return new JMXNamespaceConnector(source,toNamespace,closeable);
-    }
-
-
-    /**
-     * Returns a JMX Connector that will use an {@link EventClient}
-     * to subscribe for notifications. If the server doesn't have
-     * an {@link EventClientDelegateMBean}, then the connector will
-     * use the legacy notification mechanism instead.
-     *
-     * @param source The underlying JMX Connector wrapped by the returned
-     *               connector.
-     * @return A JMX Connector that will uses an {@link EventClient}, if
-     *         available.
-     * @see EventClient#getEventClientConnection(MBeanServerConnection)
-     */
-    public static JMXConnector withEventClient(final JMXConnector source) {
-        checkNonNull(source, "JMXConnector");
-        if (source instanceof JMXAddressable)
-            return new JMXAddressableEventConnector(source);
-        else
-            return new JMXEventConnector(source);
-    }
-
-    public static <T> T checkNonNull(T parameter, String name) {
-        if (parameter == null)
-            throw new IllegalArgumentException(name+" must not be null");
-         return parameter;
-    }
-
-
-}
--- a/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java	Thu Nov 13 11:15:01 2008 -0800
@@ -49,11 +49,6 @@
     final int tlen;
     final boolean identity;
 
-
-    public ObjectNameRouter(String targetDirName) {
-        this(targetDirName,null);
-    }
-
     /** Creates a new instance of ObjectNameRouter */
     public ObjectNameRouter(final String remove, final String add) {
         this.targetPrefix = (remove==null?"":remove);
@@ -186,6 +181,4 @@
             b.append(NAMESPACE_SEPARATOR);
         return b.toString();
     }
-
-
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java	Thu Nov 13 11:15:01 2008 -0800
@@ -31,7 +31,6 @@
 import java.util.logging.Logger;
 
 import javax.management.MBeanServerConnection;
-import javax.management.namespace.JMXNamespaces;
 
 
 /**
@@ -61,18 +60,10 @@
      * Creates a new instance of RoutingConnectionProxy
      */
     public RoutingConnectionProxy(MBeanServerConnection source,
-                               String sourceDir) {
-        this(source,sourceDir,"",false);
-    }
-
-    /**
-     * Creates a new instance of RoutingConnectionProxy
-     */
-    public RoutingConnectionProxy(MBeanServerConnection source,
                                String sourceDir,
                                String targetDir,
-                               boolean forwardsContext) {
-        super(source,sourceDir,targetDir,forwardsContext);
+                               boolean probe) {
+        super(source, sourceDir, targetDir, probe);
 
         if (LOG.isLoggable(Level.FINER))
             LOG.finer("RoutingConnectionProxy for " + getSourceNamespace() +
@@ -85,15 +76,13 @@
         final String sourceNs = getSourceNamespace();
         String wrapped = String.valueOf(source());
         if ("".equals(targetNs)) {
-            if (forwardsContext)
-                wrapped = "ClientContext.withDynamicContext("+wrapped+")";
             return "JMXNamespaces.narrowToNamespace("+
                     wrapped+", \""+
                     sourceNs+"\")";
         }
         return this.getClass().getSimpleName()+"("+wrapped+", \""+
                sourceNs+"\", \""+
-               targetNs+"\", "+forwardsContext+")";
+               targetNs+"\")";
     }
 
     static final RoutingProxyFactory
@@ -102,22 +91,16 @@
         <MBeanServerConnection,RoutingConnectionProxy>() {
 
         public RoutingConnectionProxy newInstance(MBeanServerConnection source,
-                String sourcePath, String targetPath,
-                boolean forwardsContext) {
+                String sourcePath, String targetPath, boolean probe) {
             return new RoutingConnectionProxy(source,sourcePath,
-                    targetPath,forwardsContext);
-        }
-
-        public RoutingConnectionProxy newInstance(
-                MBeanServerConnection source, String sourcePath) {
-            return new RoutingConnectionProxy(source,sourcePath);
+                    targetPath, probe);
         }
     };
 
-    public static MBeanServerConnection cd(MBeanServerConnection source,
-            String sourcePath) {
+    public static MBeanServerConnection cd(
+            MBeanServerConnection source, String sourcePath, boolean probe) {
         return RoutingProxy.cd(RoutingConnectionProxy.class, FACTORY,
-                source, sourcePath);
+                source, sourcePath, probe);
     }
 
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java	Thu Nov 13 11:15:01 2008 -0800
@@ -30,6 +30,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.management.InstanceNotFoundException;
 import javax.management.MBeanException;
 import javax.management.MBeanRegistrationException;
 
@@ -90,17 +91,9 @@
 //     targetNs=<encoded-context> // context must be removed from object name
 //     sourceNs="" // nothing to add...
 //
-// RoutingProxies can also be used on the client side to implement
-// "withClientContext" operations. In that case, the boolean parameter
-// 'forwards context' is set to true, targetNs is "", and sourceNS may
-// also be "". When forwardsContext is true, the RoutingProxy dynamically
-// creates an ObjectNameRouter for each operation - in order to dynamically add
-// the context attached to the thread to the routing ObjectName. This is
-// performed in the getObjectNameRouter() method.
-//
 // Finally, in order to avoid too many layers of wrapping,
 // RoutingConnectionProxy and RoutingServerProxy can be created through a
-// factory method that can concatenate namespace pathes in order to
+// factory method that can concatenate namespace paths in order to
 // return a single RoutingProxy - rather than wrapping a RoutingProxy inside
 // another RoutingProxy. See RoutingConnectionProxy.cd and
 // RoutingServerProxy.cd
@@ -146,25 +139,27 @@
     private final T source;
 
     // The name space we're narrowing to (usually some name space in
-    // the source MBeanServerConnection
+    // the source MBeanServerConnection), e.g. "a" for the namespace
+    // "a//".  This is empty in the case of ClientContext described above.
     private final String                sourceNs;
 
-    // The name space we pretend to be mounted in (usually "")
+    // The name space we pretend to be mounted in.  This is empty except
+    // in the case of ClientContext described above (where it will be
+    // something like "jmx.context//foo=bar".
     private final String                targetNs;
 
     // The name of the JMXNamespace that handles the source name space
     private final ObjectName            handlerName;
     private final ObjectNameRouter      router;
-    final boolean forwardsContext;
     private volatile String             defaultDomain = null;
 
     /**
      * Creates a new instance of RoutingProxy
      */
     protected RoutingProxy(T source,
-                          String sourceNs,
-                          String targetNs,
-                          boolean forwardsContext) {
+                           String sourceNs,
+                           String targetNs,
+                           boolean probe) {
         if (source == null) throw new IllegalArgumentException("null");
         this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs);
 
@@ -177,13 +172,17 @@
             // System.err.println("sourceNs: "+sourceNs);
             this.handlerName =
                 JMXNamespaces.getNamespaceObjectName(this.sourceNs);
-            try {
-                // System.err.println("handlerName: "+handlerName);
-                if (!source.isRegistered(handlerName))
-                    throw new IllegalArgumentException(sourceNs +
-                            ": no such name space");
-            } catch (IOException x) {
-                throw new IllegalArgumentException("source stale: "+x,x);
+            if (probe) {
+                try {
+                    if (!source.isRegistered(handlerName)) {
+                        InstanceNotFoundException infe =
+                                new InstanceNotFoundException(handlerName);
+                        throw new IllegalArgumentException(sourceNs +
+                                ": no such name space", infe);
+                    }
+                } catch (IOException x) {
+                    throw new IllegalArgumentException("source stale: "+x,x);
+                }
             }
         }
         this.source = source;
@@ -191,7 +190,6 @@
             JMXNamespaces.normalizeNamespaceName(targetNs));
         this.router =
                 new ObjectNameRouter(this.targetNs,this.sourceNs);
-        this.forwardsContext = forwardsContext;
 
         if (LOG.isLoggable(Level.FINER))
             LOG.finer("RoutingProxy for " + this.sourceNs + " created");
@@ -200,14 +198,6 @@
     @Override
     public T source() { return source; }
 
-    ObjectNameRouter getObjectNameRouter() {
-// TODO: uncomment this when contexts are added
-//        if (forwardsContext)
-//            return ObjectNameRouter.wrapWithContext(router);
-//        else
-            return router;
-    }
-
     @Override
     public ObjectName toSource(ObjectName targetName)
         throws MalformedObjectNameException {
@@ -222,8 +212,7 @@
             if (defaultDomain != null)
                 targetName = targetName.withDomain(defaultDomain);
         }
-        final ObjectNameRouter r = getObjectNameRouter();
-        return r.toSourceContext(targetName,true);
+        return router.toSourceContext(targetName,true);
     }
 
     @Override
@@ -243,8 +232,7 @@
     public ObjectName toTarget(ObjectName sourceName)
         throws MalformedObjectNameException {
         if (sourceName == null) return null;
-        final ObjectNameRouter r = getObjectNameRouter();
-        return r.toTargetContext(sourceName,false);
+        return router.toTargetContext(sourceName,false);
     }
 
     private Object getAttributeFromHandler(String attributeName)
@@ -357,11 +345,8 @@
     // instance.
     static interface RoutingProxyFactory<T extends MBeanServerConnection,
             R extends RoutingProxy<T>> {
-            R newInstance(T source,
-                    String sourcePath, String targetPath,
-                    boolean forwardsContext);
-            R newInstance(T source,
-                    String sourcePath);
+            public R newInstance(
+                    T source, String sourcePath, String targetPath, boolean probe);
     }
 
     // Performs a narrowDownToNamespace operation.
@@ -377,7 +362,7 @@
     static <T extends MBeanServerConnection, R extends RoutingProxy<T>>
            R cd(Class<R> routingProxyClass,
               RoutingProxyFactory<T,R> factory,
-              T source, String sourcePath) {
+              T source, String sourcePath, boolean probe) {
         if (source == null) throw new IllegalArgumentException("null");
         if (source.getClass().equals(routingProxyClass)) {
             // cast is OK here, but findbugs complains unless we use class.cast
@@ -400,14 +385,13 @@
                 final String path =
                     JMXNamespaces.concat(other.getSourceNamespace(),
                     sourcePath);
-                return factory.newInstance(other.source(),path,"",
-                                           other.forwardsContext);
+                return factory.newInstance(other.source(), path, "", probe);
             }
             // Note: we could do possibly something here - but it would involve
             //       removing part of targetDir, and possibly adding
             //       something to sourcePath.
             //       Too complex to bother! => simply default to stacking...
         }
-        return factory.newInstance(source,sourcePath);
+        return factory.newInstance(source, sourcePath, "", probe);
     }
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java	Thu Nov 13 11:15:01 2008 -0800
@@ -54,7 +54,6 @@
 import javax.management.QueryExp;
 import javax.management.ReflectionException;
 import javax.management.loading.ClassLoaderRepository;
-import javax.management.namespace.JMXNamespaces;
 
 /**
  * A RoutingServerProxy is an MBeanServer proxy that proxies a
@@ -76,19 +75,11 @@
         extends RoutingProxy<MBeanServer>
         implements MBeanServer {
 
-    /**
-     * Creates a new instance of RoutingServerProxy
-     */
-    public RoutingServerProxy(MBeanServer source,
-                           String sourceNs) {
-        this(source,sourceNs,"",false);
-    }
-
     public RoutingServerProxy(MBeanServer source,
                                 String sourceNs,
                                 String targetNs,
-                                boolean forwardsContext) {
-        super(source,sourceNs,targetNs,forwardsContext);
+                                boolean probe) {
+        super(source, sourceNs, targetNs, probe);
     }
 
     /**
@@ -571,20 +562,15 @@
         FACTORY = new RoutingProxyFactory<MBeanServer,RoutingServerProxy>() {
 
         public RoutingServerProxy newInstance(MBeanServer source,
-                String sourcePath, String targetPath,
-                boolean forwardsContext) {
-            return new RoutingServerProxy(source,sourcePath,
-                    targetPath,forwardsContext);
-        }
-
-        public RoutingServerProxy newInstance(
-                MBeanServer source, String sourcePath) {
-            return new RoutingServerProxy(source,sourcePath);
+                String sourcePath, String targetPath, boolean probe) {
+            return new RoutingServerProxy(
+                    source, sourcePath, targetPath, probe);
         }
     };
 
-    public static MBeanServer cd(MBeanServer source, String sourcePath) {
+    public static MBeanServer cd(
+            MBeanServer source, String sourcePath, boolean probe) {
         return RoutingProxy.cd(RoutingServerProxy.class, FACTORY,
-                source, sourcePath);
+                source, sourcePath, probe);
     }
 }
--- a/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java	Thu Nov 13 11:15:01 2008 -0800
@@ -430,13 +430,11 @@
      *        The {@code EventClient} is created lazily, when it is needed
      *        for the first time. If null, a default factory will be used
      *        (see {@link #createEventClient}).
-     * @return the
+     * @return the MBeanServerConnection.
      **/
     public static MBeanServerConnection getEventConnectionFor(
                     MBeanServerConnection connection,
                     Callable<EventClient> eventClientFactory) {
-        // if c already uses an EventClient no need to create a new one.
-        //
         if (connection instanceof EventClientFactory
             && eventClientFactory != null)
             throw new IllegalArgumentException("connection already uses EventClient");
--- a/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java	Thu Nov 13 11:15:01 2008 -0800
@@ -497,6 +497,10 @@
         delegate.setFixedLengthStreamingMode(contentLength);
     }
 
+    public void setFixedLengthStreamingMode(long contentLength) {
+        delegate.setFixedLengthStreamingMode(contentLength);
+    }
+
     public void setChunkedStreamingMode (int chunklen) {
         delegate.setChunkedStreamingMode(chunklen);
     }
--- a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Thu Nov 13 11:15:01 2008 -0800
@@ -86,6 +86,8 @@
  * the principal name from the configuration is used. In the
  * case where the principal property is not set and the principal
  * entry also does not exist, the user is prompted for the name.
+ * When this property of entry is set, and <code>useTicketCache</code>
+ * is set to true, only TGT belonging to this principal is used.
  *
  * <p> The following is a list of configuration options supported
  * for <code>Krb5LoginModule</code>:
@@ -101,18 +103,19 @@
  * to false if you do not want this module to use the ticket cache.
  * (Default is False).
  * This module will
- * search for the tickect
+ * search for the ticket
  * cache in the following locations:
- * For Windows 2000, it will use Local Security Authority (LSA) API
- * to get the TGT. On Solaris and Linux
+ * On Solaris and Linux
  * it will look for the ticket cache in /tmp/krb5cc_<code>uid</code>
  * where the uid is numeric user
  * identifier. If the ticket cache is
- * not available in either of the above locations, or if we are on a
- * different Windows platform,  it will look for the cache as
+ * not available in the above location, or if we are on a
+ * Windows platform, it will look for the cache as
  * {user.home}{file.separator}krb5cc_{user.name}.
  * You can override the ticket cache location by using
- * <code>ticketCache</code>
+ * <code>ticketCache</code>.
+ * For Windows, if a ticket cannot be retrieved from the file ticket cache,
+ * it will use Local Security Authority (LSA) API to get the TGT.
  * <P>
  * <dt><b><code>ticketCache</code></b>:</dt>
  * <dd>Set this to the name of the ticket
@@ -129,20 +132,20 @@
  * <dt><b><code>doNotPrompt</code></b>:</dt>
  * <dd>Set this to true if you do not want to be
  * prompted for the password
- * if credentials can
- * not be obtained from the cache or keytab.(Default is false)
- * If set to true authentication will fail if credentials can
- * not be obtained from the cache or keytab.</dd>
+ * if credentials can not be obtained from the cache, the keytab,
+ * or through shared state.(Default is false)
+ * If set to true, credential must be obtained through cache, keytab,
+ * or shared state. Otherwise, authentication will fail.</dd>
  * <P>
  * <dt><b><code>useKeyTab</code></b>:</dt>
  * <dd>Set this to true if you
  * want the module to get the principal's key from the
  * the keytab.(default value is False)
- * If <code>keyatb</code>
+ * If <code>keytab</code>
  * is not set then
  * the module will locate the keytab from the
- * Kerberos configuration file.</dd>
- * If it is not specifed in the Kerberos configuration file
+ * Kerberos configuration file.
+ * If it is not specified in the Kerberos configuration file
  * then it will look for the file
  * <code>{user.home}{file.separator}</code>krb5.keytab.</dd>
  * <P>
@@ -210,20 +213,34 @@
  *                   exist for the username and password in the shared
  *                   state, or if authentication fails.
  *
- *    clearPass     if, true, this <code>LoginModule</code> clears the
- *                  username and password stored in the module's shared
- *                  state  after both phases of authentication
- *                  (login and commit)  have completed.
+ *    clearPass      if, true, this LoginModule clears the
+ *                   username and password stored in the module's shared
+ *                   state  after both phases of authentication
+ *                   (login and commit) have completed.
  * </pre>
+ * <p>If the principal system property or key is already provided, the value of
+ * "javax.security.auth.login.name" in the shared state is ignored.
+ * <p>When multiple mechanisms to retrieve a ticket or key is provided, the
+ * preference order looks like this:
+ * <ol>
+ * <li>ticket cache
+ * <li>keytab
+ * <li>shared state
+ * <li>user prompt
+ * </ol>
+ * <p>Note that if any step fails, it will fallback to the next step.
+ * There's only one exception, it the shared state step fails and
+ * <code>useFirstPass</code>=true, no user prompt is made.
  * <p>Examples of some configuration values for Krb5LoginModule in
  * JAAS config file and the results are:
  * <ul>
  * <p> <code>doNotPrompt</code>=true;
  * </ul>
- * <p> This is an illegal combination since <code>useTicketCache</code>
- * is not set and the user can not be prompted for the password.
+ * <p> This is an illegal combination since none of <code>useTicketCache</code>,
+ * <code>useKeyTab</code>, <code>useFirstPass</code> and <code>tryFirstPass</code>
+ * is set and the user can not be prompted for the password.
  *<ul>
- * <p> <code>ticketCache</code> = < filename >;
+ * <p> <code>ticketCache</code> = &lt;filename&gt;;
  *</ul>
  * <p> This is an illegal combination since <code>useTicketCache</code>
  * is not set to true and the ticketCache is set. A configuration error
@@ -240,9 +257,9 @@
  *</ul>
  * <p> This is an illegal combination since  <code>storeKey</code> is set to
  * true but the key can not be obtained either by prompting the user or from
- * the keytab.A configuration error will occur.
+ * the keytab, or from the shared state. A configuration error will occur.
  * <ul>
- * <p>  <code>keyTab</code> = < filename > <code>doNotPrompt</code>=true ;
+ * <p>  <code>keyTab</code> = &lt;filename&gt; <code>doNotPrompt</code>=true ;
  * </ul>
  * <p>This is an illegal combination since useKeyTab is not set to true and
  * the keyTab is set. A configuration error will occur.
@@ -260,7 +277,7 @@
  * with the principal and TGT. If the TGT is not available,
  * do not prompt the user, instead fail the authentication.
  * <ul>
- * <p><code>principal</code>=< name ><code>useTicketCache</code> = true
+ * <p><code>principal</code>=&lt;name&gt;<code>useTicketCache</code> = true
  * <code>doNotPrompt</code>=true;
  *</ul>
  * <p> Get the TGT from the default cache for the principal and populate the
@@ -269,9 +286,9 @@
  * authentication will fail.
  * <ul>
  * <p> <code>useTicketCache</code> = true
- * <code>ticketCache</code>=< file name ><code>useKeyTab</code> = true
- * <code> keyTab</code>=< keytab filename >
- * <code>principal</code> = < principal name >
+ * <code>ticketCache</code>=&lt;file name&gt;<code>useKeyTab</code> = true
+ * <code> keyTab</code>=&lt;keytab filename&gt;
+ * <code>principal</code> = &lt;principal name&gt;
  * <code>doNotPrompt</code>=true;
  *</ul>
  * <p>  Search the cache for the principal's TGT. If it is not available
@@ -281,7 +298,7 @@
  * If the key is not available or valid then authentication will fail.
  * <ul>
  * <p><code>useTicketCache</code> = true
- * <code>ticketCache</code>=< file name >
+ * <code>ticketCache</code>=&lt;file name&gt;
  *</ul>
  * <p> The TGT will be obtained from the cache specified.
  * The Kerberos principal name used will be the principal name in
@@ -292,8 +309,8 @@
  * The Subject will be populated with the TGT.
  *<ul>
  * <p> <code>useKeyTab</code> = true
- * <code>keyTab</code>=< keytab filename >
- * <code>principal</code>= < principal name >
+ * <code>keyTab</code>=&lt;keytab filename&gt;
+ * <code>principal</code>= &lt;principal name&gt;
  * <code>storeKey</code>=true;
  *</ul>
  * <p>  The key for the principal will be retrieved from the keytab.
@@ -303,7 +320,7 @@
  * password entered.
  * <ul>
  * <p> <code>useKeyTab</code> = true
- * <code>keyTab</code>=< keytabname >
+ * <code>keyTab</code>=&lt;keytabname&gt;
  * <code>storeKey</code>=true
  * <code>doNotPrompt</code>=true;
  *</ul>
@@ -316,21 +333,23 @@
  * Subject's private credentials set. Otherwise the authentication will
  * fail.
  *<ul>
- * <p><code>useKeyTab</code> = true
- * <code>keyTab</code>=< file name > <code>storeKey</code>=true
- * <code>principal</code>= < principal name >
+ * <p>
  * <code>useTicketCache</code>=true
- * <code>ticketCache</code>=< file name >;
+ * <code>ticketCache</code>=&lt;file name&gt;;
+ * <code>useKeyTab</code> = true
+ * <code>keyTab</code>=&lt;file name&gt; <code>storeKey</code>=true
+ * <code>principal</code>= &lt;principal name&gt;
  *</ul>
- * <p>The principal's key will be retrieved from the keytab and added
- * to the <code>Subject</code>'s private credentials. If the key
- * is not available, the
- * user will be prompted for the password; the key derived from the password
- * will be added to the Subject's private credentials set. The
- * client's TGT will be retrieved from the ticket cache and added to the
+ * <p>
+ * The client's TGT will be retrieved from the ticket cache and added to the
  * <code>Subject</code>'s private credentials. If the TGT is not available
- * in the ticket cache, it will be obtained using the authentication
+ * in the ticket cache, or the TGT's client name does not match the principal
+ * name, Java will use a secret key to obtain the TGT using the authentication
  * exchange and added to the Subject's private credentials.
+ * This secret key will be first retrieved from the keytab. If the key
+ * is not available, the user will be prompted for the password. In either
+ * case, the key derived from the password will be added to the
+ * Subject's private credentials set.
  * <ul>
  * <p><code>isInitiator</code> = false
  *</ul>
@@ -856,11 +875,13 @@
     }
 
     private void validateConfiguration() throws LoginException {
-        if (doNotPrompt && !useTicketCache && !useKeyTab)
+        if (doNotPrompt && !useTicketCache && !useKeyTab
+                && !tryFirstPass && !useFirstPass)
             throw new LoginException
                 ("Configuration Error"
                  + " - either doNotPrompt should be "
-                 + " false or useTicketCache/useKeyTab "
+                 + " false or at least one of useTicketCache, "
+                 + " useKeyTab, tryFirstPass and useFirstPass"
                  + " should be true");
         if (ticketCacheName != null && !useTicketCache)
             throw new LoginException
@@ -872,11 +893,12 @@
             throw new LoginException
                 ("Configuration Error - useKeyTab should be set to true "
                  + "to use the keytab" + keyTabName);
-        if (storeKey && doNotPrompt && !useKeyTab)
+        if (storeKey && doNotPrompt && !useKeyTab
+                && !tryFirstPass && !useFirstPass)
             throw new LoginException
-                ("Configuration Error - either doNotPrompt "
-                 + "should be set to false or "
-                 + "useKeyTab must be set to true for storeKey option");
+                ("Configuration Error - either doNotPrompt should be set to "
+                 + " false or at least one of tryFirstPass, useFirstPass "
+                 + "or useKeyTab must be set to true for storeKey option");
         if (renewTGT && !useTicketCache)
             throw new LoginException
                 ("Configuration Error"
--- a/src/share/classes/java/net/HttpURLConnection.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/java/net/HttpURLConnection.java	Thu Nov 13 11:15:01 2008 -0800
@@ -73,11 +73,24 @@
      * The fixed content-length when using fixed-length streaming mode.
      * A value of <code>-1</code> means fixed-length streaming mode is disabled
      * for output.
+     *
+     * <P> <B>NOTE:</B> {@link #fixedContentLengthLong} is recommended instead
+     * of this field, as it allows larger content lengths to be set.
+     *
      * @since 1.5
      */
     protected int fixedContentLength = -1;
 
     /**
+     * The fixed content-length when using fixed-length streaming mode.
+     * A value of {@code -1} means fixed-length streaming mode is disabled
+     * for output.
+     *
+     * @since 1.7
+     */
+    protected long fixedContentLengthLong = -1;
+
+    /**
      * Returns the key for the <code>n</code><sup>th</sup> header field.
      * Some implementations may treat the <code>0</code><sup>th</sup>
      * header field as special, i.e. as the status line returned by the HTTP
@@ -109,6 +122,9 @@
      * This exception can be queried for the details of the error.
      * <p>
      * This method must be called before the URLConnection is connected.
+     * <p>
+     * <B>NOTE:</B> {@link #setFixedLengthStreamingMode(long)} is recommended
+     * instead of this method as it allows larger content lengths to be set.
      *
      * @param   contentLength The number of bytes which will be written
      *          to the OutputStream.
@@ -135,6 +151,52 @@
         fixedContentLength = contentLength;
     }
 
+    /**
+     * This method is used to enable streaming of a HTTP request body
+     * without internal buffering, when the content length is known in
+     * advance.
+     *
+     * <P> An exception will be thrown if the application attempts to write
+     * more data than the indicated content-length, or if the application
+     * closes the OutputStream before writing the indicated amount.
+     *
+     * <P> When output streaming is enabled, authentication and redirection
+     * cannot be handled automatically. A {@linkplain HttpRetryException} will
+     * be thrown when reading the response if authentication or redirection
+     * are required. This exception can be queried for the details of the
+     * error.
+     *
+     * <P> This method must be called before the URLConnection is connected.
+     *
+     * <P> The content length set by invoking this method takes precedence
+     * over any value set by {@link #setFixedLengthStreamingMode(int)}.
+     *
+     * @param  contentLength
+     *         The number of bytes which will be written to the OutputStream.
+     *
+     * @throws  IllegalStateException
+     *          if URLConnection is already connected or if a different
+     *          streaming mode is already enabled.
+     *
+     * @throws  IllegalArgumentException
+     *          if a content length less than zero is specified.
+     *
+     * @since 1.7
+     */
+    public void setFixedLengthStreamingMode(long contentLength) {
+        if (connected) {
+            throw new IllegalStateException("Already connected");
+        }
+        if (chunkLength != -1) {
+            throw new IllegalStateException(
+                "Chunked encoding streaming mode set");
+        }
+        if (contentLength < 0) {
+            throw new IllegalArgumentException("invalid content length");
+        }
+        fixedContentLengthLong = contentLength;
+    }
+
     /* Default chunk size (including chunk header) if not specified;
      * we want to keep this in sync with the one defined in
      * sun.net.www.http.ChunkedOutputStream
@@ -170,7 +232,7 @@
         if (connected) {
             throw new IllegalStateException ("Can't set streaming mode: already connected");
         }
-        if (fixedContentLength != -1) {
+        if (fixedContentLength != -1 || fixedContentLengthLong != -1) {
             throw new IllegalStateException ("Fixed length streaming mode set");
         }
         chunkLength = chunklen <=0? DEFAULT_CHUNK_SIZE : chunklen;
--- a/src/share/classes/java/security/cert/CertPathValidatorException.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/java/security/cert/CertPathValidatorException.java	Thu Nov 13 11:15:01 2008 -0800
@@ -113,7 +113,7 @@
      * permitted, and indicates that the cause is nonexistent or unknown.)
      */
     public CertPathValidatorException(Throwable cause) {
-        this(null, cause);
+        this((cause == null ? null : cause.toString()), cause);
     }
 
     /**
--- a/src/share/classes/javax/management/AttributeList.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/AttributeList.java	Thu Nov 13 11:15:01 2008 -0800
@@ -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);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/ClientContext.java	Thu Nov 13 11:15:01 2008 -0800
@@ -0,0 +1,1091 @@
+/*
+ * 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;
+
+import com.sun.jmx.interceptor.SingleMBeanForwarder;
+import com.sun.jmx.namespace.RoutingConnectionProxy;
+import com.sun.jmx.namespace.RoutingProxy;
+import com.sun.jmx.namespace.RoutingServerProxy;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR;
+import javax.management.namespace.JMXNamespaces;
+import javax.management.namespace.JMXNamespace;
+import javax.management.namespace.JMXNamespaceMBean;
+import javax.management.namespace.MBeanServerSupport;
+import javax.management.remote.IdentityMBeanServerForwarder;
+import javax.management.remote.MBeanServerForwarder;
+
+/**
+ * <p>Methods to communicate a client context to MBeans.  A context is
+ * a {@literal Map<String, String>} that is provided by the client and
+ * that an MBean can consult using the {@link #getContext()} method.
+ * The context is set on a per-thread basis and can be consulted by any
+ * code that the target MBean calls within the thread.</p>
+ *
+ * <p>One common usage of client context is to communicate the client's
+ * {@link Locale} to MBeans.  For example, if an MBean has a String attribute
+ * {@code LastProblemDescription}, the value of that attribute could be
+ * a description of the last problem encountered by the MBean, translated
+ * into the client's locale.  Different clients accessing this attribute
+ * from different locales would each see the appropriate version for their
+ * locale.</p>
+ *
+ * <p>The locale case is sufficiently important that it has a special
+ * shorthand, the {@link #getLocale()} method.  This method calls
+ * <code>{@link #getContext()}.get({@link #LOCALE_KEY})</code> and converts the
+ * resultant String into a Locale object.</p>
+ *
+ * <p>Here is what an MBean with a localized {@code LastProblemDescription}
+ * attribute might look like:</p>
+ *
+ * <pre>
+ * public class LocaleSensitive implements LocaleSensitiveMBean {
+ *     ...
+ *     public String getLastProblemDescription() {
+ *         Locale loc = {@link #getLocale() ClientContext.getLocale()};
+ *         ResourceBundle rb = ResourceBundle.getBundle("MyResources", loc);
+ *         String resourceKey = getLastProblemResourceKey();
+ *         return rb.getString(resourceKey);
+ *     }
+ *     ...
+ * }
+ * </pre>
+ *
+ * <p>Here is how a client can communicate its locale to the target
+ * MBean:</p>
+ *
+ * <pre>
+ * JMXConnector connector = JMXConnectorFactory.connect(url);
+ * MBeanServerConnection connection = connector.getMBeanServerConnection();
+ * <b>MBeanServerConnection localizedConnection =
+ *     {@link #withLocale(MBeanServerConnection, Locale)
+ *      ClientContext.withLocale}(connection, Locale.getDefault());</b>
+ * String problem = localizedConnection.getAttribute(
+ *          objectName, "LastProblemDescription");
+ * </pre>
+ *
+ * <p>In the more general case where the client wants to communicate context
+ * other than the locale, it can use {@link #withContext(MBeanServerConnection,
+ * String, String) withContext} instead of {@code withLocale}, and the target
+ * MBean can retrieve the context using {@link #getContext()}.</p>
+ *
+ *
+ * <h3 id="remote-use">Remote use of contexts</h3>
+ *
+ * <p>The various {@code with*} methods, for example {@link
+ * #withLocale(javax.management.MBeanServer, java.util.Locale) withLocale},
+ * transmit the context of each request by encoding it in the ObjectName of
+ * the request.  For example, if a client creates a connection in the
+ * French locale like this...</p>
+ *
+ * <pre>
+ * MBeanServerConnection mbsc = ...;
+ * Locale french = new Locale("fr");
+ * MBeanServerConnection localizedConnection = ClientContext.withLocale(mbsc, french);
+ * </pre>
+ *
+ * <p>...or, equivalently, like this...</p>
+ *
+ * <pre>
+ * MBeanServerConnection localizedConnection =
+ *     ClientContext.withContext(mbsc, {@link #LOCALE_KEY "jmx.locale"}, "fr");
+ * </pre>
+ *
+ * <p>...then the context associates {@code "jmx.locale"} with {@code "fr"}
+ * and a request such as<br>
+ * {@code localizedConnection.getAttribute("java.lang:type=Runtime", "Name")}<br>
+ * is translated into<br>
+ * {@code mbsc.getAttribute("jmx.context//jmx.locale=fr//java.lang:Runtime", "Name")}.<br>
+ * A special {@linkplain javax.management.namespace namespace} {@code jmx.context//}
+ * extracts the context from the string {@code jmx.locale=fr} and establishes
+ * it in the thread that will do<br>
+ * {@code getAttribute("java.lang:Runtime", "Name")}.</p>
+ *
+ * <p>The details of how contexts are encoded into ObjectNames are explained
+ * in the {@link #encode encode} method.</p>
+ *
+ * <p>The namespace {@code jmx.context//} just mentioned is only needed by
+ * remote clients, since local clients can set the context directly using
+ * {@link #doWithContext doWithContext}.  Accordingly, this namespace is not
+ * present by default in the {@code MBeanServer}.  Instead, it is
+ * <em>simulated</em> by the standard RMI connector using a special
+ * {@link MBeanServerForwarder}.  If you are using this connector, you do not
+ * need to do anything special.  Other connectors may or may not simulate this
+ * namespace in the same way.  If the connector server returns true from the
+ * method {@link
+ * javax.management.remote.JMXConnectorServer#supportsSystemMBeanServerForwarder()
+ * supportsSystemMBeanServerForwarder} then it does simulate the namespace.
+ * If you are using another connector, or if you want to be able to use the
+ * {@code with*} methods locally, then you can install the {@code
+ * MBeanServerForwarder} yourself as described in the method {@link
+ * #newContextForwarder newContextForwarder}.</p>
+ */
+public class ClientContext {
+    /**
+     * <p>The context key for the client locale.  The string associated with
+     * this key is an encoded locale such as {@code en_US} which could be
+     * returned by {@link Locale#toString()}.</p>
+     */
+    public static final String LOCALE_KEY = "jmx.locale";
+
+    private static final Logger LOG =
+            Logger.getLogger("javax.management.context");
+
+    /**
+     * <p>The namespace that implements contexts, {@value}.</p>
+     */
+    public static final String
+            NAMESPACE = "jmx.context";
+    private static final String NAMESPACE_PLUS_SEP =
+            NAMESPACE + NAMESPACE_SEPARATOR;
+    static final ObjectName CLIENT_CONTEXT_NAMESPACE_HANDLER =
+            ObjectName.valueOf(NAMESPACE_PLUS_SEP + ":" +
+                    JMXNamespace.TYPE_ASSIGNMENT);
+    private static final ObjectName NAMESPACE_HANDLER_WITHOUT_NAMESPACE =
+            ObjectName.valueOf(":" + JMXNamespace.TYPE_ASSIGNMENT);
+
+    private static final ThreadLocal<Map<String, String>> contextThreadLocal =
+            new InheritableThreadLocal<Map<String, String>>() {
+        @Override
+        protected Map<String, String> initialValue() {
+            return Collections.emptyMap();
+        }
+    };
+
+    /** There are no instances of this class. */
+    private ClientContext() {
+    }
+
+    /**
+     * <p>Get the client context associated with the current thread.
+     *
+     * @return the client context associated with the current thread.
+     * This may be an empty Map, but it cannot be null.  The returned
+     * Map cannot be modified.
+     */
+    public static Map<String, String> getContext() {
+        return Collections.unmodifiableMap(contextThreadLocal.get());
+    }
+
+    /**
+     * <p>Get the client locale associated with the current thread.
+     * If the client context includes the {@value #LOCALE_KEY} key
+     * then the returned value is the Locale encoded in that key.
+     * Otherwise the returned value is the {@linkplain Locale#getDefault()
+     * default locale}.
+     *
+     * @return the client locale.
+     */
+    public static Locale getLocale() {
+        String localeS = getContext().get(LOCALE_KEY);
+        if (localeS == null)
+            return Locale.getDefault();
+        // Parse the locale string.  Why isn't there a method in Locale for this?
+        String language, country, variant;
+        int ui = localeS.indexOf('_');
+        if (ui < 0) {
+            language = localeS;
+            country = variant = "";
+        } else {
+            language = localeS.substring(0, ui);
+            localeS = localeS.substring(ui + 1);
+            ui = localeS.indexOf('_');
+            if (ui < 0) {
+                country = localeS;
+                variant = "";
+            } else {
+                country = localeS.substring(0, ui);
+                variant = localeS.substring(ui + 1);
+            }
+        }
+        return new Locale(language, country, variant);
+    }
+
+    /**
+     * <p>Execute the given {@code task} with the client context set to
+     * the given Map.  This Map will be the result of {@link #getContext()}
+     * within the {@code task}.</p>
+     *
+     * <p>The {@code task} may include nested calls to {@code doWithContext}.
+     * The value returned by {@link #getContext} at any point is the Map
+     * provided to the most recent {@code doWithContext} (in the current thread)
+     * that has not yet returned.</p>
+     *
+     * <p>The {@link #getContext()} method returns the same value immediately
+     * after a call to this method as immediately before.  In other words,
+     * {@code doWithContext} only affects the context during the execution of
+     * the {@code task}.</p>
+     *
+     * <p>As an example, suppose you want to get an attribute with whatever
+     * context has already been set, plus the locale set to "fr".  You could
+     * write this:</p>
+     *
+     * <pre>
+     * {@code Map<String, String>} context =
+     *     new {@code HashMap<String, String>}(ClientContext.getContext());
+     * context.put(ClientContext.LOCALE_KEY, "fr");
+     * String lastProblemDescription =
+     *     ClientContext.doWithContext(context, new {@code Callable<String>}() {
+     *         public String call() {
+     *             return (String) mbeanServer.getAttribute(mbean, "LastProblemDescription");
+     *         }
+     *     });
+     * </pre>
+     *
+     * @param <T> the type of value that the task will return.  This type
+     * parameter is usually inferred from the type of the {@code task}
+     * parameter.  For example, if {@code task} is a {@code Callable<String>}
+     * then {@code T} is {@code String}.  If the task does not return a value,
+     * use a {@code Callable<Void>} and return null from its
+     * {@link Callable#call call} method.
+     * @param context the context to use while executing {@code task}.
+     * @param task the task to run with the {@code key}={@code value}
+     * binding.
+     * @return the result of {@link Callable#call() task.call()}.
+     * @throws IllegalArgumentException if either parameter is null, or
+     * if any key in {@code context} is null or empty, or if any value
+     * in {@code context} is null.
+     * @throws Exception If {@link Callable#call() task.call()} throws an
+     * exception, {@code doWithContext} throws the same exception.
+     */
+    public static <T> T doWithContext(Map<String, String> context, Callable<T> task)
+    throws Exception {
+        if (context == null || task == null)
+            throw new IllegalArgumentException("Null parameter");
+        Map<String, String> contextCopy = new TreeMap<String, String>(context);
+        validateContext(contextCopy);
+        Map<String, String> oldContextMap = contextThreadLocal.get();
+        try {
+            contextThreadLocal.set(contextCopy);
+            return task.call();
+        } finally {
+            contextThreadLocal.set(oldContextMap);
+        }
+    }
+
+    private static void validateContext(Map<String, String> context) {
+        for (Map.Entry<String, String> entry : context.entrySet()) {
+            // If the user passes a raw Map rather than a Map<String, String>,
+            // entries could contain objects other than Strings.  If so,
+            // we'll get a ClassCastException here.
+            String key = entry.getKey();
+            String value = entry.getValue();
+            if (key == null || value == null)
+                throw new IllegalArgumentException("Null key or value in context");
+            if (key.equals(""))
+                throw new IllegalArgumentException("Empty key in context");
+        }
+    }
+
+    /**
+     * <p>Return an MBeanServer object that is equivalent to the given
+     * MBeanServer object except that operations on MBeans run with
+     * the given Locale in their {@linkplain #getContext() thread context}.
+     * Note that this will only work if the given MBeanServer supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * <p>This method is equivalent to {@link #withContext(MBeanServer,
+     * String, String) withContext}<code>(mbs, {@value LOCALE_KEY},
+     * locale.toString())</code>.</p>
+     *
+     * @throws IllegalArgumentException if either parameter is null, or if
+     * {@code mbs} does not support contexts.  In the second case only,
+     * the cause of the {@code IllegalArgumentException} will be an {@link
+     * InstanceNotFoundException}.
+     */
+    public static MBeanServer withLocale(MBeanServer mbs, Locale locale) {
+        return withLocale(mbs, MBeanServer.class, locale);
+    }
+
+    /**
+     * <p>Return an MBeanServerConnection object that is equivalent to the given
+     * MBeanServerConnection object except that operations on MBeans run with
+     * the given Locale in their {@linkplain #getContext() thread context}.
+     * Note that this will only work if the given MBeanServerConnection supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * <p>This method is equivalent to {@link #withContext(MBeanServerConnection,
+     * String, String) withContext}<code>(mbs, {@value LOCALE_KEY},
+     * locale.toString())</code>.</p>
+     *
+     * @throws IllegalArgumentException if either parameter is null, or if
+     * the communication with {@code mbsc} fails, or if {@code mbsc} does not
+     * support contexts.  If the communication with {@code mbsc} fails, the
+     * {@linkplain Throwable#getCause() cause} of this exception will be an
+     * {@code IOException}.  If {@code mbsc} does not support contexts, the
+     * cause will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withLocale(
+            MBeanServerConnection mbsc, Locale locale) {
+        return withLocale(mbsc, MBeanServerConnection.class, locale);
+    }
+
+    private static <T extends MBeanServerConnection> T withLocale(
+            T mbsc, Class<T> mbscClass, Locale locale) {
+        if (locale == null)
+            throw new IllegalArgumentException("Null locale");
+        return withContext(mbsc, mbscClass, LOCALE_KEY, locale.toString());
+    }
+
+    /**
+     * <p>Return an MBeanServer object that is equivalent to the given
+     * MBeanServer object except that operations on MBeans run with
+     * the given key bound to the given value in their {@linkplain
+     * #getContext() thread context}.
+     * Note that this will only work if the given MBeanServer supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * @param mbs the original MBeanServer.
+     * @param key the key to bind in the context of MBean operations
+     * in the returned MBeanServer object.
+     * @param value the value to bind to the key in the context of MBean
+     * operations in the returned MBeanServer object.
+     * @throws IllegalArgumentException if any parameter is null, or
+     * if {@code key} is the empty string, or if {@code mbs} does not support
+     * contexts.  In the last case only, the cause of the {@code
+     * IllegalArgumentException} will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServer withContext(
+            MBeanServer mbs, String key, String value) {
+        return withContext(mbs, MBeanServer.class, key, value);
+    }
+
+    /**
+     * <p>Return an MBeanServerConnection object that is equivalent to the given
+     * MBeanServerConnection object except that operations on MBeans run with
+     * the given key bound to the given value in their {@linkplain
+     * #getContext() thread context}.
+     * Note that this will only work if the given MBeanServerConnection supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * @param mbsc the original MBeanServerConnection.
+     * @param key the key to bind in the context of MBean operations
+     * in the returned MBeanServerConnection object.
+     * @param value the value to bind to the key in the context of MBean
+     * operations in the returned MBeanServerConnection object.
+     * @throws IllegalArgumentException if any parameter is null, or
+     * if {@code key} is the empty string, or if the communication with {@code
+     * mbsc} fails, or if {@code mbsc} does not support contexts.  If
+     * the communication with {@code mbsc} fails, the {@linkplain
+     * Throwable#getCause() cause} of this exception will be an {@code
+     * IOException}.  If {@code mbsc} does not support contexts, the cause will
+     * be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withContext(
+            MBeanServerConnection mbsc, String key, String value) {
+        return withContext(mbsc, MBeanServerConnection.class, key, value);
+    }
+
+
+    /**
+     * <p>Returns an MBeanServerConnection object that is equivalent to the
+     * given MBeanServerConnection object except that remote operations on
+     * MBeans run with the context that has been established by the client
+     * using {@link #doWithContext doWithContext}.  Note that this will
+     * only work if the remote system supports contexts, as described <a
+     * href="#remote-use">above</a>.</p>
+     *
+     * <p>For example, suppose the remote system does support contexts, and you
+     * have created a {@code JMXConnector} like this:</p>
+     *
+     * <pre>
+     * JMXServiceURL url = ...;
+     * JMXConnector client = JMXConnectorFactory.connect(url);
+     * MBeanServerConnection mbsc = client.getMBeanServerConnection();
+     * <b>mbsc = ClientContext.withDynamicContext(mbsc);</b>
+     * </pre>
+     *
+     * <p>Then if you do this...</p>
+     *
+     * <pre>
+     * MBeanInfo mbi = ClientContext.doWithContext(
+     *     Collections.singletonMap(ClientContext.LOCALE_KEY, "fr"),
+     *     new {@code Callable<MBeanInfo>}() {
+     *         public MBeanInfo call() {
+     *             return mbsc.getMBeanInfo(objectName);
+     *         }
+     *     });
+     * </pre>
+     *
+     * <p>...then the context with the locale set to "fr" will be in place
+     * when the {@code getMBeanInfo} is executed on the remote MBean Server.</p>
+     *
+     * @param mbsc the original MBeanServerConnection.
+     *
+     * @throws IllegalArgumentException if the {@code mbsc} parameter is null,
+     * or if the communication with {@code mbsc} fails, or if {@code mbsc}
+     * does not support contexts.  If the communication with {@code mbsc}
+     * fails, the {@linkplain Throwable#getCause() cause} of this exception
+     * will be an {@code IOException}.  If {@code mbsc} does not support
+     * contexts, the cause will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withDynamicContext(
+            MBeanServerConnection mbsc) {
+        // Probe mbsc to get the right exception if it's incommunicado or
+        // doesn't support namespaces.
+        JMXNamespaces.narrowToNamespace(mbsc, NAMESPACE);
+        return (MBeanServerConnection) Proxy.newProxyInstance(
+                MBeanServerConnection.class.getClassLoader(),
+                new Class<?>[] {MBeanServerConnection.class},
+                new DynamicContextIH(mbsc));
+    }
+
+    private static class DynamicContextIH implements InvocationHandler {
+        private final MBeanServerConnection mbsc;
+
+        public DynamicContextIH(MBeanServerConnection mbsc) {
+            this.mbsc = mbsc;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            MBeanServerConnection dynMBSC = withContext(
+                    mbsc, MBeanServerConnection.class, getContext(), false);
+            try {
+                return method.invoke(dynMBSC, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    private static <T extends MBeanServerConnection> T withContext(
+            T mbsc, Class<T> mbscClass, String key, String value) {
+        return withContext(
+                mbsc, mbscClass, Collections.singletonMap(key, value), true);
+    }
+
+    private static <T extends MBeanServerConnection> T withContext(
+            T mbsc, Class<T> mbscClass, Map<String, String> context,
+            boolean probe) {
+        if (mbsc == null || context == null)
+            throw new IllegalArgumentException("Null parameter");
+        if (context.isEmpty())
+            return mbsc;
+        validateContext(context);
+        Map<String, String> contextMap = null;
+        if (mbsc.getClass() == RoutingServerProxy.class ||
+                mbsc.getClass() == RoutingProxy.class) {
+            RoutingProxy<?> nsp = (RoutingProxy<?>) mbsc;
+            String where = nsp.getSourceNamespace();
+            if (where.startsWith(NAMESPACE_PLUS_SEP)) {
+                /* Try to merge the existing context namespace with the
+                 * new one.  If it doesn't work, we fall back to just
+                 * prefixing jmx.context//key=value, which
+                 * might lead to a name like jmx.c//k1=v1//jmx.c//k2=v2//d:k=v.
+                 */
+                String encodedContext =
+                        where.substring(NAMESPACE_PLUS_SEP.length());
+                if (encodedContext.indexOf(NAMESPACE_SEPARATOR) < 0) {
+                    contextMap = stringToMapOrNull(encodedContext);
+                    if (contextMap != null) {
+                        contextMap.putAll(context);
+                        mbsc = mbscClass.cast(nsp.source());
+                    }
+                }
+            }
+        }
+        if (contextMap == null)
+            contextMap = context;
+        String contextDir = NAMESPACE_PLUS_SEP + mapToString(contextMap);
+        if (mbscClass == MBeanServer.class) {
+            return mbscClass.cast(RoutingServerProxy.cd(
+                    (MBeanServer) mbsc, contextDir, probe));
+        } else if (mbscClass == MBeanServerConnection.class) {
+            return mbscClass.cast(RoutingConnectionProxy.cd(
+                    mbsc, contextDir, probe));
+        } else
+            throw new AssertionError("Bad MBSC: " + mbscClass);
+    }
+
+    /**
+     * <p>Returns an encoded context prefix for ObjectNames.
+     * If the given context is empty, {@code ""} is returned.
+     * Otherwise, this method returns a string of the form
+     * {@code "jmx.context//key=value;key=value;..."}.
+     * For example, if the context has keys {@code "jmx.locale"}
+     * and {@code "xid"} with respective values {@code "fr"}
+     * and {@code "1234"}, this method will return
+     * {@code "jmx.context//jmx.locale=fr;xid=1234"} or
+     * {@code "jmx.context//xid=1234;jmx.locale=fr"}.</p>
+     *
+     * <p>Each key and each value in the encoded string is subject to
+     * encoding as if by the method {@link URLEncoder#encode(String, String)}
+     * with a character encoding of {@code "UTF-8"}, but with the additional
+     * encoding of any {@code *} character as {@code "%2A"}.  This ensures
+     * that keys and values can contain any character.  Without encoding,
+     * characters such as {@code =} and {@code :} would pose problems.</p>
+     *
+     * @param context the context to encode.
+     *
+     * @return the context in encoded form.
+     *
+     * @throws IllegalArgumentException if the {@code context} parameter
+     * is null or if it contains a null key or value.
+     **/
+    public static String encode(Map<String, String> context) {
+        if (context == null)
+            throw new IllegalArgumentException("Null context");
+        if (context.isEmpty())
+            return "";
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : context.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            if (key == null || value == null)
+                throw new IllegalArgumentException("Null key or value");
+            if (sb.length() > 0)
+                sb.append(";");
+            sb.append(encode(key)).append("=").append(encode(value));
+        }
+        sb.insert(0, NAMESPACE_PLUS_SEP);
+        return sb.toString();
+    }
+
+    /**
+     * <p>Create a new {@link MBeanServerForwarder} that applies the context
+     * received from a client to the current thread.  A client using
+     * one of the various {@code with*} methods (for example {@link
+     * #withContext(MBeanServerConnection, String, String) withContext}) will
+     * encode that context into the {@code ObjectName} of each
+     * {@code MBeanServer} request.  The object returned by this method
+     * decodes the context from that {@code ObjectName} and applies it
+     * as described for {@link #doWithContext doWithContext} while performing
+     * the {@code MBeanServer} request using the {@code ObjectName} without
+     * the encoded context.</p>
+     *
+     * <p>This forwarder can be used in a number of ways:</p>
+     *
+     * <ul>
+     * <li>
+     * <p>To add context decoding to a local {@code MBeanServer}, you can
+     * write:</p>
+     * <pre>
+     * MBeanServer mbs = {@link
+     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
+     * ManagementFactory.getPlatformMBeanServer()};  // for example
+     * mbs = ClientContext.newContextForwarder(mbs, null);
+     * </pre>
+     *
+     * <li>
+     * <p>To add context decoding to a {@linkplain
+     * javax.management.remote.JMXConnectorServer connector server}:</p>
+     * <pre>
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(...);
+     * MBeanServer nextMBS = cs.getMBeanServer();
+     * MBeanServerForwarder mbsf = ClientContext.newContextForwarder(nextMBS, null);
+     * cs.{@link
+     * javax.management.remote.JMXConnectorServer#setMBeanServerForwarder
+     * setMBeanServerForwarder}(mbsf);
+     * </pre>
+     *
+     * <li>
+     * <p>For connectors, such as the standard RMI connector, that support
+     * a {@linkplain
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder
+     * system chain} of {@code MBeanServerForwarder}s, this forwarder will
+     * be installed in that chain by default.  See
+     * {@link javax.management.remote.JMXConnectorServer#CONTEXT_FORWARDER
+     * JMXConnectorServer.CONTEXT_FORWARDER}.
+     * </p>
+     *
+     * </ul>
+     *
+     * @param nextMBS the next {@code MBeanServer} in the chain of
+     * forwarders, which might be another {@code MBeanServerForwarder} or
+     * a plain {@code MBeanServer}.  This is the object to which {@code
+     * MBeanServer} requests that do not include a context are sent.  It
+     * will be the value of {@link MBeanServerForwarder#getMBeanServer()
+     * getMBeanServer()} on the returned object, and can be changed with {@link
+     * MBeanServerForwarder#setMBeanServer setMBeanServer}.  It can be null but
+     * must be set to a non-null value before any {@code MBeanServer} requests
+     * arrive.
+     *
+     * @param loopMBS the {@code MBeanServer} to which requests that contain
+     * an encoded context should be sent once the context has been decoded.
+     * For example, if the request is {@link MBeanServer#getAttribute
+     * getAttribute}{@code ("jmx.context//jmx.locale=fr//java.lang:type=Runtime",
+     * "Name")}, then the {@linkplain #getContext() context} of the thread
+     * executing that request will have {@code "jmx.locale"} set to {@code "fr"}
+     * while executing {@code loopMBS.getAttribute("java.lang:type=Runtime",
+     * "Name")}.  If this parameter is null, then these requests will be
+     * sent to the newly-created {@code MBeanServerForwarder}.  Usually
+     * the parameter will either be null or will be the result of {@link
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder
+     * getSystemMBeanServerForwarder()} for the connector server in which
+     * this forwarder will be installed.
+     *
+     * @return a new {@code MBeanServerForwarder} that decodes client context
+     * from {@code ObjectName}s.
+     */
+    /*
+     * What we're building here is confusing enough to need a diagram.
+     * The MBSF that we return is actually the composition of two forwarders:
+     * the first one simulates the existence of the MBean
+     * jmx.context//:type=JMXNamespace, and the second one simulates the
+     * existence of the namespace jmx.context//.  Furthermore, that namespace
+     * loops back to the composed forwarder, so that something like
+     * jmx.context//foo=bar//jmxcontext//baz=buh will work.  And the loopback
+     * goes through yet another forwarder, which simulates the existence of
+     * (e.g.) jmx.context//foo=bar//:type=JMXNamespace, which is needed
+     * notably so that narrowToNamespace will work.
+     *
+     *          |     +--------------------------------------------------+
+     *          v     v                                                  |
+     * +----------------+                                                |
+     * | Handler MBSF   |->accesses to jmx.context//:type=JMXNamespace   |
+     * +----------------+    (handled completely here)   +-------------------+
+     *          |                                        | 2nd Handler MBSF  |
+     *          v                                        +-------------------+
+     * +----------------+                                                ^
+     * | Namespace MBSF |->accesses to jmx.context//**-------------------+
+     * +----------------+    (after attaching context to thread)
+     *          |
+     *          v          accesses to anything else
+     *
+     * And finally, we need to ensure that from the outside the composed object
+     * looks like a single forwarder, so that its get/setMBeanServer methods
+     * will do the expected thing.  That's what the anonymous subclass is for.
+     */
+    public static MBeanServerForwarder newContextForwarder(
+            MBeanServer nextMBS, MBeanServer loopMBS) {
+        final MBeanServerForwarder mbsWrapper =
+                new IdentityMBeanServerForwarder(nextMBS);
+        DynamicMBean handlerMBean = new StandardMBean(
+                new JMXNamespace(mbsWrapper), JMXNamespaceMBean.class, false);
+        SingleMBeanForwarder handlerForwarder = new SingleMBeanForwarder(
+                CLIENT_CONTEXT_NAMESPACE_HANDLER, handlerMBean, true) {
+            @Override
+            public MBeanServer getMBeanServer() {
+                return ((MBeanServerForwarder) super.getMBeanServer()).getMBeanServer();
+            }
+
+            @Override
+            public void setMBeanServer(MBeanServer mbs1) {
+                MBeanServerForwarder mbsf1 = (MBeanServerForwarder)
+                        super.getMBeanServer();
+                if (mbsf1 != null)
+                    mbsf1.setMBeanServer(mbs1);
+                else
+                    super.setMBeanServer(mbs1);
+                mbsWrapper.setMBeanServer(mbs1);
+            }
+        };
+        if (loopMBS == null)
+            loopMBS = handlerForwarder;
+        ContextInvocationHandler contextIH =
+                new ContextInvocationHandler(nextMBS, loopMBS);
+        MBeanServerForwarder contextForwarder = newForwarderProxy(contextIH);
+        handlerForwarder.setMBeanServer(contextForwarder);
+        return handlerForwarder;
+    }
+
+    /**
+     * <p>Create a new {@link MBeanServerForwarder} that localizes
+     * descriptions in {@code MBeanInfo} instances returned by
+     * {@link MBeanServer#getMBeanInfo getMBeanInfo}.  The {@code
+     * MBeanServerForwarder} returned by this method passes all {@code
+     * MBeanServer} methods through unchanged to the supplied object, {@code
+     * mbs}, with the exception of {@code getMBeanInfo}.  To handle {@code
+     * getMBeanInfo(objectName)}, it calls {@code mbs.getMBeanInfo(objectName)}
+     * to get an {@code MBeanInfo}, {@code mbi}; it calls {@link
+     * MBeanServer#getClassLoaderFor mbs.getClassLoaderFor(objectName)} to
+     * get a {@code ClassLoader}, {@code cl}; and it calls {@link
+     * #getLocale} to get a {@code Locale}, {@code locale}.  The order
+     * of these three calls is not specified.  Then the result is {@code
+     * mbi.localizeDescriptions(locale, loader)}.</p>
+     *
+     * <p>This forwarder can be used in a number of ways:</p>
+     *
+     * <ul>
+     * <li>
+     * <p>To add description localization to a local {@code MBeanServer}, you
+     * can write:</p>
+     *
+     * <pre>
+     * MBeanServer mbs = {@link
+     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
+     * ManagementFactory.getPlatformMBeanServer()};  // for example
+     * mbs = ClientContext.newLocalizeMBeanInfoForwarder(mbs);
+     * </pre>
+     *
+     * <li>
+     * <p>To add description localization to a {@linkplain
+     * javax.management.remote.JMXConnectorServer connector server}, you will
+     * need to add both a {@linkplain #newContextForwarder context forwarder}
+     * and a localization forwarder, for example like this:</p>
+     *
+     * <pre>
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(...);
+     * MBeanServer nextMBS = cs.getMBeanServer();
+     * MBeanServerForwarder localizeMBSF =
+     *     ClientContext.newLocalizeMBeanInfoForwarder(nextMBS);
+     * MBeanServerForwarder contextMBSF =
+     *     ClientContext.newContextForwarder(localizeMBSF, null);
+     * cs.{@link
+     * javax.management.remote.JMXConnectorServer#setMBeanServerForwarder
+     * setMBeanServerForwarder}(contextMBSF);
+     * </pre>
+     *
+     * <p>Notice that the context forwarder must run before the localization
+     * forwarder, so that the locale is correctly established when the latter
+     * runs.  So the {@code nextMBS} parameter of the context forwarder must
+     * be the localization forwarder, and not vice versa.</p>
+     *
+     * <li>
+     * <p>For connectors, such as the standard RMI connector, that support
+     * a {@linkplain
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder
+     * system chain} of {@code MBeanServerForwarder}s, the context forwarder and
+     * the localization forwarder will be installed in that chain, in the right
+     * order, if you include
+     * {@link
+     * javax.management.remote.JMXConnectorServer#LOCALIZE_MBEAN_INFO_FORWARDER
+     * LOCALIZE_MBEAN_INFO_FORWARDER} in the environment {@code Map} with
+     * the value {@code "true"}, for example like this:</p>
+     * </p>
+     * <pre>
+     * MBeanServer mbs = ...;
+     * JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://...");
+     * {@code Map<String, Object>} env = new {@code HashMap<String, Object>}();
+     * env.put(JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER, "true");
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
+     *     url, env, mbs);
+     * </pre>
+     *
+     * </ul>
+     *
+     * @param mbs the next {@code MBeanServer} in the chain of
+     * forwarders, which might be another {@code MBeanServerForwarder}
+     * or a plain {@code MBeanServer}.  It will be the value of
+     * {@link MBeanServerForwarder#getMBeanServer() getMBeanServer()}
+     * on the returned object, and can be changed with {@link
+     * MBeanServerForwarder#setMBeanServer setMBeanServer}.  It can be null but
+     * must be set to a non-null value before any {@code MBeanServer} requests
+     * arrive.
+     *
+     * @return a new {@code MBeanServerForwarder} that localizes descriptions
+     * in the result of {@code getMBeanInfo}.
+     */
+    public static MBeanServerForwarder newLocalizeMBeanInfoForwarder(
+            MBeanServer mbs) {
+        return new IdentityMBeanServerForwarder(mbs) {
+            @Override
+            public MBeanInfo getMBeanInfo(ObjectName name)
+                    throws InstanceNotFoundException, IntrospectionException,
+                           ReflectionException {
+                MBeanInfo mbi = super.getMBeanInfo(name);
+                Locale locale = getLocale();
+                ClassLoader loader = getClassLoaderFor(name);
+                return mbi.localizeDescriptions(locale, loader);
+            }
+        };
+    }
+
+    private static MBeanServerForwarder newForwarderProxy(InvocationHandler ih) {
+        return (MBeanServerForwarder) Proxy.newProxyInstance(
+                MBeanServerForwarder.class.getClassLoader(),
+                new Class<?>[] {MBeanServerForwarder.class},
+                ih);
+    }
+
+    // A proxy connection that will strip the 'contextDir' at input (routing),
+    // and put it back at output (createMBean / registerMBean / query* /
+    // getObjectInstance). Usually RoutingProxy / RoutingServerProxy are used
+    // the other way round (they are used for 'cd' - where they need to add
+    // something at input and remove it at output).
+    // For 'cd' operations we create RoutingProxys with a non empty sourceDir,
+    // and a possibly non-empty targetDir. This is the only case where we use
+    // RoutingProxies with an empty sourceDir (sourceDir is what we add at input
+    // and remove at output, targetDir is what we remove at input and add at
+    // output.
+    //
+    // Note that using a transient ContextRoutingConnection
+    // is possible only because RoutingProxys don't rewrite
+    // notifications sources - otherwise we would have to
+    // keep the ContextRoutingConnection - just to preserve
+    // the 'wrapping listeners'
+    //
+    private static final class ContextRoutingConnection
+            extends RoutingServerProxy {
+        public ContextRoutingConnection(MBeanServer source,
+                                 String contextDir) {
+            super(source, "", contextDir, false);
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public Integer getMBeanCount() {
+            return source().getMBeanCount();
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public String[] getDomains() {
+            return source().getDomains();
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public String getDefaultDomain() {
+            return source().getDefaultDomain();
+        }
+
+    }
+
+    private static class ContextInvocationHandler implements InvocationHandler {
+        /*
+         * MBeanServer requests that don't include jmx.context//foo=bar//
+         * are forwarded to forwardMBS, which is the unadorned MBeanServer
+         * that knows nothing about the context namespace.
+         * MBeanServer requests that do include this prefix will
+         * usually (depending on the value of the loopMBS parameter to
+         * newContextForwarder) loop back to the combined MBeanServerForwarder
+         * that first implements
+         * jmx.context//:type=JMXNamespace and then implements
+         * jmx.context//foo=bar//.  The reason is that it is valid
+         * to have jmx.context//foo=bar//jmx.context//baz=buh//, although
+         * usually that will be combined into jmx.context//foo=bar;baz=buh//.
+         *
+         * Before forwarding to loopMBS, we must check for :type=JMXNamespace
+         * so that jmx.context//foo=bar//:type=JMXNamespace will exist.  Its
+         * existence is partial because it must remain "invisible": it should
+         * not show up in queryNames or getMBeanCount even though it does
+         * accept getAttribute and isRegistered and all other methods that
+         * reference a single MBean.
+         */
+        private MBeanServer forwardMBS;
+        private final MBeanServer loopMBS;
+        private static final MBeanServer emptyMBS = new MBeanServerSupport() {
+            @Override
+            public DynamicMBean getDynamicMBeanFor(ObjectName name)
+                    throws InstanceNotFoundException {
+                throw new InstanceNotFoundException(name.toString());
+            }
+
+            @Override
+            protected Set<ObjectName> getNames() {
+                return Collections.emptySet();
+            }
+        };
+
+        ContextInvocationHandler(MBeanServer forwardMBS, MBeanServer loopMBS) {
+            this.forwardMBS = forwardMBS;
+            DynamicMBean handlerMBean = new StandardMBean(
+                    new JMXNamespace(loopMBS), JMXNamespaceMBean.class, false);
+            MBeanServerForwarder handlerMBS = new SingleMBeanForwarder(
+                    NAMESPACE_HANDLER_WITHOUT_NAMESPACE, handlerMBean, false);
+            handlerMBS.setMBeanServer(loopMBS);
+            this.loopMBS = handlerMBS;
+        }
+
+        public Object invoke(Object proxy, final Method method, final Object[] args)
+        throws Throwable {
+            String methodName = method.getName();
+            Class<?>[] paramTypes = method.getParameterTypes();
+
+            // If this is a method from MBeanServerForwarder, handle it here.
+            // There are only two such methods: getMBeanServer() and
+            // setMBeanServer(mbs).
+            if (methodName.equals("getMBeanServer"))
+                return forwardMBS;
+            else if (methodName.equals("setMBeanServer")) {
+                this.forwardMBS = (MBeanServer) args[0];
+                return null;
+            }
+
+            // It is a method from MBeanServer.
+            // Find the first parameter whose declared type is ObjectName,
+            // and see if it is in the context namespace.  If so we need to
+            // trigger the logic for that namespace.  If not, we simply
+            // forward to the next MBeanServer in the chain.  This logic
+            // depends on the fact that if a method in the MBeanServer interface
+            // has a "routing" ObjectName parameter, it is always the first
+            // parameter of that type.  Conversely, if a method has an
+            // ObjectName parameter, then it makes sense to "route" that
+            // method.  Except for deserialize and instantiate, but if we
+            // recognize a context namespace in those methods' ObjectName
+            // parameters it is pretty harmless.
+            int objectNameI = -1;
+            for (int i = 0; i < paramTypes.length; i++) {
+                if (paramTypes[i] == ObjectName.class) {
+                    objectNameI = i;
+                    break;
+                }
+            }
+
+            if (objectNameI < 0)
+                return invoke(method, forwardMBS, args);
+
+            ObjectName target = (ObjectName) args[objectNameI];
+            if (target == null ||
+                    !target.getDomain().startsWith(NAMESPACE_PLUS_SEP))
+                return invoke(method, forwardMBS, args);
+
+            String domain = target.getDomain().substring(NAMESPACE_PLUS_SEP.length());
+
+            // The method routes through the (simulated) context namespace.
+            // Decode the context after it, e.g. jmx.context//jmx.locale=fr//...
+            // If there is no context part, we can throw an exception,
+            // because a forwarder has already handled the unique MBean
+            // jmx.context//:type=JMXNamespace.
+            int sep = domain.indexOf(NAMESPACE_SEPARATOR);
+            if (sep < 0)
+                return invoke(method, emptyMBS, args);  // throw exception
+            final String encodedContext = domain.substring(0, sep);
+
+            if (method.getName().startsWith("query") &&
+                    (encodedContext.contains("*") || encodedContext.contains("?"))) {
+                // Queries like jmx.context//*//d:k=v return
+                // an empty set, consistent with "real" namespaces.
+                return Collections.EMPTY_SET;
+            }
+
+            Map<String, String> ctx = new TreeMap<String, String>(getContext());
+            ctx.putAll(stringToMap(encodedContext));
+
+            return doWithContext(ctx, new Callable<Object>() {
+                public Object call() throws Exception {
+                    // Create a proxy connection that will strip
+                    // "jmx.context//" + encodedContext + "//" on input,
+                    // and put it back on output.
+                    //
+                    // Note that using a transient ContextRoutingConnection
+                    // is possible only because it doesn't rewrite
+                    // notification sources - otherwise we would have to
+                    // keep the ContextRoutingConnection - just to preserve
+                    // the 'wrapping listeners'
+                    //
+                    String namespace = NAMESPACE_PLUS_SEP + encodedContext;
+                    final ContextRoutingConnection route =
+                              new ContextRoutingConnection(loopMBS, namespace);
+
+                    if (LOG.isLoggable(Level.FINE))
+                        LOG.fine("context="+encodedContext);
+                    if (LOG.isLoggable(Level.FINER))
+                        LOG.finer(method.getName()+""+
+                            ((args==null)?"()":(""+Arrays.asList(args))));
+
+                    return invoke(method, route, args);
+                }
+            });
+        }
+
+        private static Object invoke(Method method, Object target, Object[] args)
+                throws Exception {
+            try {
+                return method.invoke(target, args);
+            } catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof Error)
+                    throw (Error) cause;
+                throw (Exception) cause;
+            }
+        }
+    }
+
+    private static String mapToString(Map<String, String> map) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String key = encode(entry.getKey());
+            String value = encode(entry.getValue());
+            if (sb.length() > 0)
+                sb.append(";");
+            sb.append(key).append("=").append(value);
+        }
+        return sb.toString();
+    }
+
+    private static Map<String, String> stringToMap(String encodedContext) {
+        Map<String, String> map = stringToMapOrNull(encodedContext);
+        if (map == null) {
+            throw new IllegalArgumentException(
+                    "Invalid encoded context: " + encodedContext);
+        }
+        return map;
+    }
+
+    private static Map<String, String> stringToMapOrNull(String encodedContext) {
+        Map<String, String> map = new LinkedHashMap<String, String>();
+        StringTokenizer stok = new StringTokenizer(encodedContext, ";");
+        while (stok.hasMoreTokens()) {
+            String tok = stok.nextToken();
+            int eq = tok.indexOf('=');
+            if (eq < 0)
+                return null;
+            String key = decode(tok.substring(0, eq));
+            if (key.equals(""))
+                return null;
+            String value = decode(tok.substring(eq + 1));
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    private static String encode(String s) {
+        try {
+            s = URLEncoder.encode(s, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);  // Should not happen
+        }
+        return s.replace("*", "%2A");
+        // The * character is left intact in URL encodings, but for us it
+        // is special (an ObjectName wildcard) so we must map it.
+        // We are assuming that URLDecoder will decode it the same way as any
+        // other hex escape.
+    }
+
+    private static String decode(String s) {
+        try {
+            return URLDecoder.decode(s, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
--- a/src/share/classes/javax/management/Descriptor.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/Descriptor.java	Thu Nov 13 11:15:01 2008 -0800
@@ -35,8 +35,8 @@
 // Javadoc imports:
 import java.lang.management.MemoryUsage;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.ResourceBundle;
-
 import javax.management.openmbean.CompositeData;
 import javax.management.openmbean.MXBeanMappingFactory;
 import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
@@ -118,19 +118,22 @@
  * deprecation, for example {@code "1.3 Replaced by the Capacity
  * attribute"}.</td>
  *
- * <tr id="descriptionResourceBundleBaseName">
- * <td>descriptionResource<br>BundleBaseName</td><td>String</td><td>Any</td>
+ * <tr><td id="descriptionResourceBundleBaseName"><i>descriptionResource<br>
+ * BundleBaseName</i></td><td>String</td><td>Any</td>
  *
  * <td>The base name for the {@link ResourceBundle} in which the key given in
  * the {@code descriptionResourceKey} field can be found, for example
- * {@code "com.example.myapp.MBeanResources"}.</td>
+ * {@code "com.example.myapp.MBeanResources"}.  See
+ * {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}.</td>
  *
- * <tr id="descriptionResourceKey">
- * <td>descriptionResourceKey</td><td>String</td><td>Any</td>
+ * <tr><td id="descriptionResourceKey"><i>descriptionResourceKey</i></td>
+ * <td>String</td><td>Any</td>
  *
  * <td>A resource key for the description of this element.  In
  * conjunction with the {@code descriptionResourceBundleBaseName},
- * this can be used to find a localized version of the description.</td>
+ * this can be used to find a localized version of the description.
+ * See {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}.
+ * </td>
  *
  * <tr><td>enabled</td><td>String</td>
  * <td>MBeanAttributeInfo<br>MBeanNotificationInfo<br>MBeanOperationInfo</td>
@@ -157,11 +160,11 @@
  * href="MBeanInfo.html#info-changed">{@code "jmx.mbean.info.changed"}</a>
  * notification.</td>
  *
- * <tr><td>infoTimeout</td><td>String<br>Long</td><td>MBeanInfo</td>
+ * <tr id="infoTimeout"><td>infoTimeout</td><td>String<br>Long</td><td>MBeanInfo</td>
  *
- * <td id="infoTimeout">The time in milli-seconds that the MBeanInfo can
- * reasonably be expected to be unchanged.  The value can be a {@code Long}
- * or a decimal string.  This provides a hint from a DynamicMBean or any
+ * <td>The time in milli-seconds that the MBeanInfo can reasonably be
+ * expected to be unchanged.  The value can be a {@code Long} or a
+ * decimal string.  This provides a hint from a DynamicMBean or any
  * MBean that does not define {@code immutableInfo} as {@code true}
  * that the MBeanInfo is not likely to change within this period and
  * therefore can be cached.  When this field is missing or has the
@@ -185,6 +188,13 @@
  * <td>Legal values for an attribute or parameter.  See
  * {@link javax.management.openmbean}.</td>
  *
+ * <tr id="locale"><td><i>locale</i></td>
+ * <td>String</td><td>Any</td>
+ *
+ * <td>The {@linkplain Locale locale} of the description in this
+ * {@code MBeanInfo}, {@code MBeanAttributeInfo}, etc, as returned
+ * by {@link Locale#toString()}.</td>
+ *
  * <tr id="maxValue"><td><i>maxValue</i><td>Object</td>
  * <td>MBeanAttributeInfo<br>MBeanParameterInfo</td>
  *
--- a/src/share/classes/javax/management/JMX.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/JMX.java	Thu Nov 13 11:15:01 2008 -0800
@@ -30,6 +30,7 @@
 import com.sun.jmx.remote.util.ClassLogger;
 import java.beans.BeanInfo;
 import java.beans.PropertyDescriptor;
+import java.io.IOException;
 import java.io.Serializable;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
@@ -37,6 +38,7 @@
 import java.lang.reflect.Proxy;
 import java.util.Map;
 import java.util.TreeMap;
+import javax.management.namespace.JMXNamespaces;
 import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
@@ -60,6 +62,21 @@
      */
     public static final String DEFAULT_VALUE_FIELD = "defaultValue";
 
+   /**
+     * The name of the <a
+     * href="Descriptor.html#descriptionResourceBundleBaseName">{@code
+     * descriptionResourceBundleBaseName}</a> field.
+     */
+    public static final String DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD =
+            "descriptionResourceBundleBaseName";
+
+    /**
+     * The name of the <a href="Descriptor.html#descriptionResourceKey">{@code
+     * descriptionResourceKey}</a> field.
+     */
+    public static final String DESCRIPTION_RESOURCE_KEY_FIELD =
+            "descriptionResourceKey";
+
     /**
      * The name of the <a href="Descriptor.html#immutableInfo">{@code
      * immutableInfo}</a> field.
@@ -79,6 +96,12 @@
     public static final String LEGAL_VALUES_FIELD = "legalValues";
 
     /**
+     * The name of the <a href="Descriptor.html#locale">{@code locale}</a>
+     * field.
+     */
+    public static final String LOCALE_FIELD = "locale";
+
+    /**
      * The name of the <a href="Descriptor.html#maxValue">{@code
      * maxValue}</a> field.
      */
@@ -120,13 +143,12 @@
      * <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>
+     * <p>For example, to specify the "wrapped object visible" option for a
+     * {@code StandardMBean}, you might write this:</p>
      *
      * <pre>
-     * MXBeanMappingFactory factory = new MyMXBeanMappingFactory();
-     * JMX.MBeanOptions opts = new JMX.MBeanOptions();
-     * opts.setMXBeanMappingFactory(factory);
+     * StandardMBean.Options opts = new StandardMBean.Options();
+     * opts.setWrappedObjectVisible(true);
      * StandardMBean mbean = new StandardMBean(impl, intf, opts);
      * </pre>
      *
@@ -808,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/MBeanInfo.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/MBeanInfo.java	Thu Nov 13 11:15:01 2008 -0800
@@ -25,6 +25,7 @@
 
 package javax.management;
 
+import com.sun.jmx.mbeanserver.Util;
 import java.io.IOException;
 import java.io.StreamCorruptedException;
 import java.io.Serializable;
@@ -37,6 +38,12 @@
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import static javax.management.ImmutableDescriptor.nonNullDescriptor;
 
 /**
@@ -290,6 +297,7 @@
      * <p>Since this class is immutable, the clone method is chiefly of
      * interest to subclasses.</p>
      */
+     @Override
      public Object clone () {
          try {
              return super.clone() ;
@@ -474,6 +482,7 @@
         return (Descriptor) nonNullDescriptor(descriptor).clone();
     }
 
+    @Override
     public String toString() {
         return
             getClass().getName() + "[" +
@@ -505,6 +514,7 @@
      * @return true if and only if <code>o</code> is an MBeanInfo that is equal
      * to this one according to the rules above.
      */
+    @Override
     public boolean equals(Object o) {
         if (o == this)
             return true;
@@ -524,6 +534,7 @@
              Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
     }
 
+    @Override
     public int hashCode() {
         /* Since computing the hashCode is quite expensive, we cache it.
            If by some terrible misfortune the computed value is 0, the
@@ -747,4 +758,377 @@
             throw new StreamCorruptedException("Got unexpected byte.");
         }
     }
+
+    /**
+     * <p>Return an {@code MBeanInfo} object that is the same as this one
+     * except that its descriptions are localized in the given locale.
+     * This means the text returned by {@link MBeanInfo#getDescription}
+     * (the description of the MBean itself), and the text returned by the
+     * {@link MBeanFeatureInfo#getDescription getDescription()} method
+     * for every {@linkplain MBeanAttributeInfo attribute}, {@linkplain
+     * MBeanOperationInfo operation}, {@linkplain MBeanConstructorInfo
+     * constructor}, and {@linkplain MBeanNotificationInfo notification}
+     * contained in the {@code MBeanInfo}.</p>
+     *
+     * <p>Here is how the description {@code this.getDescription()} is
+     * localized.</p>
+     *
+     * <p>First, if the {@linkplain #getDescriptor() descriptor}
+     * of this {@code MBeanInfo} contains a field <code><a
+     * href="Descriptor.html#locale">"locale"</a></code>, and the value of
+     * the field is the same as {@code locale.toString()}, then this {@code
+     * MBeanInfo} is returned. Otherwise, localization proceeds as follows,
+     * and the {@code "locale"} field in the returned {@code MBeanInfo} will
+     * be {@code locale.toString()}.
+     *
+     * <p>A <em>{@code className}</em> is determined. If this
+     * {@code MBeanInfo} contains a descriptor with the field
+     * <a href="Descriptor.html#interfaceClassName">{@code
+     * "interfaceClassName"}</a>, then the value of that field is the
+     * {@code className}. Otherwise, it is {@link #getClassName()}.
+     * Everything before the last period (.) in the {@code className} is
+     * the <em>{@code package}</em>, and everything after is the <em>{@code
+     * simpleClassName}</em>. (If there is no period, then the {@code package}
+     * is empty and the {@code simpleClassName} is the same as the {@code
+     * className}.)</p>
+     *
+     * <p>A <em>{@code resourceKey}</em> is determined. If this {@code
+     * MBeanInfo} contains a {@linkplain MBeanInfo#getDescriptor() descriptor}
+     * with a field {@link JMX#DESCRIPTION_RESOURCE_KEY_FIELD
+     * "descriptionResourceKey"}, the value of the field is
+     * the {@code resourceKey}. Otherwise, the {@code resourceKey} is {@code
+     * simpleClassName + ".mbean"}.</p>
+     *
+     * <p>A <em>{@code resourceBundleBaseName}</em> is determined. If
+     * this {@code MBeanInfo} contains a descriptor with a field {@link
+     * JMX#DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD
+     * "descriptionResourceBundleBaseName"}, the value of the field
+     * is the {@code resourceBundleBaseName}. Otherwise, the {@code
+     * resourceBundleBaseName} is {@code package + ".MBeanDescriptions"}.
+     *
+     * <p>Then, a {@link java.util.ResourceBundle ResourceBundle} is
+     * determined, using<br> {@link java.util.ResourceBundle#getBundle(String,
+     * Locale, ClassLoader) ResourceBundle.getBundle(resourceBundleBaseName,
+     * locale, loader)}. If this succeeds, and if {@link
+     * java.util.ResourceBundle#getString(String) getString(resourceKey)}
+     * returns a string, then that string is the localized description.
+     * Otherwise, the original description is unchanged.</p>
+     *
+     * <p>A localized description for an {@code MBeanAttributeInfo} is
+     * obtained similarly. The default {@code resourceBundleBaseName}
+     * is the same as above. The default description and the
+     * descriptor fields {@code "descriptionResourceKey"} and {@code
+     * "descriptionResourceBundleBaseName"} come from the {@code
+     * MBeanAttributeInfo} rather than the {@code MBeanInfo}. If the
+     * attribute's {@linkplain MBeanFeatureInfo#getName() name} is {@code
+     * Foo} then its default {@code resourceKey} is {@code simpleClassName +
+     * ".attribute.Foo"}.</p>
+     *
+     * <p>Similar rules apply for operations, constructors, and notifications.
+     * If the name of the operation, constructor, or notification is {@code
+     * Foo} then the default {@code resourceKey} is respectively {@code
+     * simpleClassName + ".operation.Foo"}, {@code simpleClassName +
+     * ".constructor.Foo"}, or {@code simpleClassName + ".notification.Foo"}.
+     * If two operations or constructors have the same name (overloading) then
+     * they have the same default {@code resourceKey}; if different localized
+     * descriptions are needed then a non-default key must be supplied using
+     * {@code "descriptionResourceKey"}.</p>
+     *
+     * <p>Similar rules also apply for descriptions of parameters ({@link
+     * MBeanParameterInfo}). The default {@code resourceKey} for a parameter
+     * whose {@linkplain MBeanFeatureInfo#getName() name} is {@code
+     * Bar} in an operation or constructor called {@code Foo} is {@code
+     * simpleClassName + ".operation.Foo.Bar"} or {@code simpleClassName +
+     * ".constructor.Foo.Bar"} respectively.</p>
+     *
+     * <h4>Example</h4>
+     *
+     * <p>Suppose you have an MBean defined by these two Java source files:</p>
+     *
+     * <pre>
+     * // ConfigurationMBean.java
+     * package com.example;
+     * public interface ConfigurationMBean {
+     *     public String getName();
+     *     public void save(String fileName);
+     * }
+     *
+     * // Configuration.java
+     * package com.example;
+     * public class Configuration implements ConfigurationMBean {
+     *     public Configuration(String defaultName) {
+     *         ...
+     *     }
+     *     ...
+     * }
+     * </pre>
+     *
+     * <p>Then you could define the default descriptions for the MBean, by
+     * including a resource bundle called {@code com/example/MBeanDescriptions}
+     * with the compiled classes. Most often this is done by creating a file
+     * {@code MBeanDescriptions.properties} in the same directory as {@code
+     * ConfigurationMBean.java}. Make sure that this file is copied into the
+     * same place as the compiled classes; in typical build environments that
+     * will be true by default.</p>
+     *
+     * <p>The file {@code com/example/MBeanDescriptions.properties} might
+     * look like this:</p>
+     *
+     * <pre>
+     * # Description of the MBean
+     * ConfigurationMBean.mbean = Configuration manager
+     *
+     * # Description of the Name attribute
+     * ConfigurationMBean.attribute.Name = The name of the configuration
+     *
+     * # Description of the save operation
+     * ConfigurationMBean.operation.save = Save the configuration to a file
+     *
+     * # Description of the parameter to the save operation.
+     * # Parameter names from the original Java source are not available,
+     * # so the default names are p1, p2, etc.  If the names were available,
+     * # this would be ConfigurationMBean.operation.save.fileName
+     * ConfigurationMBean.operation.save.p1 = The name of the file
+     *
+     * # Description of the constructor.  The default name of a constructor is
+     * # its fully-qualified class name.
+     * ConfigurationMBean.constructor.com.example.Configuration = <!--
+     * -->Constructor with name of default file
+     * # Description of the constructor parameter.
+     * ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
+     * -->Name of the default file
+     * </pre>
+     *
+     * <p>Starting with this file, you could create descriptions for the French
+     * locale by creating {@code com/example/MBeanDescriptions_fr.properties}.
+     * The keys in this file are the same as before but the text has been
+     * translated:
+     *
+     * <pre>
+     * ConfigurationMBean.mbean = Gestionnaire de configuration
+     *
+     * ConfigurationMBean.attribute.Name = Le nom de la configuration
+     *
+     * ConfigurationMBean.operation.save = Sauvegarder la configuration <!--
+     * -->dans un fichier
+     *
+     * ConfigurationMBean.operation.save.p1 = Le nom du fichier
+     *
+     * ConfigurationMBean.constructor.com.example.Configuration = <!--
+     * -->Constructeur avec nom du fichier par d&eacute;faut
+     * ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
+     * -->Nom du fichier par d&eacute;faut
+     * </pre>
+     *
+     * <p>The descriptions in {@code MBeanDescriptions.properties} and
+     * {@code MBeanDescriptions_fr.properties} will only be consulted if
+     * {@code localizeDescriptions} is called, perhaps because the
+     * MBean Server has been wrapped by {@link
+     * ClientContext#newLocalizeMBeanInfoForwarder} or because the
+     * connector server has been created with the {@link
+     * javax.management.remote.JMXConnectorServer#LOCALIZE_MBEAN_INFO_FORWARDER
+     * LOCALIZE_MBEAN_INFO_FORWARDER} option. If you want descriptions
+     * even when there is no localization step, then you should consider
+     * using {@link Description &#64;Description} annotations. Annotations
+     * provide descriptions by default but are overridden if {@code
+     * localizeDescriptions} is called.</p>
+     *
+     * @param locale the target locale for descriptions.  Cannot be null.
+     *
+     * @param loader the {@code ClassLoader} to use for looking up resource
+     * bundles.
+     *
+     * @return an {@code MBeanInfo} with descriptions appropriately localized.
+     *
+     * @throws NullPointerException if {@code locale} is null.
+     */
+    public MBeanInfo localizeDescriptions(Locale locale, ClassLoader loader) {
+        if (locale == null)
+            throw new NullPointerException("locale");
+        Descriptor d = getDescriptor();
+        String mbiLocaleString = (String) d.getFieldValue(JMX.LOCALE_FIELD);
+        if (locale.toString().equals(mbiLocaleString))
+            return this;
+        return new Rewriter(this, locale, loader).getMBeanInfo();
+    }
+
+    private static class Rewriter {
+        private final MBeanInfo mbi;
+        private final ClassLoader loader;
+        private final Locale locale;
+        private final String packageName;
+        private final String simpleClassNamePlusDot;
+        private ResourceBundle defaultBundle;
+        private boolean defaultBundleLoaded;
+
+        // ResourceBundle.getBundle throws NullPointerException
+        // if the loader is null, even though that is perfectly
+        // valid and means the bootstrap loader.  So we work
+        // around with a ClassLoader that is equivalent to the
+        // bootstrap loader but is not null.
+        private static final ClassLoader bootstrapLoader =
+                new ClassLoader(null) {};
+
+        Rewriter(MBeanInfo mbi, Locale locale, ClassLoader loader) {
+            this.mbi = mbi;
+            this.locale = locale;
+            if (loader == null)
+                loader = bootstrapLoader;
+            this.loader = loader;
+
+            String intfName = (String)
+                    mbi.getDescriptor().getFieldValue("interfaceClassName");
+            if (intfName == null)
+                intfName = mbi.getClassName();
+            int lastDot = intfName.lastIndexOf('.');
+            this.packageName = intfName.substring(0, lastDot + 1);
+            this.simpleClassNamePlusDot = intfName.substring(lastDot + 1) + ".";
+            // Inner classes show up as Outer$Inner so won't match the dot.
+            // When there is no dot, lastDot is -1,
+            // packageName is empty, and simpleClassNamePlusDot is intfName.
+        }
+
+        MBeanInfo getMBeanInfo() {
+            MBeanAttributeInfo[] mbais =
+                    rewrite(mbi.getAttributes(), "attribute.");
+            MBeanOperationInfo[] mbois =
+                    rewrite(mbi.getOperations(), "operation.");
+            MBeanConstructorInfo[] mbcis =
+                    rewrite(mbi.getConstructors(), "constructor.");
+            MBeanNotificationInfo[] mbnis =
+                    rewrite(mbi.getNotifications(), "notification.");
+            Descriptor d = mbi.getDescriptor();
+            d = changeLocale(d);
+            String description = getDescription(d, "mbean", "");
+            if (description == null)
+                description = mbi.getDescription();
+            return new MBeanInfo(
+                    mbi.getClassName(), description,
+                    mbais, mbcis, mbois, mbnis, d);
+        }
+
+        private Descriptor changeLocale(Descriptor d) {
+            if (d.getFieldValue(JMX.LOCALE_FIELD) != null) {
+                Map<String, Object> map = new HashMap<String, Object>();
+                for (String field : d.getFieldNames())
+                    map.put(field, d.getFieldValue(field));
+                map.remove(JMX.LOCALE_FIELD);
+                d = new ImmutableDescriptor(map);
+            }
+            return ImmutableDescriptor.union(
+                    d, new ImmutableDescriptor(JMX.LOCALE_FIELD + "=" + locale));
+        }
+
+        private String getDescription(
+                Descriptor d, String defaultPrefix, String defaultSuffix) {
+            ResourceBundle bundle = bundleFromDescriptor(d);
+            if (bundle == null)
+                return null;
+            String key =
+                    (String) d.getFieldValue(JMX.DESCRIPTION_RESOURCE_KEY_FIELD);
+            if (key == null)
+                key = simpleClassNamePlusDot + defaultPrefix + defaultSuffix;
+            return descriptionFromResource(bundle, key);
+        }
+
+        private <T extends MBeanFeatureInfo> T[] rewrite(
+                T[] features, String resourcePrefix) {
+            for (int i = 0; i < features.length; i++) {
+                T feature = features[i];
+                Descriptor d = feature.getDescriptor();
+                String description =
+                        getDescription(d, resourcePrefix, feature.getName());
+                if (description != null &&
+                        !description.equals(feature.getDescription())) {
+                    features[i] = setDescription(feature, description);
+                }
+            }
+            return features;
+        }
+
+        private <T extends MBeanFeatureInfo> T setDescription(
+                T feature, String description) {
+
+            Object newf;
+            String name = feature.getName();
+            Descriptor d = feature.getDescriptor();
+
+            if (feature instanceof MBeanAttributeInfo) {
+                MBeanAttributeInfo mbai = (MBeanAttributeInfo) feature;
+                newf = new MBeanAttributeInfo(
+                        name, mbai.getType(), description,
+                        mbai.isReadable(), mbai.isWritable(), mbai.isIs(),
+                        d);
+            } else if (feature instanceof MBeanOperationInfo) {
+                MBeanOperationInfo mboi = (MBeanOperationInfo) feature;
+                MBeanParameterInfo[] sig = rewrite(
+                        mboi.getSignature(), "operation." + name + ".");
+                newf = new MBeanOperationInfo(
+                        name, description, sig,
+                        mboi.getReturnType(), mboi.getImpact(), d);
+            } else if (feature instanceof MBeanConstructorInfo) {
+                MBeanConstructorInfo mbci = (MBeanConstructorInfo) feature;
+                MBeanParameterInfo[] sig = rewrite(
+                        mbci.getSignature(), "constructor." + name + ".");
+                newf = new MBeanConstructorInfo(
+                        name, description, sig, d);
+            } else if (feature instanceof MBeanNotificationInfo) {
+                MBeanNotificationInfo mbni = (MBeanNotificationInfo) feature;
+                newf = new MBeanNotificationInfo(
+                        mbni.getNotifTypes(), name, description, d);
+            } else if (feature instanceof MBeanParameterInfo) {
+                MBeanParameterInfo mbpi = (MBeanParameterInfo) feature;
+                newf = new MBeanParameterInfo(
+                        name, mbpi.getType(), description, d);
+            } else {
+                logger().log(Level.FINE, "Unknown feature type: " +
+                        feature.getClass());
+                newf = feature;
+            }
+
+            return Util.<T>cast(newf);
+        }
+
+        private ResourceBundle bundleFromDescriptor(Descriptor d) {
+            String bundleName = (String) d.getFieldValue(
+                    JMX.DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD);
+
+            if (bundleName != null)
+                return getBundle(bundleName);
+
+            if (defaultBundleLoaded)
+                return defaultBundle;
+
+            bundleName = packageName + "MBeanDescriptions";
+            defaultBundle = getBundle(bundleName);
+            defaultBundleLoaded = true;
+            return defaultBundle;
+        }
+
+        private String descriptionFromResource(
+                ResourceBundle bundle, String key) {
+            try {
+                return bundle.getString(key);
+            } catch (MissingResourceException e) {
+                logger().log(Level.FINEST, "No resource for " + key, e);
+            } catch (Exception e) {
+                logger().log(Level.FINE, "Bad resource for " + key, e);
+            }
+            return null;
+        }
+
+        private ResourceBundle getBundle(String name) {
+            try {
+                return ResourceBundle.getBundle(name, locale, loader);
+            } catch (Exception e) {
+                logger().log(Level.FINE,
+                           "Could not load ResourceBundle " + name, e);
+                return null;
+            }
+        }
+
+        private Logger logger() {
+            return Logger.getLogger("javax.management.locale");
+        }
+    }
 }
--- a/src/share/classes/javax/management/MBeanServerConnection.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/MBeanServerConnection.java	Thu Nov 13 11:15:01 2008 -0800
@@ -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:45:22 2008 -0800
+++ b/src/share/classes/javax/management/MBeanServerNotification.java	Thu Nov 13 11:15:01 2008 -0800
@@ -27,15 +27,70 @@
 
 
 /**
- * Represents a notification emitted by the MBean server through the MBeanServerDelegate MBean.
+ * Represents a notification emitted by the MBean Server through the MBeanServerDelegate MBean.
  * The MBean Server emits the following types of notifications: MBean registration, MBean
- * de-registration.
+ * unregistration.
  * <P>
- * To receive to MBeanServerNotifications, you need to be declared as listener to
- * the {@link javax.management.MBeanServerDelegate javax.management.MBeanServerDelegate} MBean
- * that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is:
+ * To receive MBeanServerNotifications, you need to register a listener with
+ * the {@link MBeanServerDelegate MBeanServerDelegate} MBean
+ * that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is
+ * {@link MBeanServerDelegate#DELEGATE_NAME}, which is
  * <CODE>JMImplementation:type=MBeanServerDelegate</CODE>.
  *
+ * <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>
+ *
+ * <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/Notification.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/Notification.java	Thu Nov 13 11:15:01 2008 -0800
@@ -54,7 +54,7 @@
  * @since 1.5
  */
 @SuppressWarnings("serial")  // serialVersionUID is not constant
-public class Notification extends EventObject {
+public class Notification extends EventObject implements Cloneable {
 
     // Serialization compatibility stuff:
     // Two serial forms are supported in this class. The selected form depends
@@ -244,6 +244,26 @@
     }
 
     /**
+     * <p>Creates and returns a copy of this object.  The copy is created as
+     * described for {@link Object#clone()}.  This means, first, that the
+     * class of the object will be the same as the class of this object, and,
+     * second, that the copy is a "shallow copy".  Fields of this notification
+     * are not themselves copied.  In particular, the {@linkplain
+     * #getUserData user data} of the copy is the same object as the
+     * original.</p>
+     *
+     * @return a copy of this object.
+     */
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
      * Sets the source.
      *
      * @param source the new source for this object.
@@ -285,8 +305,10 @@
     /**
      * Get the notification type.
      *
-     * @return The notification type. It's a string expressed in a dot notation similar
-     * to Java properties. An example of a notification type is network.alarm.router .
+     * @return The notification type. It's a string expressed in a dot notation
+     * similar to Java properties. It is recommended that the notification type
+     * should follow the reverse-domain-name convention used by Java package
+     * names.  An example of a notification type is com.example.alarm.router.
      */
     public String getType() {
         return type ;
@@ -317,15 +339,26 @@
     /**
      * Get the notification message.
      *
-     * @return The message string of this notification object. It contains in a string,
-     * which could be the explanation of the notification for displaying to a user
+     * @return The message string of this notification object.
      *
+     * @see #setMessage
      */
     public String getMessage() {
         return message ;
     }
 
     /**
+     * Set the notification message.
+     *
+     * @param message the new notification message.
+     *
+     * @see #getMessage
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    /**
      * Get the user data.
      *
      * @return The user data object. It is used for whatever data
@@ -355,6 +388,7 @@
      *
      * @return A String representation of this notification.
      */
+    @Override
     public String toString() {
         return super.toString()+"[type="+type+"][message="+message+"]";
     }
--- a/src/share/classes/javax/management/QueryNotificationFilter.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/QueryNotificationFilter.java	Thu Nov 13 11:15:01 2008 -0800
@@ -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/event/EventClient.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/event/EventClient.java	Thu Nov 13 11:15:01 2008 -0800
@@ -29,7 +29,6 @@
 import com.sun.jmx.event.LeaseRenewer;
 import com.sun.jmx.event.ReceiverBuffer;
 import com.sun.jmx.event.RepeatedSingletonJob;
-import com.sun.jmx.namespace.JMXNamespaceUtils;
 import com.sun.jmx.mbeanserver.PerThreadGroupPool;
 import com.sun.jmx.remote.util.ClassLogger;
 
@@ -58,7 +57,6 @@
 import javax.management.NotificationFilter;
 import javax.management.NotificationListener;
 import javax.management.ObjectName;
-import javax.management.remote.JMXConnector;
 import javax.management.remote.NotificationResult;
 import javax.management.remote.TargetedNotification;
 
@@ -129,11 +127,12 @@
     public static final String NOTIFS_LOST = "jmx.event.service.notifs.lost";
 
     /**
-     * The default lease time, {@value}, in milliseconds.
+     * The default lease time that EventClient instances will request, in
+     * milliseconds.  This value is {@value}.
      *
      * @see EventClientDelegateMBean#lease
      */
-    public static final long DEFAULT_LEASE_TIMEOUT = 300000;
+    public static final long DEFAULT_REQUESTED_LEASE_TIME = 300000;
 
     /**
      * <p>Constructs a default {@code EventClient} object.</p>
@@ -173,7 +172,7 @@
      */
     public EventClient(EventClientDelegateMBean delegate)
     throws IOException {
-        this(delegate, null, null, null, DEFAULT_LEASE_TIMEOUT);
+        this(delegate, null, null, null, DEFAULT_REQUESTED_LEASE_TIME);
     }
 
     /**
@@ -196,7 +195,7 @@
      * If {@code null}, a default scheduler will be used.
      * @param requestedLeaseTime The lease time used to keep this client alive
      * in the {@link EventClientDelegateMBean}.  A value of zero is equivalent
-     * to the {@linkplain #DEFAULT_LEASE_TIMEOUT default value}.
+     * to the {@linkplain #DEFAULT_REQUESTED_LEASE_TIME default value}.
      *
      * @throws IllegalArgumentException If {@code delegate} is null.
      * @throws IOException If an I/O error occurs when communicating with the
@@ -213,7 +212,7 @@
         }
 
         if (requestedLeaseTime == 0)
-            requestedLeaseTime = DEFAULT_LEASE_TIMEOUT;
+            requestedLeaseTime = DEFAULT_REQUESTED_LEASE_TIME;
         else if (requestedLeaseTime < 0) {
             throw new IllegalArgumentException(
                     "Negative lease time: " + requestedLeaseTime);
@@ -269,7 +268,13 @@
                         new ScheduledThreadPoolExecutor(20, daemonThreadFactory);
                 executor.setKeepAliveTime(1, TimeUnit.SECONDS);
                 executor.allowCoreThreadTimeOut(true);
-                executor.setRemoveOnCancelPolicy(true);
+                if (setRemoveOnCancelPolicy != null) {
+                    try {
+                        setRemoveOnCancelPolicy.invoke(executor, true);
+                    } catch (Exception e) {
+                        logger.trace("setRemoveOnCancelPolicy", e);
+                    }
+                }
                 // By default, a ScheduledThreadPoolExecutor will keep jobs
                 // in its queue even after they have been cancelled.  They
                 // will only be removed when their scheduled time arrives.
@@ -277,12 +282,25 @@
                 // this EventClient, this can lead to a moderately large number
                 // of objects remaining referenced until the renewal time
                 // arrives.  Hence the above call, which removes the job from
-                // the queue as soon as it is cancelled.
+                // the queue as soon as it is cancelled.  Since the call is
+                // new with JDK 7, we invoke it via reflection to make it
+                // easier to use this code on JDK 6.
                 return executor;
             }
         };
         return leaseRenewerThreadPool.getThreadPoolExecutor(create);
+    }
 
+    private static final Method setRemoveOnCancelPolicy;
+    static {
+        Method m;
+        try {
+            m = ScheduledThreadPoolExecutor.class.getMethod(
+                    "setRemoveOnCancelPolicy", boolean.class);
+        } catch (Exception e) {
+            m = null;
+        }
+        setRemoveOnCancelPolicy = m;
     }
 
     /**
@@ -1042,7 +1060,7 @@
             final public EventClient call() throws Exception {
                 EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn);
                 return new EventClient(ecd, eventRelay, null, null,
-                        DEFAULT_LEASE_TIMEOUT);
+                        DEFAULT_REQUESTED_LEASE_TIME);
             }
         };
 
@@ -1080,24 +1098,6 @@
         return clientId;
     }
 
-    /**
-     * Returns a JMX Connector that will use an {@link EventClient}
-     * to subscribe for notifications. If the server doesn't have
-     * an {@link EventClientDelegateMBean}, then the connector will
-     * use the legacy notification mechanism instead.
-     *
-     * @param wrapped The underlying JMX Connector wrapped by the returned
-     *               connector.
-     *
-     * @return A JMX Connector that will uses an {@link EventClient}, if
-     *         available.
-     *
-     * @see EventClient#getEventClientConnection(MBeanServerConnection)
-     */
-    public static JMXConnector withEventClient(final JMXConnector wrapped) {
-        return JMXNamespaceUtils.withEventClient(wrapped);
-    }
-
     private static final PerThreadGroupPool<ScheduledThreadPoolExecutor>
             leaseRenewerThreadPool = PerThreadGroupPool.make();
 }
--- a/src/share/classes/javax/management/event/EventClientDelegate.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/event/EventClientDelegate.java	Thu Nov 13 11:15:01 2008 -0800
@@ -149,6 +149,7 @@
     // of a setMBeanServer on some other forwarder later in the chain.
 
     private static class Forwarder extends SingleMBeanForwarder {
+        private MBeanServer loopMBS;
 
         private static class UnsupportedInvocationHandler
                 implements InvocationHandler {
@@ -173,7 +174,11 @@
         private volatile boolean madeECD;
 
         Forwarder() {
-            super(OBJECT_NAME, makeUnsupportedECD());
+            super(OBJECT_NAME, makeUnsupportedECD(), true);
+        }
+
+        synchronized void setLoopMBS(MBeanServer loopMBS) {
+            this.loopMBS = loopMBS;
         }
 
         @Override
@@ -186,7 +191,7 @@
                         AccessController.doPrivileged(
                             new PrivilegedAction<EventClientDelegate>() {
                                 public EventClientDelegate run() {
-                                    return getEventClientDelegate(Forwarder.this);
+                                    return getEventClientDelegate(loopMBS);
                                 }
                             });
                     DynamicMBean mbean = new StandardMBean(
@@ -208,11 +213,46 @@
      * that are targeted for that MBean and handles them itself.  All other
      * requests are forwarded to the next element in the forwarder chain.</p>
      *
+     * @param nextMBS the next {@code MBeanServer} in the chain of forwarders,
+     * which might be another {@code MBeanServerForwarder} or a plain {@code
+     * MBeanServer}.  This is the object to which {@code MBeanServer} requests
+     * that do not concern the {@code EventClientDelegateMBean} are sent.
+     * It will be the value of {@link MBeanServerForwarder#getMBeanServer()
+     * getMBeanServer()} on the returned object, and can be changed with {@link
+     * MBeanServerForwarder#setMBeanServer setMBeanServer}.  It can be null but
+     * must be set to a non-null value before any {@code MBeanServer} requests
+     * arrive.
+     *
+     * @param loopMBS the {@code MBeanServer} to which requests from the
+     * {@code EventClientDelegateMBean} should be sent.  For example,
+     * when you invoke the {@link EventClientDelegateMBean#addListener
+     * addListener} operation on the {@code EventClientDelegateMBean}, it will
+     * result in a call to {@link
+     * MBeanServer#addNotificationListener(ObjectName, NotificationListener,
+     * NotificationFilter, Object) addNotificationListener} on this object.
+     * If this parameter is null, then these requests will be sent to the
+     * newly-created {@code MBeanServerForwarder}.  Usually the parameter will
+     * either be null or will be the result of {@link
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder()
+     * getSystemMBeanServerForwarder()} for the connector server in which
+     * this forwarder will be installed.
+     *
      * @return a new {@code MBeanServerForwarder} that simulates the existence
      * of an {@code EventClientDelegateMBean}.
+     *
+     * @see javax.management.remote.JMXConnectorServer#installStandardForwarders
      */
-    public static MBeanServerForwarder newForwarder() {
-        return new Forwarder();
+    public static MBeanServerForwarder newForwarder(
+            MBeanServer nextMBS, MBeanServer loopMBS) {
+        Forwarder mbsf = new Forwarder();
+        // We must setLoopMBS before setMBeanServer, because when we
+        // setMBeanServer that will call getEventClientDelegate(loopMBS).
+        if (loopMBS == null)
+            loopMBS = mbsf;
+        mbsf.setLoopMBS(loopMBS);
+        if (nextMBS != null)
+            mbsf.setMBeanServer(nextMBS);
+        return mbsf;
     }
 
     /**
@@ -437,10 +477,9 @@
     // private classes
     // ------------------------------------
     private class ClientInfo {
-        String clientId;
-        EventBuffer buffer;
-        NotificationListener clientListener;
-        Map<Integer, AddedListener> listenerInfoMap =
+        final String clientId;
+        final NotificationListener clientListener;
+        final Map<Integer, AddedListener> listenerInfoMap =
                 new HashMap<Integer, AddedListener>();
 
         ClientInfo(String clientId, EventForwarder forwarder) {
@@ -703,7 +742,8 @@
         clientInfo = clientInfoMap.get(clientId);
 
         if (clientInfo == null) {
-            throw new EventClientNotFoundException("The client is not found.");
+            throw new EventClientNotFoundException(
+                    "Client not found (id " + clientId + ")");
         }
 
         return clientInfo;
--- a/src/share/classes/javax/management/event/EventClientDelegateMBean.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/event/EventClientDelegateMBean.java	Thu Nov 13 11:15:01 2008 -0800
@@ -51,7 +51,8 @@
  * and the MBean Server, that will intercept accesses to the Event Client
  * Delegate MBean and treat them as the real MBean would. This forwarder is
  * inserted by default with the standard RMI Connector Server, and can also
- * be created explicitly using {@link EventClientDelegate#newForwarder()}.
+ * be created explicitly using {@link EventClientDelegate#newForwarder
+ * EventClientDelegate.newForwarder}.
  *
  * <li><p>A variant on the above is to replace the MBean Server that is
  * used locally with a forwarder as described above.  Since
@@ -61,9 +62,7 @@
  *
  * <pre>
  * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  // or whatever
- * MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
- * mbsf.setMBeanServer(mbs);
- * mbs = mbsf;
+ * mbs = EventClientDelegate.newForwarder(mbs, null);
  * // now use mbs just as you did before, but it will have an EventClientDelegate
  * </pre>
  *
--- a/src/share/classes/javax/management/event/EventRelay.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/event/EventRelay.java	Thu Nov 13 11:15:01 2008 -0800
@@ -27,7 +27,6 @@
 
 import java.io.IOException;
 import java.util.concurrent.Executors;  // for javadoc
-import java.util.concurrent.ScheduledFuture;
 
 /**
  * This interface is used to specify a way to receive
--- a/src/share/classes/javax/management/event/package-info.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/event/package-info.java	Thu Nov 13 11:15:01 2008 -0800
@@ -83,8 +83,8 @@
  * javax.management.event.EventClientDelegateMBean EventClientDelegateMBean}
  * must be registered in the MBean Server, or the connector server must
  * be configured to simulate the existence of this MBean, for example
- * using {@link javax.management.event.EventClientDelegate#newForwarder()
- * EventClientDelegate.newForwarder()}. The standard RMI connector is so
+ * using {@link javax.management.event.EventClientDelegate#newForwarder
+ * EventClientDelegate.newForwarder}. The standard RMI connector is so
  * configured by default. The {@code EventClientDelegateMBean} documentation
  * has further details.</p>
  *
--- a/src/share/classes/javax/management/namespace/JMXNamespaces.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/namespace/JMXNamespaces.java	Thu Nov 13 11:15:01 2008 -0800
@@ -26,21 +26,19 @@
 package javax.management.namespace;
 
 import com.sun.jmx.defaults.JmxProperties;
-import com.sun.jmx.namespace.JMXNamespaceUtils;
 import com.sun.jmx.namespace.ObjectNameRouter;
 import com.sun.jmx.namespace.serial.RewritingProcessor;
 import com.sun.jmx.namespace.RoutingConnectionProxy;
 import com.sun.jmx.namespace.RoutingServerProxy;
 
-import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.management.InstanceNotFoundException;
 import javax.management.MBeanServer;
 import javax.management.MBeanServerConnection;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
-import javax.management.remote.JMXConnector;
 
 /**
  * Static constants and utility methods to help work with
@@ -69,23 +67,6 @@
 
 
     /**
-     * Returns a connector connected to a sub name space exposed through
-     * the parent connector.
-     * @param parent the parent connector.
-     * @param namespace the {@linkplain javax.management.namespace name space}
-     *                  to which the returned connector is
-     *                  connected.
-     * @return A connector connected to a sub name space exposed through
-     * the parent connector.
-     **/
-    public static JMXConnector narrowToNamespace(final JMXConnector parent,
-                                  final String namespace)
-        throws IOException {
-
-        return JMXNamespaceUtils.cd(parent,namespace,true);
-    }
-
-    /**
      * Creates a new {@code MBeanServerConnection} proxy on a
      * {@linkplain javax.management.namespace sub name space}
      * of the given parent.
@@ -96,15 +77,18 @@
      *               name space} in which to narrow.
      * @return A new {@code MBeanServerConnection} proxy that shows the content
      *         of that name space.
-     * @throws IllegalArgumentException if the name space does not exist, or
-     *         if a proxy for that name space cannot be created.
+     * @throws IllegalArgumentException if either argument is null,
+     * or the name space does not exist, or if a proxy for that name space
+     * cannot be created.  The {@linkplain Throwable#getCause() cause} of
+     * this exception will be an {@link InstanceNotFoundException} if and only
+     * if the name space is found not to exist.
      */
     public static MBeanServerConnection narrowToNamespace(
                         MBeanServerConnection parent,
                         String namespace) {
         if (LOG.isLoggable(Level.FINER))
             LOG.finer("Making MBeanServerConnection for: " +namespace);
-        return RoutingConnectionProxy.cd(parent,namespace);
+        return RoutingConnectionProxy.cd(parent, namespace, true);
     }
 
     /**
@@ -120,13 +104,15 @@
      *         of that name space.
      * @throws IllegalArgumentException if either argument is null,
      * or the name space does not exist, or if a proxy for that name space
-     * cannot be created.
+     * cannot be created.  The {@linkplain Throwable#getCause() cause} of
+     * this exception will be an {@link InstanceNotFoundException} if and only
+     * if the name space is found not to exist.
      */
     public static MBeanServer narrowToNamespace(MBeanServer parent,
             String namespace) {
         if (LOG.isLoggable(Level.FINER))
-            LOG.finer("Making NamespaceServerProxy for: " +namespace);
-        return RoutingServerProxy.cd(parent,namespace);
+            LOG.finer("Making MBeanServer for: " +namespace);
+        return RoutingServerProxy.cd(parent, namespace, true);
     }
 
     /**
@@ -266,7 +252,7 @@
                 ObjectNameRouter.normalizeNamespacePath(namespace,false,
                             true,false);
         try {
-            // We could use Util.newObjectName here - but throwing an
+            // We could use ObjectName.valueOf here - but throwing an
             // IllegalArgumentException that contains just the supplied
             // namespace instead of the whole ObjectName seems preferable.
             return ObjectName.getInstance(sourcePath+
--- a/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java	Thu Nov 13 11:15:01 2008 -0800
@@ -27,10 +27,10 @@
 
 import com.sun.jmx.defaults.JmxProperties;
 import com.sun.jmx.mbeanserver.Util;
-import com.sun.jmx.namespace.JMXNamespaceUtils;
 import com.sun.jmx.remote.util.EnvHelp;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
@@ -39,6 +39,7 @@
 
 import javax.management.AttributeChangeNotification;
 
+import javax.management.ClientContext;
 import javax.management.InstanceNotFoundException;
 import javax.management.ListenerNotFoundException;
 import javax.management.MBeanNotificationInfo;
@@ -220,17 +221,26 @@
                 initParentOnce(this);
 
         // URL must not be null.
-        this.jmxURL     = JMXNamespaceUtils.checkNonNull(sourceURL,"url");
+        if (sourceURL == null)
+            throw new IllegalArgumentException("Null URL");
+        this.jmxURL     = sourceURL;
         this.broadcaster =
             new NotificationBroadcasterSupport(connectNotification);
 
         // handles options
-        this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap);
+        this.optionsMap = unmodifiableMap(optionsMap);
 
         // handles (dis)connection events
         this.listener = new ConnectionListener();
     }
 
+    // returns un unmodifiable view of a map.
+    private static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
+        if (aMap == null || aMap.isEmpty())
+            return Collections.emptyMap();
+        return Collections.unmodifiableMap(aMap);
+    }
+
    /**
     * Returns the {@code JMXServiceURL} that is (or will be) used to
     * connect to the remote name space. <p>
@@ -483,106 +493,171 @@
         }
     }
 
-    JMXConnector connect(JMXServiceURL url, Map<String,?> env)
+    private JMXConnector connect(JMXServiceURL url, Map<String,?> env)
             throws IOException {
-        final JMXConnector c = newJMXConnector(jmxURL, env);
+        final JMXConnector c = newJMXConnector(url, env);
         c.connect(env);
         return c;
     }
 
     /**
-     * Creates a new JMXConnector with the specified {@code url} and
-     * {@code env} options map.
-     * <p>
-     * This method first calls {@link JMXConnectorFactory#newJMXConnector
-     * JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new
-     * JMX connector, and returns that.
-     * </p>
-     * <p>
-     * A subclass of {@link JMXRemoteNamespace} can provide an implementation
-     * that connects to a  sub namespace of the remote server by subclassing
-     * this class in the following way:
-     * <pre>
-     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
-     *    private final String subnamespace;
-     *    JMXRemoteSubNamespace(JMXServiceURL url,
-     *              Map{@code <String,?>} env, String subnamespace) {
-     *        super(url,options);
-     *        this.subnamespace = subnamespace;
-     *    }
-     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
-     *              Map<String,?> env) throws IOException {
-     *        final JMXConnector inner = super.newJMXConnector(url,env);
-     *        return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String)
-     *               JMXNamespaces.narrowToNamespace(inner,subnamespace)};
-     *    }
-     * }
-     * </pre>
-     * </p>
-     * <p>
-     * Some connectors, like the JMXMP connector server defined by the
-     * version 1.2 of the JMX API may not have been upgraded to use the
-     * new {@linkplain javax.management.event Event Service} defined in this
-     * version of the JMX API.
-     * <p>
-     * In that case, and if the remote server to which this JMXRemoteNamespace
-     * connects also contains namespaces, it may be necessary to configure
-     * explicitly an {@linkplain
-     * javax.management.event.EventClientDelegate#newForwarder()
-     * Event Client Forwarder} on the remote server side, and to force the use
-     * of an {@link EventClient} on this client side.
-     * <br>
-     * A subclass of {@link JMXRemoteNamespace} can provide an implementation
-     * of {@code newJMXConnector} that will force notification subscriptions
-     * to flow through an {@link EventClient} over a legacy protocol by
-     * overriding this method in the following way:
-     * </p>
-     * <pre>
-     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
-     *    JMXRemoteSubNamespaceConnector(JMXServiceURL url,
-     *              Map<String,?> env) {
-     *        super(url,options);
-     *    }
-     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
-     *              Map<String,?> env) throws IOException {
-     *        final JMXConnector inner = super.newJMXConnector(url,env);
-     *        return {@link EventClient#withEventClient(
-     *                JMXConnector) EventClient.withEventClient(inner)};
-     *    }
-     * }
-     * </pre>
-     * <p>
-     * Note that the remote server also needs to provide an {@link
-     * javax.management.event.EventClientDelegateMBean}: only configuring
-     * the client side (this object) is not enough.<br>
-     * In summary, this technique should be used if the remote server
-     * supports JMX namespaces, but uses a JMX Connector Server whose
-     * implementation does not transparently use the new Event Service
-     * (as would be the case with the JMXMPConnectorServer implementation
-     * from the reference implementation of the JMX Remote API 1.0
-     * specification).
-     * </p>
+     * <p>Creates a new JMXConnector with the specified {@code url} and
+     * {@code env} options map.  The default implementation of this method
+     * returns {@link JMXConnectorFactory#newJMXConnector
+     * JMXConnectorFactory.newJMXConnector(jmxURL, env)}.  Subclasses can
+     * override this method to customize behavior.</p>
+     *
      * @param url  The JMXServiceURL of the remote server.
-     * @param optionsMap An unmodifiable options map that will be passed to the
+     * @param optionsMap An options map that will be passed to the
      *        {@link JMXConnectorFactory} when {@linkplain
      *        JMXConnectorFactory#newJMXConnector creating} the
      *        {@link JMXConnector} that can connect to the remote source
      *        MBean Server.
-     * @return An unconnected JMXConnector to use to connect to the remote
-     *         server
-     * @throws java.io.IOException if the connector could not be created.
+     * @return A JMXConnector to use to connect to the remote server
+     * @throws IOException if the connector could not be created.
      * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
      * @see #JMXRemoteNamespace
      */
     protected JMXConnector newJMXConnector(JMXServiceURL url,
             Map<String,?> optionsMap) throws IOException {
-        final JMXConnector c =
-                JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
-// TODO: uncomment this when contexts are added
-//        return ClientContext.withDynamicContext(c);
-        return c;
+        return JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
     }
 
+    /**
+     * <p>Called when a new connection is established using {@link #connect}
+     * so that subclasses can customize the connection.  The default
+     * implementation of this method effectively does the following:</p>
+     *
+     * <pre>
+     * MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
+     *                               jmxc.getMBeanServerConnection()};
+     * try {
+     *     return {@link ClientContext#withDynamicContext
+     *             ClientContext.withDynamicContext(mbsc)};
+     * } catch (IllegalArgumentException e) {
+     *     return mbsc;
+     * }
+     * </pre>
+     *
+     * <p>In other words, it arranges for the client context to be forwarded
+     * to the remote MBean Server if the remote MBean Server supports contexts;
+     * otherwise it ignores the client context.</p>
+     *
+     * <h4>Example: connecting to a remote namespace</h4>
+     *
+     * <p>A subclass that wanted to narrow into a namespace of
+     * the remote MBeanServer might look like this:</p>
+     *
+     * <pre>
+     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
+     *     private final String subnamespace;
+     *
+     *     JMXRemoteSubNamespace(
+     *             JMXServiceURL url, Map{@code <String, ?>} env, String subnamespace) {
+     *        super(url, env);
+     *        this.subnamespace = subnamespace;
+     *     }
+     *
+     *     {@code @Override}
+     *     protected MBeanServerConnection getMBeanServerConnection(
+     *             JMXConnector jmxc) throws IOException {
+     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
+     *         return {@link JMXNamespaces#narrowToNamespace(MBeanServerConnection,String)
+     *                 JMXNamespaces.narrowToNamespace(mbsc, subnamespace)};
+     *     }
+     * }
+     * </pre>
+     *
+     * <h4>Example: using the Event Service for notifications</h4>
+     *
+     * <p>Some connectors may have been designed to work with an earlier
+     * version of the JMX API, and may not have been upgraded to use
+     * the {@linkplain javax.management.event Event Service} defined in
+     * this version of the JMX API.  In that case, and if the remote
+     * server to which this JMXRemoteNamespace connects also contains
+     * namespaces, it may be necessary to configure explicitly an {@linkplain
+     * javax.management.event.EventClientDelegate#newForwarder Event Client
+     * Forwarder} on the remote server side, and to force the use of an {@link
+     * EventClient} on this client side.</p>
+     *
+     * <p>A subclass of {@link JMXRemoteNamespace} can provide an
+     * implementation of {@code getMBeanServerConnection} that will force
+     * notification subscriptions to flow through an {@link EventClient} over
+     * a legacy protocol.  It can do so by overriding this method in the
+     * following way:</p>
+     *
+     * <pre>
+     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
+     *     JMXRemoteEventClientNamespace(JMXServiceURL url, {@code Map<String,?>} env) {
+     *         super(url, env);
+     *     }
+     *
+     *     {@code @Override}
+     *     protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
+     *             throws IOException {
+     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
+     *         return EventClient.getEventClientConnection(mbsc);
+     *     }
+     * }
+     * </pre>
+     *
+     * <p>
+     * Note that the remote server also needs to provide an {@link
+     * javax.management.event.EventClientDelegateMBean}: configuring only
+     * the client side (this object) is not enough.</p>
+     *
+     * <p>In summary, this technique should be used if the remote server
+     * supports JMX namespaces, but uses a JMX Connector Server whose
+     * implementation does not transparently use the new Event Service
+     * (as would be the case with the JMXMPConnectorServer implementation
+     * from the reference implementation of the JMX Remote API 1.0
+     * specification).</p>
+     *
+     * @param jmxc the newly-created {@code JMXConnector}.
+     *
+     * @return an {@code MBeanServerConnection} connected to the remote
+     * MBeanServer.
+     *
+     * @throws IOException if the connection cannot be made.  If this method
+     * throws {@code IOException} then the calling {@link #connect()} method
+     * will also fail with an {@code IOException}.
+     *
+     * @see #connect
+     */
+    protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
+            throws IOException {
+        final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
+        try {
+            return ClientContext.withDynamicContext(mbsc);
+        } catch (IllegalArgumentException e) {
+            LOG.log(Level.FINER, "ClientContext.withDynamicContext", e);
+            return mbsc;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The sequence of events when this method is called includes,
+     * effectively, the following code:</p>
+     *
+     * <pre>
+     * JMXServiceURL url = {@link #getJMXServiceURL getJMXServiceURL}();
+     * JMXConnector jmxc = {@link #newJMXConnector newJMXConnector}(url, env);
+     * jmxc.connect();
+     * MBeanServerConnection mbsc = {@link #getMBeanServerConnection(JMXConnector)
+     *                               getMBeanServerConnection}(jmxc);
+     * </pre>
+     *
+     * <p>Here, {@code env} is a {@code Map} containing the entries from the
+     * {@code optionsMap} that was passed to the {@linkplain #JMXRemoteNamespace
+     * constructor} or to the {@link #newJMXRemoteNamespace newJMXRemoteNamespace}
+     * factory method.</p>
+     *
+     * <p>Subclasses can customize connection behavior by overriding the
+     * {@code getJMXServiceURL}, {@code newJMXConnector}, or
+     * {@code getMBeanServerConnection} methods.</p>
+     */
     public void connect() throws IOException {
         LOG.fine("connecting...");
         final Map<String,Object> env =
@@ -590,7 +665,7 @@
         try {
             // XXX: We should probably document this...
             // This allows to specify a loader name - which will be
-            // retrieved from the paret MBeanServer.
+            // retrieved from the parent MBeanServer.
             defaultClassLoader =
                 EnvHelp.resolveServerClassLoader(env,getMBeanServer());
         } catch (InstanceNotFoundException x) {
@@ -604,7 +679,7 @@
         final JMXConnector aconn = connect(url,env);
         final MBeanServerConnection msc;
         try {
-            msc = aconn.getMBeanServerConnection();
+            msc = getMBeanServerConnection(aconn);
             aconn.addConnectionNotificationListener(listener,null,aconn);
         } catch (IOException io) {
             close(aconn);
--- a/src/share/classes/javax/management/openmbean/CompositeDataSupport.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java	Thu Nov 13 11:15:01 2008 -0800
@@ -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:45:22 2008 -0800
+++ b/src/share/classes/javax/management/package.html	Thu Nov 13 11:15:01 2008 -0800
@@ -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>
--- a/src/share/classes/javax/management/remote/JMXConnectorFactory.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/remote/JMXConnectorFactory.java	Thu Nov 13 11:15:01 2008 -0800
@@ -322,10 +322,12 @@
                 JMXConnectorProvider.class;
         final String protocol = serviceURL.getProtocol();
         final String providerClassName = "ClientProvider";
+        final JMXServiceURL providerURL = serviceURL;
 
-        JMXConnectorProvider provider =
-            getProvider(serviceURL, envcopy, providerClassName,
-                        targetInterface, loader);
+        JMXConnectorProvider provider = getProvider(providerURL, envcopy,
+                                               providerClassName,
+                                               targetInterface,
+                                               loader);
 
         IOException exception = null;
         if (provider == null) {
@@ -336,7 +338,7 @@
             if (loader != null) {
                 try {
                     JMXConnector connection =
-                        getConnectorAsService(loader, serviceURL, envcopy);
+                        getConnectorAsService(loader, providerURL, envcopy);
                     if (connection != null)
                         return connection;
                 } catch (JMXProviderException e) {
@@ -345,8 +347,7 @@
                     exception = e;
                 }
             }
-            provider =
-                getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
+            provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
                             JMXConnectorFactory.class.getClassLoader(),
                             providerClassName, targetInterface);
         }
@@ -448,9 +449,10 @@
                 getProviderIterator(JMXConnectorProvider.class, loader);
         JMXConnector connection;
         IOException exception = null;
-        while(providers.hasNext()) {
+        while (providers.hasNext()) {
+            JMXConnectorProvider provider = providers.next();
             try {
-                connection = providers.next().newJMXConnector(url, map);
+                connection = provider.newJMXConnector(url, map);
                 return connection;
             } catch (JMXProviderException e) {
                 throw e;
@@ -553,4 +555,5 @@
     private static String protocol2package(String protocol) {
         return protocol.replace('+', '.').replace('-', '_');
     }
+
 }
--- a/src/share/classes/javax/management/remote/JMXConnectorServer.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/remote/JMXConnectorServer.java	Thu Nov 13 11:15:01 2008 -0800
@@ -33,6 +33,7 @@
 import java.util.Map;
 
 import java.util.NoSuchElementException;
+import javax.management.ClientContext;
 import javax.management.MBeanInfo;  // for javadoc
 import javax.management.MBeanNotificationInfo;
 import javax.management.MBeanRegistration;
@@ -103,6 +104,56 @@
 
      /**
       * <p>Name of the attribute that specifies whether this connector
+      * server allows clients to communicate a context with each request.
+      * The value associated with this attribute, if any, must be a string
+      * that is equal to {@code "true"} or {@code "false"}, ignoring case.
+      * If it is {@code "true"}, then the connector server will simulate
+      * a namespace {@code jmx.context//}, as described in
+      * {@link ClientContext#newContextForwarder}.  This namespace is needed
+      * for {@link ClientContext#withContext ClientContext.withContext} to
+      * function correctly.</p>
+      *
+      * <p>Not all connector servers will understand this attribute, but the
+      * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
+      * RMI Connector Server} does.  For a connector server that understands
+      * this attribute, the default value is {@code "true"}.</p>
+      *
+      * @since 1.7
+      */
+     public static final String CONTEXT_FORWARDER =
+         "jmx.remote.context.forwarder";
+
+     /**
+      * <p>Name of the attribute that specifies whether this connector server
+      * localizes the descriptions in the {@link MBeanInfo} object returned by
+      * {@link MBeanServer#getMBeanInfo MBeanServer.getMBeanInfo}, based on the
+      * locale communicated by the client.</p>
+      *
+      * <p>The value associated with this attribute, if any, must be a string
+      * that is equal to {@code "true"} or {@code "false"}, ignoring case.
+      * If it is {@code "true"}, then the connector server will localize
+      * {@code MBeanInfo} descriptions as specified in {@link
+      * ClientContext#newLocalizeMBeanInfoForwarder}.</p>
+      *
+      * <p>Not all connector servers will understand this attribute, but the
+      * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
+      * RMI Connector Server} does.  For a connector server that understands
+      * this attribute, the default value is {@code "false"}.</p>
+      *
+      * <p>Because localization requires the client to be able to communicate
+      * its locale, it does not make sense to specify this attribute as
+      * {@code "true"} if {@link #CONTEXT_FORWARDER} is not also {@code "true"}.
+      * For a connector server that understands these attributes, specifying
+      * this inconsistent combination will result in an {@link
+      * IllegalArgumentException}.</p>
+      *
+      * @since 1.7
+      */
+     public static final String LOCALIZE_MBEAN_INFO_FORWARDER =
+        "jmx.remote.localize.mbean.info";
+
+     /**
+      * <p>Name of the attribute that specifies whether this connector
       * server simulates the existence of the {@link EventClientDelegate}
       * MBean. The value associated with this attribute, if any, must
       * be a string that is equal to {@code "true"} or {@code "false"},
@@ -155,7 +206,7 @@
      * to, or null if it is not yet attached to an MBean server.
      *
      * @see #setMBeanServerForwarder
-     * @see #getSystemMBeanServer
+     * @see #getSystemMBeanServerForwarder
      */
     public synchronized MBeanServer getMBeanServer() {
         return userMBeanServer;
@@ -176,30 +227,36 @@
      * this method, the first occurrence in the chain of an object that is
      * {@linkplain Object#equals equal} to {@code mbsf} will have been
      * removed.</p>
+     *
      * @param mbsf the forwarder to remove
+     *
      * @throws NoSuchElementException if there is no occurrence of {@code mbsf}
      * in the chain.
-     * @throws IllegalArgumentException if {@code mbsf} is null.
+     * @throws IllegalArgumentException if {@code mbsf} is null or is the
+     * {@linkplain #getSystemMBeanServerForwarder() system forwarder}.
+     *
+     * @since 1.7
      */
     public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) {
         if (mbsf == null)
             throw new IllegalArgumentException("Invalid null argument: mbsf");
+        if (systemMBeanServerForwarder.equals(mbsf))
+            throw new IllegalArgumentException("Cannot remove system forwarder");
 
-        MBeanServerForwarder prev = null;
-        MBeanServer curr = systemMBeanServer;
-        while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) {
-            prev = (MBeanServerForwarder) curr;
+        MBeanServerForwarder prev = systemMBeanServerForwarder;
+        MBeanServer curr;
+        while (true) {
             curr = prev.getMBeanServer();
+            if (mbsf.equals(curr))
+                break;
+            if (curr instanceof MBeanServerForwarder)
+                prev = (MBeanServerForwarder) curr;
+            else
+                throw new NoSuchElementException("MBeanServerForwarder not in chain");
         }
-        if (!(curr instanceof MBeanServerForwarder))
-            throw new NoSuchElementException("MBeanServerForwarder not in chain");
-        MBeanServerForwarder deleted = (MBeanServerForwarder) curr;
-        MBeanServer next = deleted.getMBeanServer();
-        if (prev != null)
-            prev.setMBeanServer(next);
-        if (systemMBeanServer == deleted)
-            systemMBeanServer = next;
-        if (userMBeanServer == deleted)
+        MBeanServer next = mbsf.getMBeanServer();
+        prev.setMBeanServer(next);
+        if (userMBeanServer == mbsf)
             userMBeanServer = next;
     }
 
@@ -209,66 +266,63 @@
      * the systemMBeanServer and userMBeanServer field declarations.
      */
     private void insertUserMBeanServer(MBeanServer mbs) {
-        MBeanServerForwarder lastSystemMBSF = null;
-        for (MBeanServer mbsi = systemMBeanServer;
-             mbsi != userMBeanServer;
-             mbsi = lastSystemMBSF.getMBeanServer()) {
+        MBeanServerForwarder lastSystemMBSF = systemMBeanServerForwarder;
+        while (true) {
+            MBeanServer mbsi = lastSystemMBSF.getMBeanServer();
+            if (mbsi == userMBeanServer)
+                break;
             lastSystemMBSF = (MBeanServerForwarder) mbsi;
         }
         userMBeanServer = mbs;
-        if (lastSystemMBSF == null)
-            systemMBeanServer = mbs;
-        else
-            lastSystemMBSF.setMBeanServer(mbs);
+        lastSystemMBSF.setMBeanServer(mbs);
     }
 
     /**
      * <p>Returns the first item in the chain of system and then user
-     * forwarders.  In the simplest case, a {@code JMXConnectorServer}
-     * is connected directly to an {@code MBeanServer}.  But there can
-     * also be a chain of {@link MBeanServerForwarder}s between the two.
-     * This chain consists of two sub-chains: first the <em>system chain</em>
-     * and then the <em>user chain</em>.  Incoming requests are given to the
-     * first forwarder in the system chain.  Each forwarder can handle
-     * a request itself, or more usually forward it to the next forwarder,
-     * perhaps with some extra behavior such as logging or security
-     * checking before or after the forwarding.  The last forwarder in
-     * the system chain is followed by the first forwarder in the user
-     * chain.</p>
+     * forwarders.  There is a chain of {@link MBeanServerForwarder}s between
+     * a {@code JMXConnectorServer} and its {@code MBeanServer}.  This chain
+     * consists of two sub-chains: first the <em>system chain</em> and then
+     * the <em>user chain</em>.  Incoming requests are given to the first
+     * forwarder in the system chain.  Each forwarder can handle a request
+     * itself, or more usually forward it to the next forwarder, perhaps with
+     * some extra behavior such as logging or security checking before or after
+     * the forwarding.  The last forwarder in the system chain is followed by
+     * the first forwarder in the user chain.</p>
      *
-     * <p>The <em>system chain</em> is usually
-     * defined by a connector server based on the environment Map;
-     * see {@link JMXConnectorServerFactory#newJMXConnectorServer}.  Allowing the
-     * connector server to define its forwarders in this way ensures that
-     * they are in the correct order - some forwarders need to be inserted
-     * before others for correct behavior.  It is possible to modify the
-     * system chain, for example using {@link #setSystemMBeanServerForwarder} or
-     * {@link #removeMBeanServerForwarder}, but in that case the system
-     * chain is no longer guaranteed to be correct.</p>
+     * <p>The object returned by this method is the first forwarder in the
+     * system chain.  For a given {@code JMXConnectorServer}, this method
+     * always returns the same object, which simply forwards every request
+     * to the next object in the chain.</p>
+     *
+     * <p>Not all connector servers support a system chain of forwarders,
+     * although the standard {@linkplain
+     * javax.management.remote.rmi.RMIConnectorServer RMI connector
+     * server} does.  For those that do not, this method will throw {@code
+     * UnsupportedOperationException}.  All
+     * connector servers do support a user chain of forwarders.</p>
+     *
+     * <p>The <em>system chain</em> is usually defined by a
+     * connector server based on the environment Map; see {@link
+     * JMXConnectorServerFactory#newJMXConnectorServer
+     * JMXConnectorServerFactory.newJMXConnectorServer}.  Allowing
+     * the connector server to define its forwarders in this way
+     * ensures that they are in the correct order - some forwarders
+     * need to be inserted before others for correct behavior.  It is
+     * possible to modify the system chain, for example using {@code
+     * connectorServer.getSystemMBeanServerForwarder().setMBeanServer(mbsf)} or
+     * {@link #removeMBeanServerForwarder removeMBeanServerForwarder}, but in
+     * that case the system chain is no longer guaranteed to be correct.</p>
      *
      * <p>The <em>user chain</em> is defined by calling {@link
-     * #setMBeanServerForwarder} to insert forwarders at the head of the user
-     * chain.</p>
-     *
-     * <p>If there are no forwarders in either chain, then both
-     * {@link #getMBeanServer()} and {@code getSystemMBeanServer()} will
-     * return the {@code MBeanServer} for this connector server.  If there
-     * are forwarders in the user chain but not the system chain, then
-     * both methods will return the first forwarder in the user chain.
-     * If there are forwarders in the system chain but not the user chain,
-     * then {@code getSystemMBeanServer()} will return the first forwarder
-     * in the system chain, and {@code getMBeanServer()} will return the
-     * {@code MBeanServer} for this connector server.  Finally, if there
-     * are forwarders in each chain then {@code getSystemMBeanServer()}
-     * will return the first forwarder in the system chain, and {@code
-     * getMBeanServer()} will return the first forwarder in the user chain.</p>
+     * #setMBeanServerForwarder setMBeanServerForwarder} to insert forwarders
+     * at the head of the user chain.</p>
      *
      * <p>This code illustrates how the chains can be traversed:</p>
      *
      * <pre>
      * JMXConnectorServer cs;
      * System.out.println("system chain:");
-     * MBeanServer mbs = cs.getSystemMBeanServer();
+     * MBeanServer mbs = cs.getSystemMBeanServerForwarder();
      * while (true) {
      *     if (mbs == cs.getMBeanServer())
      *         System.out.println("user chain:");
@@ -281,65 +335,40 @@
      * System.out.println("--MBean Server");
      * </pre>
      *
+     * <h4>Note for connector server implementors</h4>
+     *
+     * <p>Existing connector server implementations can be updated to support
+     * a system chain of forwarders as follows:</p>
+     *
+     * <ul>
+     * <li><p>Override the {@link #supportsSystemMBeanServerForwarder()}
+     * method so that it returns true.</p>
+     *
+     * <li><p>Call {@link #installStandardForwarders} from the constructor of
+     * the connector server.</p>
+     *
+     * <li><p>Direct incoming requests to the result of {@link
+     * #getSystemMBeanServerForwarder()} instead of the result of {@link
+     * #getMBeanServer()}.</p>
+     * </ul>
+     *
      * @return the first item in the system chain of forwarders.
      *
-     * @see #setSystemMBeanServerForwarder
+     * @throws UnsupportedOperationException if {@link
+     * #supportsSystemMBeanServerForwarder} returns false.
+     *
+     * @see #supportsSystemMBeanServerForwarder
+     * @see #setMBeanServerForwarder
+     *
+     * @since 1.7
      */
-    public synchronized MBeanServer getSystemMBeanServer() {
-        return systemMBeanServer;
-    }
-
-    /**
-     * <p>Inserts an object that intercepts requests for the MBean server
-     * that arrive through this connector server.  This object will be
-     * supplied as the <code>MBeanServer</code> for any new connection
-     * created by this connector server.  Existing connections are
-     * unaffected.</p>
-     *
-     * <p>This method can be called more than once with different
-     * {@link MBeanServerForwarder} objects.  The result is a chain
-     * of forwarders.  The last forwarder added is the first in the chain.</p>
-     *
-     * <p>This method modifies the system chain of {@link MBeanServerForwarder}s.
-     * Usually user code should change the user chain instead, via
-     * {@link #setMBeanServerForwarder}.</p>
-     *
-     * <p>Not all connector servers support a system chain of forwarders.
-     * Calling this method on a connector server that does not will produce an
-     * {@link UnsupportedOperationException}.</p>
-     *
-     * <p>Suppose {@code mbs} is the result of {@link #getSystemMBeanServer()}
-     * before calling this method.  If {@code mbs} is not null, then
-     * {@code mbsf.setMBeanServer(mbs)} will be called.  If doing so
-     * produces an exception, this method throws the same exception without
-     * any other effect.  If {@code mbs} is null, or if the call to
-     * {@code mbsf.setMBeanServer(mbs)} succeeds, then this method will
-     * return normally and {@code getSystemMBeanServer()} will then return
-     * {@code mbsf}.</p>
-     *
-     * <p>The result of {@link #getMBeanServer()} is unchanged by this method.</p>
-     *
-     * @param mbsf the new <code>MBeanServerForwarder</code>.
-     *
-     * @throws IllegalArgumentException if the call to {@link
-     * MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails
-     * with <code>IllegalArgumentException</code>, or if
-     * <code>mbsf</code> is null.
-     *
-     * @throws UnsupportedOperationException if
-     * {@link #supportsSystemMBeanServerForwarder} returns false.
-     *
-     * @see #getSystemMBeanServer()
-     */
-    public synchronized void setSystemMBeanServerForwarder(
-            MBeanServerForwarder mbsf) {
-        if (mbsf == null)
-            throw new IllegalArgumentException("Invalid null argument: mbsf");
-        mustSupportSystemMBSF();
-
-        if (systemMBeanServer != null)
-            mbsf.setMBeanServer(systemMBeanServer);
-        systemMBeanServer = mbsf;
+    public MBeanServerForwarder getSystemMBeanServerForwarder() {
+        if (!supportsSystemMBeanServerForwarder()) {
+            throw new UnsupportedOperationException(
+                    "System MBeanServerForwarder not supported by this " +
+                    "connector server");
+        }
+        return systemMBeanServerForwarder;
     }
 
     /**
@@ -350,19 +379,13 @@
      *
      * @return true if this connector server supports the system chain of
      * forwarders.
+     *
+     * @since 1.7
      */
     public boolean supportsSystemMBeanServerForwarder() {
         return false;
     }
 
-    private void mustSupportSystemMBSF() {
-        if (!supportsSystemMBeanServerForwarder()) {
-            throw new UnsupportedOperationException(
-                    "System MBeanServerForwarder not supported by this " +
-                    "connector server");
-        }
-    }
-
     /**
      * <p>Install {@link MBeanServerForwarder}s in the system chain
      * based on the attributes in the given {@code Map}.  A connector
@@ -374,34 +397,90 @@
      * <ul>
      *
      * <li>If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is
-     * present with the value {@code "true"}, then a forwarder with the
-     * functionality of {@link EventClientDelegate#newForwarder} is inserted
-     * at the start of the system chain.</li>
+     * present with the value {@code "true"}, then a forwarder
+     * equivalent to {@link EventClientDelegate#newForwarder
+     * EventClientDelegate.newForwarder}{@code (sysMBSF.getMBeanServer(),
+     * sysMBSF)} is inserted at the start of the system chain,
+     * where {@code sysMBSF} is the object returned by {@link
+     * #getSystemMBeanServerForwarder()}. </li>
+     *
+     * <li>If {@link #LOCALIZE_MBEAN_INFO_FORWARDER} is present with the
+     * value {@code "true"}, then a forwarder equivalent to
+     * {@link ClientContext#newLocalizeMBeanInfoForwarder
+     * ClientContext.newLocalizeMBeanInfoForwarder}{@code
+     * (sysMBSF.getMBeanServer())} is inserted at the start of the system
+     * chain.</li>
+     *
+     * <li>If {@link #CONTEXT_FORWARDER} is absent, or is present with
+     * the value {@code "true"}, then a forwarder equivalent to
+     * {@link ClientContext#newContextForwarder
+     * ClientContext.newContextForwarder}{@code (sysMSBF.getMBeanServer(),
+     * sysMBSF)} is inserted at the tart of the system chain.</li>
      *
      * </ul>
      *
-     * <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER}, if the
-     * attribute is absent from the {@code Map} and a system property
-     * of the same name is defined, then the value of the system
-     * property is used as if it were in the {@code Map}.
+     * <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER} and {@code
+     * CONTEXT_FORWARDER}, if the attribute is absent from the {@code
+     * Map} and a system property of the same name is defined, then
+     * the value of the system property is used as if it were in the
+     * {@code Map}.
+     *
+     * <p>Since each forwarder is inserted at the start of the chain,
+     * the final order of the forwarders is the <b>reverse</b> of the order
+     * above.  This is important, because the {@code
+     * LOCALIZE_MBEAN_INFO_FORWARDER} can only work if the {@code
+     * CONTEXT_FORWARDER} has already installed the remote client's locale
+     * in the {@linkplain ClientContext#getContext context} of the current
+     * thread.</p>
      *
      * <p>Attributes in {@code env} that are not listed above are ignored
      * by this method.</p>
      *
      * @throws UnsupportedOperationException if {@link
      * #supportsSystemMBeanServerForwarder} is false.
+     *
+     * @throws IllegalArgumentException if the relevant attributes in {@code env} are
+     * inconsistent, for example if {@link #LOCALIZE_MBEAN_INFO_FORWARDER} is
+     * {@code "true"} but {@link #CONTEXT_FORWARDER} is {@code "false"}; or
+     * if one of the attributes has an illegal value.
+     *
+     * @since 1.7
      */
     protected void installStandardForwarders(Map<String, ?> env) {
-        mustSupportSystemMBSF();
+        MBeanServerForwarder sysMBSF = getSystemMBeanServerForwarder();
 
         // Remember that forwarders must be added in reverse order!
 
         boolean ecd = EnvHelp.computeBooleanFromString(
                 env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true);
+        boolean localize = EnvHelp.computeBooleanFromString(
+                env, LOCALIZE_MBEAN_INFO_FORWARDER, false, false);
+        boolean context = EnvHelp.computeBooleanFromString(
+                env, CONTEXT_FORWARDER, false, true);
+
+        if (localize && !context) {
+            throw new IllegalArgumentException(
+                    "Inconsistent environment parameters: " +
+                    LOCALIZE_MBEAN_INFO_FORWARDER + "=\"true\" requires " +
+                    CONTEXT_FORWARDER + "=\"true\"");
+        }
 
         if (ecd) {
-            MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
-            setSystemMBeanServerForwarder(mbsf);
+            MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(
+                    sysMBSF.getMBeanServer(), sysMBSF);
+            sysMBSF.setMBeanServer(mbsf);
+        }
+
+        if (localize) {
+            MBeanServerForwarder mbsf = ClientContext.newLocalizeMBeanInfoForwarder(
+                    sysMBSF.getMBeanServer());
+            sysMBSF.setMBeanServer(mbsf);
+        }
+
+        if (context) {
+            MBeanServerForwarder mbsf = ClientContext.newContextForwarder(
+                    sysMBSF.getMBeanServer(), sysMBSF);
+            sysMBSF.setMBeanServer(mbsf);
         }
     }
 
@@ -473,6 +552,7 @@
      *
      * @return the array of possible notifications.
      */
+    @Override
     public MBeanNotificationInfo[] getNotificationInfo() {
         final String[] types = {
             JMXConnectionNotification.OPENED,
@@ -684,30 +764,29 @@
      * Fields describing the chains of forwarders (MBeanServerForwarders).
      * In the general case, the forwarders look something like this:
      *
-     * systemMBeanServer          userMBeanServer
-     * |                          |
-     * v                          v
-     * mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
+     *                                        userMBeanServer
+     *                                        |
+     *                                        v
+     * systemMBeanServerForwarder -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
      *
      * Here, each mbsfi is an MBeanServerForwarder, and the arrows
      * illustrate its getMBeanServer() method.  The last MBeanServerForwarder
      * can point to an MBeanServer that is not instanceof MBeanServerForwarder,
      * here mbs.
      *
-     * Initially, the chain can be empty if this JMXConnectorServer was
-     * constructed without an MBeanServer.  In this case, both systemMBS
-     * and userMBS will be null.  If there is initially an MBeanServer,
-     * then both systemMBS and userMBS will point to it.
+     * The system chain is never empty because it always has at least
+     * systemMBeanServerForwarder.  Initially, the user chain can be empty if
+     * this JMXConnectorServer was constructed without an MBeanServer.  In
+     * this case, userMBS will be null.  If there is initially an MBeanServer,
+     * userMBS will point to it.
      *
-     * Whenever userMBS is changed, the system chain must be updated. If there
-     * are forwarders in the system chain (between systemMBS and userMBS in the
-     * picture above), then the last one must point to the old value of userMBS
-     * (possibly null). It must be updated to point to the new value. If there
-     * are no forwarders in the system chain, then systemMBS must be updated to
-     * the new value of userMBS. The invariant is that starting from systemMBS
-     * and repeatedly calling MBSF.getMBeanServer() you will end up at
-     * userMBS.  The implication is that you will not see any MBeanServer
-     * object on the way that is not also an MBeanServerForwarder.
+     * Whenever userMBS is changed, the system chain must be updated.  Before
+     * the update, the last forwarder in the system chain points to the old
+     * value of userMBS (possibly null).  It must be updated to point to
+     * the new value. The invariant is that starting from systemMBSF and
+     * repeatedly calling MBSF.getMBeanServer() you will end up at userMBS.
+     * The implication is that you will not see any MBeanServer object on the
+     * way that is not also an MBeanServerForwarder.
      *
      * The method insertUserMBeanServer contains the logic to change userMBS
      * and adjust the system chain appropriately.
@@ -716,7 +795,7 @@
      * MBeanServer, then userMBS becomes that MBeanServer, and the system
      * chain must be updated as just described.
      *
-     * When systemMBS is updated, there is no effect on userMBS. The system
+     * When systemMBSF is updated, there is no effect on userMBS. The system
      * chain may contain forwarders even though the user chain is empty
      * (there is no MBeanServer). In that case an attempt to forward an
      * incoming request through the chain will fall off the end and fail with a
@@ -726,7 +805,8 @@
 
     private MBeanServer userMBeanServer;
 
-    private MBeanServer systemMBeanServer;
+    private final MBeanServerForwarder systemMBeanServerForwarder =
+            new IdentityMBeanServerForwarder();
 
     /**
      * The name used to registered this server in an MBeanServer.
--- a/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java	Thu Nov 13 11:15:01 2008 -0800
@@ -132,7 +132,7 @@
      *
      * <p>A connector server may support two chains of forwarders,
      * a system chain and a user chain.  See {@link
-     * JMXConnectorServer#setSystemMBeanServerForwarder} for details.</p>
+     * JMXConnectorServer#getSystemMBeanServerForwarder} for details.</p>
      *
      * @param mbsf the new <code>MBeanServerForwarder</code>.
      *
@@ -141,7 +141,7 @@
      * with <code>IllegalArgumentException</code>.  This includes the
      * case where <code>mbsf</code> is null.
      *
-     * @see JMXConnectorServer#setSystemMBeanServerForwarder
+     * @see JMXConnectorServer#getSystemMBeanServerForwarder
      */
     public void setMBeanServerForwarder(MBeanServerForwarder mbsf);
 
--- a/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Thu Nov 13 11:15:01 2008 -0800
@@ -134,14 +134,14 @@
 public class RMIConnector implements JMXConnector, Serializable, JMXAddressable {
 
     private static final ClassLogger logger =
-        new ClassLogger("javax.management.remote.rmi", "RMIConnector");
+            new ClassLogger("javax.management.remote.rmi", "RMIConnector");
 
     private static final long serialVersionUID = 817323035842634473L;
 
     private RMIConnector(RMIServer rmiServer, JMXServiceURL address,
-                         Map<String, ?> environment) {
+            Map<String, ?> environment) {
         if (rmiServer == null && address == null) throw new
-          IllegalArgumentException("rmiServer and jmxServiceURL both null");
+                IllegalArgumentException("rmiServer and jmxServiceURL both null");
 
         initTransients();
 
@@ -257,7 +257,7 @@
     }
 
     public synchronized void connect(Map<String,?> environment)
-            throws IOException {
+    throws IOException {
         final boolean tracing = logger.traceOn();
         String        idstr   = (tracing?"["+this.toString()+"]":null);
 
@@ -274,8 +274,8 @@
             if (tracing) logger.trace("connect",idstr + " connecting...");
 
             final Map<String, Object> usemap =
-                new HashMap<String, Object>((this.env==null) ?
-                    Collections.<String, Object>emptyMap() : this.env);
+                    new HashMap<String, Object>((this.env==null) ?
+                        Collections.<String, Object>emptyMap() : this.env);
 
 
             if (environment != null) {
@@ -315,7 +315,7 @@
             defaultClassLoader = EnvHelp.resolveClientClassLoader(usemap);
 
             usemap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,
-                       defaultClassLoader);
+                    defaultClassLoader);
 
             rmiNotifClient = new RMINotifClient(defaultClassLoader, usemap);
 
@@ -333,12 +333,12 @@
             eventServiceEnabled = EnvHelp.eventServiceEnabled(env);
 
             Notification connectedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.OPENED,
-                                              this,
-                                              connectionId,
-                                              clientNotifSeqNo++,
-                                              "Successful connection",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.OPENED,
+                    this,
+                    connectionId,
+                    clientNotifSeqNo++,
+                    "Successful connection",
+                    null);
             sendNotification(connectedNotif);
 
             // whether or not event service
@@ -363,7 +363,7 @@
         if (terminated || !connected) {
             if (logger.traceOn())
                 logger.trace("getConnectionId","["+this.toString()+
-                      "] not connected.");
+                        "] not connected.");
 
             throw new IOException("Not connected");
         }
@@ -374,23 +374,23 @@
     }
 
     public synchronized MBeanServerConnection getMBeanServerConnection()
-            throws IOException {
+    throws IOException {
         return getMBeanServerConnection(null);
     }
 
     public synchronized MBeanServerConnection
-        getMBeanServerConnection(Subject delegationSubject)
+            getMBeanServerConnection(Subject delegationSubject)
             throws IOException {
 
         if (terminated) {
             if (logger.traceOn())
                 logger.trace("getMBeanServerConnection","[" + this.toString() +
-                      "] already closed.");
+                        "] already closed.");
             throw new IOException("Connection closed");
         } else if (!connected) {
             if (logger.traceOn())
                 logger.trace("getMBeanServerConnection","[" + this.toString() +
-                      "] is not connected.");
+                        "] is not connected.");
             throw new IOException("Not connected");
         }
 
@@ -405,7 +405,7 @@
                     rmbsc, EventClientDelegateMBean.OBJECT_NAME,
                     EventClientDelegateMBean.class);
             EventClient ec = new EventClient(ecd, null, defaultExecutor(), null,
-                    EventClient.DEFAULT_LEASE_TIMEOUT);
+                    EventClient.DEFAULT_REQUESTED_LEASE_TIME);
 
             rmbsc = EventConnection.Factory.make(rmbsc, ec);
             ec.addEventClientListener(
@@ -433,17 +433,17 @@
     }
 
     public void
-        addConnectionNotificationListener(NotificationListener listener,
-                                          NotificationFilter filter,
-                                          Object handback) {
+            addConnectionNotificationListener(NotificationListener listener,
+            NotificationFilter filter,
+            Object handback) {
         if (listener == null)
             throw new NullPointerException("listener");
         connectionBroadcaster.addNotificationListener(listener, filter,
-                                                      handback);
+                handback);
     }
 
     public void
-        removeConnectionNotificationListener(NotificationListener listener)
+            removeConnectionNotificationListener(NotificationListener listener)
             throws ListenerNotFoundException {
         if (listener == null)
             throw new NullPointerException("listener");
@@ -451,14 +451,14 @@
     }
 
     public void
-        removeConnectionNotificationListener(NotificationListener listener,
-                                             NotificationFilter filter,
-                                             Object handback)
+            removeConnectionNotificationListener(NotificationListener listener,
+            NotificationFilter filter,
+            Object handback)
             throws ListenerNotFoundException {
         if (listener == null)
             throw new NullPointerException("listener");
         connectionBroadcaster.removeNotificationListener(listener, filter,
-                                                         handback);
+                handback);
     }
 
     private void sendNotification(Notification n) {
@@ -526,11 +526,11 @@
             try {
                 rmiNotifClient.terminate();
                 if (tracing) logger.trace("close",idstr +
-                                    " RMI Notification client terminated.");
+                        " RMI Notification client terminated.");
             } catch (RuntimeException x) {
                 closeException = x;
                 if (tracing) logger.trace("close",idstr +
-                         " Failed to terminate RMI Notification client: " + x);
+                        " Failed to terminate RMI Notification client: " + x);
                 if (debug) logger.debug("close",x);
             }
         }
@@ -544,7 +544,7 @@
             } catch (IOException e) {
                 closeException = e;
                 if (tracing) logger.trace("close",idstr +
-                                          " Failed to close RMIServer: " + e);
+                        " Failed to close RMIServer: " + e);
                 if (debug) logger.debug("close",e);
             }
         }
@@ -559,12 +559,12 @@
 
         if (savedConnectionId != null) {
             Notification closedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.CLOSED,
-                                              this,
-                                              savedConnectionId,
-                                              clientNotifSeqNo++,
-                                              "Client has been closed",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.CLOSED,
+                    this,
+                    savedConnectionId,
+                    clientNotifSeqNo++,
+                    "Client has been closed",
+                    null);
             sendNotification(closedNotif);
         }
 
@@ -572,13 +572,13 @@
         //
         if (closeException != null) {
             if (tracing) logger.trace("close",idstr + " failed to close: " +
-                               closeException);
+                    closeException);
             if (closeException instanceof IOException)
                 throw (IOException) closeException;
             if (closeException instanceof RuntimeException)
                 throw (RuntimeException) closeException;
             final IOException x =
-                new IOException("Failed to close: " + closeException);
+                    new IOException("Failed to close: " + closeException);
             throw EnvHelp.initCause(x,closeException);
         }
     }
@@ -593,7 +593,7 @@
         final boolean debug = logger.debugOn();
         if (debug)
             logger.debug("addListenerWithSubject",
-                         "(ObjectName,MarshalledObject,Subject)");
+                    "(ObjectName,MarshalledObject,Subject)");
 
         final ObjectName[] names = new ObjectName[] {name};
         final MarshalledObject<NotificationFilter>[] filters =
@@ -603,11 +603,11 @@
         };
 
         final Integer[] listenerIDs =
-            addListenersWithSubjects(names,filters,delegationSubjects,
-                                          reconnect);
+                addListenersWithSubjects(names,filters,delegationSubjects,
+                reconnect);
 
         if (debug) logger.debug("addListenerWithSubject","listenerID="
-                                + listenerIDs[0]);
+                + listenerIDs[0]);
         return listenerIDs[0];
     }
 
@@ -620,24 +620,24 @@
 
         final boolean debug = logger.debugOn();
         if (debug)
-        logger.debug("addListenersWithSubjects",
-                         "(ObjectName[],MarshalledObject[],Subject[])");
+            logger.debug("addListenersWithSubjects",
+                    "(ObjectName[],MarshalledObject[],Subject[])");
 
         final ClassLoader old = pushDefaultClassLoader();
         Integer[] listenerIDs = null;
 
         try {
             listenerIDs = connection.addNotificationListeners(names,
-                                                              filters,
-                                                          delegationSubjects);
+                    filters,
+                    delegationSubjects);
         } catch (NoSuchObjectException noe) {
             // maybe reconnect
             if (reconnect) {
                 communicatorAdmin.gotIOException(noe);
 
                 listenerIDs = connection.addNotificationListeners(names,
-                                                       filters,
-                                                       delegationSubjects);
+                        filters,
+                        delegationSubjects);
             } else {
                 throw noe;
             }
@@ -671,65 +671,65 @@
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name)
+                ObjectName name)
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("createMBean(String,ObjectName)",
-                             "className=" + className + ", name=" +
-                             name);
+                        "className=" + className + ", name=" +
+                        name);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             delegationSubject);
+                        name,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             delegationSubject);
+                        name,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          ObjectName loaderName)
-            throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       InstanceNotFoundException,
-                       IOException {
+                ObjectName name,
+                ObjectName loaderName)
+                throws ReflectionException,
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                InstanceNotFoundException,
+                IOException {
 
             if (logger.debugOn())
                 logger.debug("createMBean(String,ObjectName,ObjectName)",
-                      "className=" + className + ", name="
-                      + name + ", loaderName="
-                      + loaderName + ")");
+                        "className=" + className + ", name="
+                        + name + ", loaderName="
+                        + loaderName + ")");
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        delegationSubject);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        delegationSubject);
 
             } finally {
                 popDefaultClassLoader(old);
@@ -737,90 +737,90 @@
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          Object params[],
-                                          String signature[])
+                ObjectName name,
+                Object params[],
+                String signature[])
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                IOException {
             if (logger.debugOn())
-               logger.debug("createMBean(String,ObjectName,Object[],String[])",
-                      "className=" + className + ", name="
-                      + name + ", params="
-                      + objects(params) + ", signature="
-                      + strings(signature));
+                logger.debug("createMBean(String,ObjectName,Object[],String[])",
+                        "className=" + className + ", name="
+                        + name + ", params="
+                        + objects(params) + ", signature="
+                        + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          ObjectName loaderName,
-                                          Object params[],
-                                          String signature[])
+                ObjectName name,
+                ObjectName loaderName,
+                Object params[],
+                String signature[])
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       InstanceNotFoundException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                InstanceNotFoundException,
+                IOException {
             if (logger.debugOn()) logger.debug(
-                "createMBean(String,ObjectName,ObjectName,Object[],String[])",
-                "className=" + className + ", name=" + name + ", loaderName="
-                + loaderName + ", params=" + objects(params)
-                + ", signature=" + strings(signature));
+                    "createMBean(String,ObjectName,ObjectName,Object[],String[])",
+                    "className=" + className + ", name=" + name + ", loaderName="
+                    + loaderName + ", params=" + objects(params)
+                    + ", signature=" + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void unregisterMBean(ObjectName name)
-                throws InstanceNotFoundException,
-                       MBeanRegistrationException,
-                       IOException {
+        throws InstanceNotFoundException,
+                MBeanRegistrationException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("unregisterMBean", "name=" + name);
 
@@ -837,8 +837,8 @@
         }
 
         public ObjectInstance getObjectInstance(ObjectName name)
-                throws InstanceNotFoundException,
-                       IOException {
+        throws InstanceNotFoundException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("getObjectInstance", "name=" + name);
 
@@ -855,10 +855,10 @@
         }
 
         public Set<ObjectInstance> queryMBeans(ObjectName name,
-                                               QueryExp query)
-            throws IOException {
+                QueryExp query)
+                throws IOException {
             if (logger.debugOn()) logger.debug("queryMBeans",
-                                   "name=" + name + ", query=" + query);
+                    "name=" + name + ", query=" + query);
 
             final MarshalledObject<QueryExp> sQuery =
                     new MarshalledObject<QueryExp>(query);
@@ -875,10 +875,10 @@
         }
 
         public Set<ObjectName> queryNames(ObjectName name,
-                                          QueryExp query)
+                QueryExp query)
                 throws IOException {
             if (logger.debugOn()) logger.debug("queryNames",
-                                   "name=" + name + ", query=" + query);
+                    "name=" + name + ", query=" + query);
 
             final MarshalledObject<QueryExp> sQuery =
                     new MarshalledObject<QueryExp>(query);
@@ -895,7 +895,7 @@
         }
 
         public boolean isRegistered(ObjectName name)
-                throws IOException {
+        throws IOException {
             if (logger.debugOn())
                 logger.debug("isRegistered", "name=" + name);
 
@@ -912,7 +912,7 @@
         }
 
         public Integer getMBeanCount()
-                throws IOException {
+        throws IOException {
             if (logger.debugOn()) logger.debug("getMBeanCount", "");
 
             final ClassLoader old = pushDefaultClassLoader();
@@ -928,53 +928,53 @@
         }
 
         public Object getAttribute(ObjectName name,
-                                   String attribute)
+                String attribute)
                 throws MBeanException,
-                       AttributeNotFoundException,
-                       InstanceNotFoundException,
-                       ReflectionException,
-                       IOException {
+                AttributeNotFoundException,
+                InstanceNotFoundException,
+                ReflectionException,
+                IOException {
             if (logger.debugOn()) logger.debug("getAttribute",
-                                   "name=" + name + ", attribute="
-                                   + attribute);
+                    "name=" + name + ", attribute="
+                    + attribute);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.getAttribute(name,
-                                              attribute,
-                                              delegationSubject);
+                        attribute,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.getAttribute(name,
-                                              attribute,
-                                              delegationSubject);
+                        attribute,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public AttributeList getAttributes(ObjectName name,
-                                           String[] attributes)
+                String[] attributes)
                 throws InstanceNotFoundException,
-                       ReflectionException,
-                       IOException {
+                ReflectionException,
+                IOException {
             if (logger.debugOn()) logger.debug("getAttributes",
-                                   "name=" + name + ", attributes="
-                                   + strings(attributes));
+                    "name=" + name + ", attributes="
+                    + strings(attributes));
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.getAttributes(name,
-                                               attributes,
-                                               delegationSubject);
+                        attributes,
+                        delegationSubject);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.getAttributes(name,
-                                               attributes,
-                                               delegationSubject);
+                        attributes,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -982,20 +982,20 @@
 
 
         public void setAttribute(ObjectName name,
-                                 Attribute attribute)
-            throws InstanceNotFoundException,
-                   AttributeNotFoundException,
-                   InvalidAttributeValueException,
-                   MBeanException,
-                   ReflectionException,
-                   IOException {
+                Attribute attribute)
+                throws InstanceNotFoundException,
+                AttributeNotFoundException,
+                InvalidAttributeValueException,
+                MBeanException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("setAttribute",
-                                   "name=" + name + ", attribute="
-                                   + attribute);
+                    "name=" + name + ", attribute="
+                    + attribute);
 
             final MarshalledObject<Attribute> sAttribute =
-                new MarshalledObject<Attribute>(attribute);
+                    new MarshalledObject<Attribute>(attribute);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.setAttribute(name, sAttribute, delegationSubject);
@@ -1009,28 +1009,28 @@
         }
 
         public AttributeList setAttributes(ObjectName name,
-                                           AttributeList attributes)
-            throws InstanceNotFoundException,
-                   ReflectionException,
-                   IOException {
+                AttributeList attributes)
+                throws InstanceNotFoundException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("setAttributes",
-                                   "name=" + name + ", attributes="
-                                   + attributes);
+                    "name=" + name + ", attributes="
+                    + attributes);
 
             final MarshalledObject<AttributeList> sAttributes =
-                new MarshalledObject<AttributeList>(attributes);
+                    new MarshalledObject<AttributeList>(attributes);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.setAttributes(name,
-                                               sAttributes,
-                                               delegationSubject);
+                        sAttributes,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.setAttributes(name,
-                                               sAttributes,
-                                               delegationSubject);
+                        sAttributes,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1038,37 +1038,37 @@
 
 
         public Object invoke(ObjectName name,
-                             String operationName,
-                             Object params[],
-                             String signature[])
+                String operationName,
+                Object params[],
+                String signature[])
                 throws InstanceNotFoundException,
-                       MBeanException,
-                       ReflectionException,
-                       IOException {
+                MBeanException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("invoke",
-                                   "name=" + name
-                                   + ", operationName=" + operationName
-                                   + ", params=" + objects(params)
-                                   + ", signature=" + strings(signature));
+                    "name=" + name
+                    + ", operationName=" + operationName
+                    + ", params=" + objects(params)
+                    + ", signature=" + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.invoke(name,
-                                        operationName,
-                                        sParams,
-                                        signature,
-                                        delegationSubject);
+                        operationName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.invoke(name,
-                                        operationName,
-                                        sParams,
-                                        signature,
-                                        delegationSubject);
+                        operationName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1076,7 +1076,7 @@
 
 
         public String getDefaultDomain()
-                throws IOException {
+        throws IOException {
             if (logger.debugOn()) logger.debug("getDefaultDomain", "");
 
             final ClassLoader old = pushDefaultClassLoader();
@@ -1107,10 +1107,10 @@
         }
 
         public MBeanInfo getMBeanInfo(ObjectName name)
-                throws InstanceNotFoundException,
-                       IntrospectionException,
-                       ReflectionException,
-                       IOException {
+        throws InstanceNotFoundException,
+                IntrospectionException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("getMBeanInfo", "name=" + name);
             final ClassLoader old = pushDefaultClassLoader();
@@ -1127,41 +1127,41 @@
 
 
         public boolean isInstanceOf(ObjectName name,
-                                    String className)
+                String className)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
             if (logger.debugOn())
                 logger.debug("isInstanceOf", "name=" + name +
-                             ", className=" + className);
+                        ", className=" + className);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.isInstanceOf(name,
-                                              className,
-                                              delegationSubject);
+                        className,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.isInstanceOf(name,
-                                              className,
-                                              delegationSubject);
+                        className,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void addNotificationListener(ObjectName name,
-                                            ObjectName listener,
-                                            NotificationFilter filter,
-                                            Object handback)
+                ObjectName listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
 
             if (logger.debugOn())
                 logger.debug("addNotificationListener" +
-                       "(ObjectName,ObjectName,NotificationFilter,Object)",
-                       "name=" + name + ", listener=" + listener
-                       + ", filter=" + filter + ", handback=" + handback);
+                        "(ObjectName,ObjectName,NotificationFilter,Object)",
+                        "name=" + name + ", listener=" + listener
+                        + ", filter=" + filter + ", handback=" + handback);
 
             final MarshalledObject<NotificationFilter> sFilter =
                     new MarshalledObject<NotificationFilter>(filter);
@@ -1170,64 +1170,64 @@
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.addNotificationListener(name,
-                                                  listener,
-                                                  sFilter,
-                                                  sHandback,
-                                                  delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.addNotificationListener(name,
-                                                  listener,
-                                                  sFilter,
-                                                  sHandback,
-                                                  delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               ObjectName listener)
+                ObjectName listener)
                 throws InstanceNotFoundException,
-                       ListenerNotFoundException,
-                       IOException {
+                ListenerNotFoundException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("removeNotificationListener" +
-                                   "(ObjectName,ObjectName)",
-                                   "name=" + name
-                                   + ", listener=" + listener);
+                    "(ObjectName,ObjectName)",
+                    "name=" + name
+                    + ", listener=" + listener);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     delegationSubject);
+                        listener,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     delegationSubject);
+                        listener,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               ObjectName listener,
-                                               NotificationFilter filter,
-                                               Object handback)
+                ObjectName listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       ListenerNotFoundException,
-                       IOException {
+                ListenerNotFoundException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("removeNotificationListener" +
-                      "(ObjectName,ObjectName,NotificationFilter,Object)",
-                      "name=" + name
-                      + ", listener=" + listener
-                      + ", filter=" + filter
-                      + ", handback=" + handback);
+                        "(ObjectName,ObjectName,NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final MarshalledObject<NotificationFilter> sFilter =
                     new MarshalledObject<NotificationFilter>(filter);
@@ -1236,18 +1236,18 @@
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     sFilter,
-                                                     sHandback,
-                                                     delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     sFilter,
-                                                     sHandback,
-                                                     delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1256,34 +1256,34 @@
         // Specific Notification Handle ----------------------------------
 
         public void addNotificationListener(ObjectName name,
-                                            NotificationListener listener,
-                                            NotificationFilter filter,
-                                            Object handback)
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
 
             final boolean debug = logger.debugOn();
 
             if (debug)
                 logger.debug("addNotificationListener" +
-                             "(ObjectName,NotificationListener,"+
-                             "NotificationFilter,Object)",
-                             "name=" + name
-                             + ", listener=" + listener
-                             + ", filter=" + filter
-                             + ", handback=" + handback);
+                        "(ObjectName,NotificationListener,"+
+                        "NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final Integer listenerID =
-                addListenerWithSubject(name,
-                                       new MarshalledObject<NotificationFilter>(filter),
-                                       delegationSubject,true);
+                    addListenerWithSubject(name,
+                    new MarshalledObject<NotificationFilter>(filter),
+                    delegationSubject,true);
             rmiNotifClient.addNotificationListener(listenerID, name, listener,
-                                                   filter, handback,
-                                                   delegationSubject);
+                    filter, handback,
+                    delegationSubject);
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               NotificationListener listener)
+                NotificationListener listener)
                 throws InstanceNotFoundException,
                 ListenerNotFoundException,
                 IOException {
@@ -1291,28 +1291,28 @@
             final boolean debug = logger.debugOn();
 
             if (debug) logger.debug("removeNotificationListener"+
-                             "(ObjectName,NotificationListener)",
-                             "name=" + name
-                             + ", listener=" + listener);
+                    "(ObjectName,NotificationListener)",
+                    "name=" + name
+                    + ", listener=" + listener);
 
             final Integer[] ret =
-                rmiNotifClient.removeNotificationListener(name, listener);
+                    rmiNotifClient.removeNotificationListener(name, listener);
 
             if (debug) logger.debug("removeNotificationListener",
-                             "listenerIDs=" + objects(ret));
+                    "listenerIDs=" + objects(ret));
 
             final ClassLoader old = pushDefaultClassLoader();
 
             try {
                 connection.removeNotificationListeners(name,
-                                                       ret,
-                                                       delegationSubject);
+                        ret,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(name,
-                                                       ret,
-                                                       delegationSubject);
+                        ret,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1320,41 +1320,41 @@
         }
 
         public void removeNotificationListener(ObjectName name,
-                                           NotificationListener listener,
-                                           NotificationFilter filter,
-                                           Object handback)
-            throws InstanceNotFoundException,
-                   ListenerNotFoundException,
-                   IOException {
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback)
+                throws InstanceNotFoundException,
+                ListenerNotFoundException,
+                IOException {
             final boolean debug = logger.debugOn();
 
             if (debug)
                 logger.debug("removeNotificationListener"+
-                      "(ObjectName,NotificationListener,"+
-                      "NotificationFilter,Object)",
-                      "name=" + name
-                      + ", listener=" + listener
-                      + ", filter=" + filter
-                      + ", handback=" + handback);
+                        "(ObjectName,NotificationListener,"+
+                        "NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final Integer ret =
-                rmiNotifClient.removeNotificationListener(name, listener,
-                                                         filter, handback);
+                    rmiNotifClient.removeNotificationListener(name, listener,
+                    filter, handback);
 
             if (debug) logger.debug("removeNotificationListener",
-                             "listenerID=" + ret);
+                    "listenerID=" + ret);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListeners(name,
-                                                       new Integer[] {ret},
-                                                       delegationSubject);
+                        new Integer[] {ret},
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(name,
-                                                       new Integer[] {ret},
-                                                       delegationSubject);
+                        new Integer[] {ret},
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1369,16 +1369,16 @@
         }
 
         protected NotificationResult fetchNotifs(long clientSequenceNumber,
-                                                 int maxNotifications,
-                                                 long timeout)
+                int maxNotifications,
+                long timeout)
                 throws IOException, ClassNotFoundException {
             IOException org;
 
             while (true) { // used for a successful re-connection
                 try {
                     return connection.fetchNotifications(clientSequenceNumber,
-                                                         maxNotifications,
-                                                         timeout);
+                            maxNotifications,
+                            timeout);
                 } catch (IOException ioe) {
                     org = ioe;
 
@@ -1417,7 +1417,7 @@
                    clear we can do much better.  */
                 if (ume.detail instanceof WriteAbortedException) {
                     WriteAbortedException wae =
-                        (WriteAbortedException) ume.detail;
+                            (WriteAbortedException) ume.detail;
                     if (wae.detail instanceof IOException)
                         throw (IOException) wae.detail;
                 }
@@ -1435,11 +1435,11 @@
         }
 
         protected Integer addListenerForMBeanRemovedNotif()
-                throws IOException, InstanceNotFoundException {
+        throws IOException, InstanceNotFoundException {
             NotificationFilterSupport clientFilter =
-                new NotificationFilterSupport();
+                    new NotificationFilterSupport();
             clientFilter.enableType(
-                MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
+                    MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
             MarshalledObject<NotificationFilter> sFilter =
                 new MarshalledObject<NotificationFilter>(clientFilter);
 
@@ -1451,36 +1451,36 @@
             final Subject[] subjects = new Subject[] {null};
             try {
                 listenerIDs =
-                    connection.addNotificationListeners(names,
-                                                        filters,
-                                                        subjects);
+                        connection.addNotificationListeners(names,
+                        filters,
+                        subjects);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 listenerIDs =
-                    connection.addNotificationListeners(names,
-                                                        filters,
-                                                        subjects);
+                        connection.addNotificationListeners(names,
+                        filters,
+                        subjects);
             }
             return listenerIDs[0];
         }
 
         protected void removeListenerForMBeanRemovedNotif(Integer id)
-                throws IOException, InstanceNotFoundException,
-                       ListenerNotFoundException {
+        throws IOException, InstanceNotFoundException,
+                ListenerNotFoundException {
             try {
                 connection.removeNotificationListeners(
-                                             MBeanServerDelegate.DELEGATE_NAME,
-                                             new Integer[] {id},
-                                             null);
+                        MBeanServerDelegate.DELEGATE_NAME,
+                        new Integer[] {id},
+                        null);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(
-                                             MBeanServerDelegate.DELEGATE_NAME,
-                                             new Integer[] {id},
-                                             null);
+                        MBeanServerDelegate.DELEGATE_NAME,
+                        new Integer[] {id},
+                        null);
             }
 
         }
@@ -1530,7 +1530,7 @@
                     // we should close the connection,
                     // but send a failed notif at first
                     final Notification failedNotif =
-                        new JMXConnectionNotification(
+                            new JMXConnectionNotification(
                             JMXConnectionNotification.FAILED,
                             this,
                             connectionId,
@@ -1559,7 +1559,7 @@
                    will throw a ServerException at client side which wraps this
                    UnmarshalException.
                    No failed notif here.
-                */
+                 */
                 Throwable tt = ((ServerException)ioe).detail;
 
                 if (tt instanceof IOException) {
@@ -1600,11 +1600,11 @@
 
                 for (i=0;i<len;i++) {
                     clis[i] = new ClientListenerInfo(ids[i],
-                                                     names[i],
-                                                     listeners[i],
-                                                     filters[i],
-                                                     handbacks[i],
-                                                     subjects[i]);
+                            names[i],
+                            listeners[i],
+                            filters[i],
+                            handbacks[i],
+                            subjects[i]);
                 }
 
                 rmiNotifClient.postReconnection(clis);
@@ -1623,15 +1623,15 @@
                             false);
 
                     clis[j++] = new ClientListenerInfo(id,
-                                                       names[i],
-                                                       listeners[i],
-                                                       filters[i],
-                                                       handbacks[i],
-                                                       subjects[i]);
+                            names[i],
+                            listeners[i],
+                            filters[i],
+                            handbacks[i],
+                            subjects[i]);
                 } catch (InstanceNotFoundException infe) {
                     logger.warning("reconnectNotificationListeners",
-                                   "Can't reconnect listener for " +
-                                   names[i]);
+                            "Can't reconnect listener for " +
+                            names[i]);
                 }
             }
 
@@ -1647,7 +1647,7 @@
         protected void checkConnection() throws IOException {
             if (logger.debugOn())
                 logger.debug("RMIClientCommunicatorAdmin-checkConnection",
-                             "Calling the method getDefaultDomain.");
+                        "Calling the method getDefaultDomain.");
 
             connection.getDefaultDomain(null);
         }
@@ -1677,12 +1677,12 @@
             connectionId = getConnectionId();
 
             Notification reconnectedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.OPENED,
-                                              this,
-                                              connectionId,
-                                              clientNotifSeqNo++,
-                                              "Reconnected to server",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.OPENED,
+                    this,
+                    connectionId,
+                    clientNotifSeqNo++,
+                    "Reconnected to server",
+                    null);
             sendNotification(reconnectedNotif);
 
         }
@@ -1692,7 +1692,7 @@
                 close();
             } catch (IOException ioe) {
                 logger.warning("RMIClientCommunicatorAdmin-doStop",
-                               "Failed to call the method close():" + ioe);
+                        "Failed to call the method close():" + ioe);
                 logger.debug("RMIClientCommunicatorAdmin-doStop",ioe);
             }
         }
@@ -1785,15 +1785,15 @@
             final Object orb = environment.get(EnvHelp.DEFAULT_ORB);
             if (orb != null && !(orb instanceof  ORB))
                 throw new IllegalArgumentException(EnvHelp.DEFAULT_ORB +
-                          " must be an instance of org.omg.CORBA.ORB.");
+                        " must be an instance of org.omg.CORBA.ORB.");
             if (orb != null) return (ORB)orb;
         }
         final ORB orb =
-            (RMIConnector.orb==null)?null:RMIConnector.orb.get();
+                (RMIConnector.orb==null)?null:RMIConnector.orb.get();
         if (orb != null) return orb;
 
         final ORB newOrb =
-            ORB.init((String[])null, (Properties)null);
+                ORB.init((String[])null, (Properties)null);
         RMIConnector.orb = new WeakReference<ORB>(newOrb);
         return newOrb;
     }
@@ -1810,11 +1810,11 @@
      * @see #RMIConnector(RMIServer,Map)
      **/
     private void readObject(java.io.ObjectInputStream s)
-        throws IOException, ClassNotFoundException  {
+    throws IOException, ClassNotFoundException  {
         s.defaultReadObject();
 
         if (rmiServer == null && jmxServiceURL == null) throw new
-          InvalidObjectException("rmiServer and jmxServiceURL both null");
+                InvalidObjectException("rmiServer and jmxServiceURL both null");
 
         initTransients();
     }
@@ -1851,9 +1851,9 @@
      * @see #RMIConnector(RMIServer,Map)
      **/
     private void writeObject(java.io.ObjectOutputStream s)
-        throws IOException {
+    throws IOException {
         if (rmiServer == null && jmxServiceURL == null) throw new
-          InvalidObjectException("rmiServer and jmxServiceURL both null.");
+                InvalidObjectException("rmiServer and jmxServiceURL both null.");
         connectStub(this.rmiServer,env);
         s.defaultWriteObject();
     }
@@ -1892,21 +1892,21 @@
     //--------------------------------------------------------------------
 
     private static void checkStub(Remote stub,
-                                  Class<?> stubClass) {
+            Class<?> stubClass) {
 
         // Check remote stub is from the expected class.
         //
         if (stub.getClass() != stubClass) {
             if (!Proxy.isProxyClass(stub.getClass())) {
                 throw new SecurityException(
-                          "Expecting a " + stubClass.getName() + " stub!");
+                        "Expecting a " + stubClass.getName() + " stub!");
             } else {
                 InvocationHandler handler = Proxy.getInvocationHandler(stub);
                 if (handler.getClass() != RemoteObjectInvocationHandler.class)
                     throw new SecurityException(
-                              "Expecting a dynamic proxy instance with a " +
-                              RemoteObjectInvocationHandler.class.getName() +
-                              " invocation handler!");
+                            "Expecting a dynamic proxy instance with a " +
+                            RemoteObjectInvocationHandler.class.getName() +
+                            " invocation handler!");
                 else
                     stub = (Remote) handler;
             }
@@ -1918,8 +1918,8 @@
         RemoteRef ref = ((RemoteObject)stub).getRef();
         if (ref.getClass() != UnicastRef2.class)
             throw new SecurityException(
-                      "Expecting a " + UnicastRef2.class.getName() +
-                      " remote reference in stub!");
+                    "Expecting a " + UnicastRef2.class.getName() +
+                    " remote reference in stub!");
 
         // Check RMIClientSocketFactory in stub is from the expected class
         // "javax.rmi.ssl.SslRMIClientSocketFactory".
@@ -1928,8 +1928,8 @@
         RMIClientSocketFactory csf = liveRef.getClientSocketFactory();
         if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class)
             throw new SecurityException(
-                  "Expecting a " + SslRMIClientSocketFactory.class.getName() +
-                  " RMI client socket factory in stub!");
+                    "Expecting a " + SslRMIClientSocketFactory.class.getName() +
+                    " RMI client socket factory in stub!");
     }
 
     //--------------------------------------------------------------------
@@ -1937,8 +1937,8 @@
     //--------------------------------------------------------------------
 
     private RMIServer findRMIServer(JMXServiceURL directoryURL,
-                                    Map<String, Object> environment)
-        throws NamingException, IOException {
+            Map<String, Object> environment)
+            throws NamingException, IOException {
         final boolean isIiop = RMIConnectorServer.isIiopURL(directoryURL,true);
         if (isIiop) {
             // Make sure java.naming.corba.orb is in the Map.
@@ -1956,7 +1956,7 @@
             return findRMIServerIIOP(path.substring(5,end), environment, isIiop);
         else {
             final String msg = "URL path must begin with /jndi/ or /stub/ " +
-                "or /ior/: " + path;
+                    "or /ior/: " + path;
             throw new MalformedURLException(msg);
         }
     }
@@ -1975,8 +1975,8 @@
      * @exception NamingException if the stub couldn't be found.
      **/
     private RMIServer findRMIServerJNDI(String jndiURL, Map<String, ?> env,
-                                        boolean isIiop)
-        throws NamingException {
+            boolean isIiop)
+            throws NamingException {
 
         InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env));
 
@@ -1997,11 +1997,11 @@
     private static RMIServer narrowIIOPServer(Object objref) {
         try {
             return (RMIServer)
-                PortableRemoteObject.narrow(objref, RMIServer.class);
+            PortableRemoteObject.narrow(objref, RMIServer.class);
         } catch (ClassCastException e) {
             if (logger.traceOn())
                 logger.trace("narrowIIOPServer","Failed to narrow objref=" +
-                      objref + ": " + e);
+                        objref + ": " + e);
             if (logger.debugOn()) logger.debug("narrowIIOPServer",e);
             return null;
         }
@@ -2010,7 +2010,7 @@
     private RMIServer findRMIServerIIOP(String ior, Map<String, ?> env, boolean isIiop) {
         // could forbid "rmi:" URL here -- but do we need to?
         final ORB orb = (ORB)
-            env.get(EnvHelp.DEFAULT_ORB);
+        env.get(EnvHelp.DEFAULT_ORB);
         final Object stub = orb.string_to_object(ior);
         return (RMIServer) PortableRemoteObject.narrow(stub, RMIServer.class);
     }
@@ -2023,15 +2023,15 @@
             serialized = base64ToByteArray(base64);
         } catch (IllegalArgumentException e) {
             throw new MalformedURLException("Bad BASE64 encoding: " +
-                                            e.getMessage());
+                    e.getMessage());
         }
         final ByteArrayInputStream bin = new ByteArrayInputStream(serialized);
 
         final ClassLoader loader = EnvHelp.resolveClientClassLoader(env);
         final ObjectInputStream oin =
-            (loader == null) ?
-                new ObjectInputStream(bin) :
-                new ObjectInputStreamWithLoader(bin, loader);
+                (loader == null) ?
+                    new ObjectInputStream(bin) :
+                    new ObjectInputStreamWithLoader(bin, loader);
         final Object stub;
         try {
             stub = oin.readObject();
@@ -2044,7 +2044,7 @@
     private static final class ObjectInputStreamWithLoader
             extends ObjectInputStream {
         ObjectInputStreamWithLoader(InputStream in, ClassLoader cl)
-                throws IOException {
+        throws IOException {
             super(in);
             this.loader = cl;
         }
@@ -2127,40 +2127,40 @@
         RMIServer.class.getName() + "Impl_Stub";
     private static final Class<?> rmiServerImplStubClass;
     private static final String rmiConnectionImplStubClassName =
-        RMIConnection.class.getName() + "Impl_Stub";
+            RMIConnection.class.getName() + "Impl_Stub";
     private static final Class<?> rmiConnectionImplStubClass;
     private static final String pRefClassName =
         "com.sun.jmx.remote.internal.PRef";
     private static final Constructor<?> proxyRefConstructor;
     static {
         final String pRefByteCodeString =
-            "\312\376\272\276\0\0\0.\0\27\12\0\5\0\15\11\0\4\0\16\13\0\17\0"+
-            "\20\7\0\21\7\0\22\1\0\6<init>\1\0\36(Ljava/rmi/server/RemoteRef;"+
-            ")V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/reflec"+
-            "t/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12Exception"+
-            "s\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1\0\40com/"+
-            "sun/jmx/remote/internal/PRef\1\0$com/sun/jmx/remote/internal/Pr"+
-            "oxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Ljava/rmi/serve"+
-            "r/RemoteRef;\1\0\31java/rmi/server/RemoteRef\0!\0\4\0\5\0\0\0\0"+
-            "\0\2\0\1\0\6\0\7\0\1\0\10\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261"+
-            "\0\0\0\0\0\1\0\11\0\12\0\2\0\10\0\0\0\33\0\6\0\6\0\0\0\17*\264\0"+
-            "\2+,-\26\4\271\0\3\6\0\260\0\0\0\0\0\13\0\0\0\4\0\1\0\14\0\0";
+                "\312\376\272\276\0\0\0.\0\27\12\0\5\0\15\11\0\4\0\16\13\0\17\0"+
+                "\20\7\0\21\7\0\22\1\0\6<init>\1\0\36(Ljava/rmi/server/RemoteRef;"+
+                ")V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/reflec"+
+                "t/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12Exception"+
+                "s\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1\0\40com/"+
+                "sun/jmx/remote/internal/PRef\1\0$com/sun/jmx/remote/internal/Pr"+
+                "oxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Ljava/rmi/serve"+
+                "r/RemoteRef;\1\0\31java/rmi/server/RemoteRef\0!\0\4\0\5\0\0\0\0"+
+                "\0\2\0\1\0\6\0\7\0\1\0\10\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261"+
+                "\0\0\0\0\0\1\0\11\0\12\0\2\0\10\0\0\0\33\0\6\0\6\0\0\0\17*\264\0"+
+                "\2+,-\26\4\271\0\3\6\0\260\0\0\0\0\0\13\0\0\0\4\0\1\0\14\0\0";
         final byte[] pRefByteCode =
-            NoCallStackClassLoader.stringToBytes(pRefByteCodeString);
+                NoCallStackClassLoader.stringToBytes(pRefByteCodeString);
         PrivilegedExceptionAction<Constructor<?>> action =
                 new PrivilegedExceptionAction<Constructor<?>>() {
             public Constructor<?> run() throws Exception {
                 Class thisClass = RMIConnector.class;
                 ClassLoader thisLoader = thisClass.getClassLoader();
                 ProtectionDomain thisProtectionDomain =
-                    thisClass.getProtectionDomain();
+                        thisClass.getProtectionDomain();
                 String[] otherClassNames = {ProxyRef.class.getName()};
                 ClassLoader cl =
-                    new NoCallStackClassLoader(pRefClassName,
-                                               pRefByteCode,
-                                               otherClassNames,
-                                               thisLoader,
-                                               thisProtectionDomain);
+                        new NoCallStackClassLoader(pRefClassName,
+                        pRefByteCode,
+                        otherClassNames,
+                        thisLoader,
+                        thisProtectionDomain);
                 Class<?> c = cl.loadClass(pRefClassName);
                 return c.getConstructor(RemoteRef.class);
             }
@@ -2171,8 +2171,8 @@
             serverStubClass = Class.forName(rmiServerImplStubClassName);
         } catch (Exception e) {
             logger.error("<clinit>",
-                         "Failed to instantiate " +
-                         rmiServerImplStubClassName + ": " + e);
+                    "Failed to instantiate " +
+                    rmiServerImplStubClassName + ": " + e);
             logger.debug("<clinit>",e);
             serverStubClass = null;
         }
@@ -2185,8 +2185,8 @@
             constr = (Constructor<?>) AccessController.doPrivileged(action);
         } catch (Exception e) {
             logger.error("<clinit>",
-                         "Failed to initialize proxy reference constructor "+
-                         "for " + rmiConnectionImplStubClassName + ": " + e);
+                    "Failed to initialize proxy reference constructor "+
+                    "for " + rmiConnectionImplStubClassName + ": " + e);
             logger.debug("<clinit>",e);
             stubClass = null;
             constr = null;
@@ -2196,9 +2196,9 @@
     }
 
     private static RMIConnection shadowJrmpStub(RemoteObject stub)
-            throws InstantiationException, IllegalAccessException,
-                   InvocationTargetException, ClassNotFoundException,
-                   NoSuchMethodException {
+    throws InstantiationException, IllegalAccessException,
+            InvocationTargetException, ClassNotFoundException,
+            NoSuchMethodException {
         RemoteRef ref = stub.getRef();
         RemoteRef proxyRef = (RemoteRef)
             proxyRefConstructor.newInstance(new Object[] {ref});
@@ -2206,7 +2206,7 @@
             rmiConnectionImplStubClass.getConstructor(RemoteRef.class);
         Object[] args = {proxyRef};
         RMIConnection proxyStub = (RMIConnection)
-            rmiConnectionImplStubConstructor.newInstance(args);
+        rmiConnectionImplStubConstructor.newInstance(args);
         return proxyStub;
     }
 
@@ -2326,55 +2326,55 @@
 
      */
     private static final String iiopConnectionStubClassName =
-        "org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub";
+            "org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub";
     private static final String proxyStubClassName =
-        "com.sun.jmx.remote.internal.ProxyStub";
+            "com.sun.jmx.remote.internal.ProxyStub";
     private static final String pInputStreamClassName =
         "com.sun.jmx.remote.internal.PInputStream";
     private static final Class<?> proxyStubClass;
     static {
         final String proxyStubByteCodeString =
-            "\312\376\272\276\0\0\0.\0)\12\0\14\0\26\7\0\27\12\0\14\0\30\12"+
-            "\0\2\0\31\7\0\32\12\0\5\0\33\12\0\5\0\34\12\0\5\0\35\12\0\2\0"+
-            "\36\12\0\14\0\37\7\0\40\7\0!\1\0\6<init>\1\0\3()V\1\0\4Code\1"+
-            "\0\7_invoke\1\0K(Lorg/omg/CORBA/portable/OutputStream;)Lorg/o"+
-            "mg/CORBA/portable/InputStream;\1\0\12Exceptions\7\0\"\1\0\15_"+
-            "releaseReply\1\0'(Lorg/omg/CORBA/portable/InputStream;)V\14\0"+
-            "\15\0\16\1\0(com/sun/jmx/remote/internal/PInputStream\14\0\20"+
-            "\0\21\14\0\15\0\25\1\0+org/omg/CORBA/portable/ApplicationExce"+
-            "ption\14\0#\0$\14\0%\0&\14\0\15\0'\14\0(\0$\14\0\24\0\25\1\0%"+
-            "com/sun/jmx/remote/internal/ProxyStub\1\0<org/omg/stub/javax/"+
-            "management/remote/rmi/_RMIConnection_Stub\1\0)org/omg/CORBA/p"+
-            "ortable/RemarshalException\1\0\16getInputStream\1\0&()Lorg/om"+
-            "g/CORBA/portable/InputStream;\1\0\5getId\1\0\24()Ljava/lang/S"+
-            "tring;\1\09(Ljava/lang/String;Lorg/omg/CORBA/portable/InputSt"+
-            "ream;)V\1\0\25getProxiedInputStream\0!\0\13\0\14\0\0\0\0\0\3\0"+
-            "\1\0\15\0\16\0\1\0\17\0\0\0\21\0\1\0\1\0\0\0\5*\267\0\1\261\0"+
-            "\0\0\0\0\1\0\20\0\21\0\2\0\17\0\0\0;\0\4\0\4\0\0\0'\273\0\2Y*"+
-            "+\267\0\3\267\0\4\260M\273\0\2Y,\266\0\6\267\0\4N\273\0\5Y,\266"+
-            "\0\7-\267\0\10\277\0\1\0\0\0\14\0\15\0\5\0\0\0\22\0\0\0\6\0\2"+
-            "\0\5\0\23\0\1\0\24\0\25\0\1\0\17\0\0\0\36\0\2\0\2\0\0\0\22+\306"+
-            "\0\13+\300\0\2\266\0\11L*+\267\0\12\261\0\0\0\0\0\0";
+                "\312\376\272\276\0\0\0.\0)\12\0\14\0\26\7\0\27\12\0\14\0\30\12"+
+                "\0\2\0\31\7\0\32\12\0\5\0\33\12\0\5\0\34\12\0\5\0\35\12\0\2\0"+
+                "\36\12\0\14\0\37\7\0\40\7\0!\1\0\6<init>\1\0\3()V\1\0\4Code\1"+
+                "\0\7_invoke\1\0K(Lorg/omg/CORBA/portable/OutputStream;)Lorg/o"+
+                "mg/CORBA/portable/InputStream;\1\0\12Exceptions\7\0\"\1\0\15_"+
+                "releaseReply\1\0'(Lorg/omg/CORBA/portable/InputStream;)V\14\0"+
+                "\15\0\16\1\0(com/sun/jmx/remote/internal/PInputStream\14\0\20"+
+                "\0\21\14\0\15\0\25\1\0+org/omg/CORBA/portable/ApplicationExce"+
+                "ption\14\0#\0$\14\0%\0&\14\0\15\0'\14\0(\0$\14\0\24\0\25\1\0%"+
+                "com/sun/jmx/remote/internal/ProxyStub\1\0<org/omg/stub/javax/"+
+                "management/remote/rmi/_RMIConnection_Stub\1\0)org/omg/CORBA/p"+
+                "ortable/RemarshalException\1\0\16getInputStream\1\0&()Lorg/om"+
+                "g/CORBA/portable/InputStream;\1\0\5getId\1\0\24()Ljava/lang/S"+
+                "tring;\1\09(Ljava/lang/String;Lorg/omg/CORBA/portable/InputSt"+
+                "ream;)V\1\0\25getProxiedInputStream\0!\0\13\0\14\0\0\0\0\0\3\0"+
+                "\1\0\15\0\16\0\1\0\17\0\0\0\21\0\1\0\1\0\0\0\5*\267\0\1\261\0"+
+                "\0\0\0\0\1\0\20\0\21\0\2\0\17\0\0\0;\0\4\0\4\0\0\0'\273\0\2Y*"+
+                "+\267\0\3\267\0\4\260M\273\0\2Y,\266\0\6\267\0\4N\273\0\5Y,\266"+
+                "\0\7-\267\0\10\277\0\1\0\0\0\14\0\15\0\5\0\0\0\22\0\0\0\6\0\2"+
+                "\0\5\0\23\0\1\0\24\0\25\0\1\0\17\0\0\0\36\0\2\0\2\0\0\0\22+\306"+
+                "\0\13+\300\0\2\266\0\11L*+\267\0\12\261\0\0\0\0\0\0";
         final String pInputStreamByteCodeString =
-            "\312\376\272\276\0\0\0.\0\36\12\0\7\0\17\11\0\6\0\20\12\0\21\0"+
-            "\22\12\0\6\0\23\12\0\24\0\25\7\0\26\7\0\27\1\0\6<init>\1\0'(L"+
-            "org/omg/CORBA/portable/InputStream;)V\1\0\4Code\1\0\10read_an"+
-            "y\1\0\25()Lorg/omg/CORBA/Any;\1\0\12read_value\1\0)(Ljava/lan"+
-            "g/Class;)Ljava/io/Serializable;\14\0\10\0\11\14\0\30\0\31\7\0"+
-            "\32\14\0\13\0\14\14\0\33\0\34\7\0\35\14\0\15\0\16\1\0(com/sun"+
-            "/jmx/remote/internal/PInputStream\1\0,com/sun/jmx/remote/inte"+
-            "rnal/ProxyInputStream\1\0\2in\1\0$Lorg/omg/CORBA/portable/Inp"+
-            "utStream;\1\0\"org/omg/CORBA/portable/InputStream\1\0\6narrow"+
-            "\1\0*()Lorg/omg/CORBA_2_3/portable/InputStream;\1\0&org/omg/C"+
-            "ORBA_2_3/portable/InputStream\0!\0\6\0\7\0\0\0\0\0\3\0\1\0\10"+
-            "\0\11\0\1\0\12\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261\0\0\0\0"+
-            "\0\1\0\13\0\14\0\1\0\12\0\0\0\24\0\1\0\1\0\0\0\10*\264\0\2\266"+
-            "\0\3\260\0\0\0\0\0\1\0\15\0\16\0\1\0\12\0\0\0\25\0\2\0\2\0\0\0"+
-            "\11*\266\0\4+\266\0\5\260\0\0\0\0\0\0";
+                "\312\376\272\276\0\0\0.\0\36\12\0\7\0\17\11\0\6\0\20\12\0\21\0"+
+                "\22\12\0\6\0\23\12\0\24\0\25\7\0\26\7\0\27\1\0\6<init>\1\0'(L"+
+                "org/omg/CORBA/portable/InputStream;)V\1\0\4Code\1\0\10read_an"+
+                "y\1\0\25()Lorg/omg/CORBA/Any;\1\0\12read_value\1\0)(Ljava/lan"+
+                "g/Class;)Ljava/io/Serializable;\14\0\10\0\11\14\0\30\0\31\7\0"+
+                "\32\14\0\13\0\14\14\0\33\0\34\7\0\35\14\0\15\0\16\1\0(com/sun"+
+                "/jmx/remote/internal/PInputStream\1\0,com/sun/jmx/remote/inte"+
+                "rnal/ProxyInputStream\1\0\2in\1\0$Lorg/omg/CORBA/portable/Inp"+
+                "utStream;\1\0\"org/omg/CORBA/portable/InputStream\1\0\6narrow"+
+                "\1\0*()Lorg/omg/CORBA_2_3/portable/InputStream;\1\0&org/omg/C"+
+                "ORBA_2_3/portable/InputStream\0!\0\6\0\7\0\0\0\0\0\3\0\1\0\10"+
+                "\0\11\0\1\0\12\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261\0\0\0\0"+
+                "\0\1\0\13\0\14\0\1\0\12\0\0\0\24\0\1\0\1\0\0\0\10*\264\0\2\266"+
+                "\0\3\260\0\0\0\0\0\1\0\15\0\16\0\1\0\12\0\0\0\25\0\2\0\2\0\0\0"+
+                "\11*\266\0\4+\266\0\5\260\0\0\0\0\0\0";
         final byte[] proxyStubByteCode =
-            NoCallStackClassLoader.stringToBytes(proxyStubByteCodeString);
+                NoCallStackClassLoader.stringToBytes(proxyStubByteCodeString);
         final byte[] pInputStreamByteCode =
-            NoCallStackClassLoader.stringToBytes(pInputStreamByteCodeString);
+                NoCallStackClassLoader.stringToBytes(pInputStreamByteCodeString);
         final String[] classNames={proxyStubClassName, pInputStreamClassName};
         final byte[][] byteCodes = {proxyStubByteCode, pInputStreamByteCode};
         final String[] otherClassNames = {
@@ -2388,13 +2388,13 @@
                 Class thisClass = RMIConnector.class;
                 ClassLoader thisLoader = thisClass.getClassLoader();
                 ProtectionDomain thisProtectionDomain =
-                    thisClass.getProtectionDomain();
+                        thisClass.getProtectionDomain();
                 ClassLoader cl =
-                    new NoCallStackClassLoader(classNames,
-                                               byteCodes,
-                                               otherClassNames,
-                                               thisLoader,
-                                               thisProtectionDomain);
+                        new NoCallStackClassLoader(classNames,
+                        byteCodes,
+                        otherClassNames,
+                        thisLoader,
+                        thisProtectionDomain);
                 return cl.loadClass(proxyStubClassName);
             }
         };
@@ -2403,7 +2403,7 @@
             stubClass = AccessController.doPrivileged(action);
         } catch (Exception e) {
             logger.error("<clinit>",
-                   "Unexpected exception making shadow IIOP stub class: "+e);
+                    "Unexpected exception making shadow IIOP stub class: "+e);
             logger.debug("<clinit>",e);
             stubClass = null;
         }
@@ -2411,15 +2411,15 @@
     }
 
     private static RMIConnection shadowIiopStub(Stub stub)
-            throws InstantiationException, IllegalAccessException {
+    throws InstantiationException, IllegalAccessException {
         Stub proxyStub = (Stub) proxyStubClass.newInstance();
         proxyStub._set_delegate(stub._get_delegate());
         return (RMIConnection) proxyStub;
     }
 
     private static RMIConnection getConnection(RMIServer server,
-                                               Object credentials,
-                                               boolean checkStub)
+            Object credentials,
+            boolean checkStub)
             throws IOException {
         RMIConnection c = server.newClient(credentials);
         if (checkStub) checkStub(c, rmiConnectionImplStubClass);
@@ -2429,14 +2429,14 @@
             if (c.getClass().getName().equals(iiopConnectionStubClassName))
                 return shadowIiopStub((Stub) c);
             logger.trace("getConnection",
-                         "Did not wrap " + c.getClass() + " to foil " +
-                         "stack search for classes: class loading semantics " +
-                         "may be incorrect");
+                    "Did not wrap " + c.getClass() + " to foil " +
+                    "stack search for classes: class loading semantics " +
+                    "may be incorrect");
         } catch (Exception e) {
             logger.error("getConnection",
-                         "Could not wrap " + c.getClass() + " to foil " +
-                         "stack search for classes: class loading semantics " +
-                         "may be incorrect: " + e);
+                    "Could not wrap " + c.getClass() + " to foil " +
+                    "stack search for classes: class loading semantics " +
+                    "may be incorrect: " + e);
             logger.debug("getConnection",e);
             // so just return the original stub, which will work for all
             // but the most exotic class loading situations
@@ -2449,7 +2449,7 @@
         int numGroups = sLen/4;
         if (4*numGroups != sLen)
             throw new IllegalArgumentException(
-                "String length must be a multiple of four.");
+                    "String length must be a multiple of four.");
         int missingBytesInLastGroup = 0;
         int numFullGroups = numGroups;
         if (sLen != 0) {
@@ -2535,21 +2535,21 @@
         final ClassLoader old =  t.getContextClassLoader();
         if (defaultClassLoader != null)
             AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                    public Void run() {
-                        t.setContextClassLoader(defaultClassLoader);
-                        return null;
-                    }
-                });
-        return old;
+                public Void run() {
+                    t.setContextClassLoader(defaultClassLoader);
+                    return null;
+                }
+            });
+            return old;
     }
 
     private void popDefaultClassLoader(final ClassLoader old) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    Thread.currentThread().setContextClassLoader(old);
-                    return null;
-                }
-            });
+            public Void run() {
+                Thread.currentThread().setContextClassLoader(old);
+                return null;
+            }
+        });
     }
 
     //--------------------------------------------------------------------
--- a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Thu Nov 13 11:15:01 2008 -0800
@@ -383,7 +383,7 @@
         try {
             if (tracing) logger.trace("start", "setting default class loader");
             defaultClassLoader = EnvHelp.resolveServerClassLoader(
-                    attributes, getSystemMBeanServer());
+                    attributes, getSystemMBeanServerForwarder());
         } catch (InstanceNotFoundException infc) {
             IllegalArgumentException x = new
                 IllegalArgumentException("ClassLoader not found: "+infc);
@@ -398,7 +398,7 @@
         else
             rmiServer = newServer();
 
-        rmiServer.setMBeanServer(getSystemMBeanServer());
+        rmiServer.setMBeanServer(getSystemMBeanServerForwarder());
         rmiServer.setDefaultClassLoader(defaultClassLoader);
         rmiServer.setRMIConnectorServer(this);
         rmiServer.export();
@@ -592,31 +592,6 @@
         return Collections.unmodifiableMap(map);
     }
 
-    @Override
-    public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
-        MBeanServer oldSMBS = getSystemMBeanServer();
-        super.setMBeanServerForwarder(mbsf);
-        if (oldSMBS != getSystemMBeanServer())
-            updateMBeanServer();
-        // If the system chain of MBeanServerForwarders is not empty, then
-        // there is no need to call rmiServerImpl.setMBeanServer, because
-        // it is pointing to the head of the system chain and that has not
-        // changed.  (The *end* of the system chain will have been changed
-        // to point to mbsf.)
-    }
-
-    private void updateMBeanServer() {
-        if (rmiServerImpl != null)
-            rmiServerImpl.setMBeanServer(getSystemMBeanServer());
-    }
-
-    @Override
-    public synchronized void setSystemMBeanServerForwarder(
-            MBeanServerForwarder mbsf) {
-        super.setSystemMBeanServerForwarder(mbsf);
-        updateMBeanServer();
-    }
-
     /**
      * {@inheritDoc}
      * @return true, since this connector server does support a system chain
@@ -631,16 +606,19 @@
        here so that they are accessible to other classes in this package
        even though they have protected access.  */
 
+    @Override
     protected void connectionOpened(String connectionId, String message,
                                     Object userData) {
         super.connectionOpened(connectionId, message, userData);
     }
 
+    @Override
     protected void connectionClosed(String connectionId, String message,
                                     Object userData) {
         super.connectionClosed(connectionId, message, userData);
     }
 
+    @Override
     protected void connectionFailed(String connectionId, String message,
                                     Object userData) {
         super.connectionFailed(connectionId, message, userData);
--- a/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java	Thu Nov 13 11:15:01 2008 -0800
@@ -435,8 +435,14 @@
             if (streaming()) {
                 if (chunkLength != -1) {
                     requests.set ("Transfer-Encoding", "chunked");
-                } else {
-                    requests.set ("Content-Length", String.valueOf(fixedContentLength));
+                } else { /* fixed content length */
+                    if (fixedContentLengthLong != -1) {
+                        requests.set ("Content-Length",
+                                      String.valueOf(fixedContentLengthLong));
+                    } else if (fixedContentLength != -1) {
+                        requests.set ("Content-Length",
+                                      String.valueOf(fixedContentLength));
+                    }
                 }
             } else if (poster != null) {
                 /* add Content-Length & POST/PUT data */
@@ -871,11 +877,17 @@
             ps = (PrintStream)http.getOutputStream();
             if (streaming()) {
                 if (strOutputStream == null) {
-                    if (fixedContentLength != -1) {
-                        strOutputStream = new StreamingOutputStream (ps, fixedContentLength);
-                    } else if (chunkLength != -1) {
-                        strOutputStream =
-                            new StreamingOutputStream (new ChunkedOutputStream (ps, chunkLength), -1);
+                    if (chunkLength != -1) { /* chunked */
+                         strOutputStream = new StreamingOutputStream(
+                               new ChunkedOutputStream(ps, chunkLength), -1L);
+                    } else { /* must be fixed content length */
+                        long length = 0L;
+                        if (fixedContentLengthLong != -1) {
+                            length = fixedContentLengthLong;
+                        } else if (fixedContentLength != -1) {
+                            length = fixedContentLength;
+                        }
+                        strOutputStream = new StreamingOutputStream(ps, length);
                     }
                 }
                 return strOutputStream;
@@ -895,7 +907,8 @@
     }
 
     private boolean streaming () {
-        return (fixedContentLength != -1) || (chunkLength != -1);
+        return (fixedContentLength != -1) || (fixedContentLengthLong != -1) ||
+               (chunkLength != -1);
     }
 
     /*
@@ -2619,8 +2632,8 @@
 
     class StreamingOutputStream extends FilterOutputStream {
 
-        int expected;
-        int written;
+        long expected;
+        long written;
         boolean closed;
         boolean error;
         IOException errorExcp;
@@ -2631,10 +2644,10 @@
          *    In the 2nd case, we make sure the expected number of
          *    of bytes are actually written
          */
-        StreamingOutputStream (OutputStream os, int expectedLength) {
+        StreamingOutputStream (OutputStream os, long expectedLength) {
             super (os);
             expected = expectedLength;
-            written = 0;
+            written = 0L;
             closed = false;
             error = false;
         }
@@ -2643,7 +2656,7 @@
         public void write (int b) throws IOException {
             checkError();
             written ++;
-            if (expected != -1 && written > expected) {
+            if (expected != -1L && written > expected) {
                 throw new IOException ("too many bytes written");
             }
             out.write (b);
@@ -2658,7 +2671,7 @@
         public void write (byte[] b, int off, int len) throws IOException {
             checkError();
             written += len;
-            if (expected != -1 && written > expected) {
+            if (expected != -1L && written > expected) {
                 out.close ();
                 throw new IOException ("too many bytes written");
             }
@@ -2691,7 +2704,7 @@
                 return;
             }
             closed = true;
-            if (expected != -1) {
+            if (expected != -1L) {
                 /* not chunked */
                 if (written != expected) {
                     error = true;
--- a/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java	Thu Nov 13 11:15:01 2008 -0800
@@ -527,6 +527,10 @@
         delegate.setFixedLengthStreamingMode(contentLength);
     }
 
+    public void setFixedLengthStreamingMode(long contentLength) {
+        delegate.setFixedLengthStreamingMode(contentLength);
+    }
+
     public void setChunkedStreamingMode (int chunklen) {
         delegate.setChunkedStreamingMode(chunklen);
     }
--- a/src/share/classes/sun/security/jgss/GSSContextImpl.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/sun/security/jgss/GSSContextImpl.java	Thu Nov 13 11:15:01 2008 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2000-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
@@ -284,7 +284,8 @@
         ByteArrayOutputStream bos = new ByteArrayOutputStream(100);
         acceptSecContext(new ByteArrayInputStream(inTok, offset, len),
                          bos);
-        return bos.toByteArray();
+        byte[] out = bos.toByteArray();
+        return (out.length == 0) ? null : out;
     }
 
     public void acceptSecContext(InputStream inStream,
--- a/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Thu Nov 13 11:15:01 2008 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2005-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
@@ -413,13 +413,14 @@
                     // pull out the mechanism token
                     byte[] accept_token = targToken.getResponseToken();
                     if (accept_token == null) {
-                        // return wth failure
-                        throw new GSSException(errorCode, -1,
-                                        "mechansim token from server is null");
+                        if (!isMechContextEstablished()) {
+                            // return with failure
+                            throw new GSSException(errorCode, -1,
+                                    "mechanism token from server is null");
+                        }
+                    } else {
+                        mechToken = GSS_initSecContext(accept_token);
                     }
-
-                    mechToken = GSS_initSecContext(accept_token);
-
                     // verify MIC
                     if (!GSSUtil.useMSInterop()) {
                         byte[] micToken = targToken.getMechListMIC();
@@ -428,7 +429,6 @@
                                 "verification of MIC on MechList Failed!");
                         }
                     }
-
                     if (isMechContextEstablished()) {
                         state = STATE_DONE;
                         retVal = mechToken;
@@ -556,9 +556,6 @@
 
                 // get the token for mechanism
                 byte[] accept_token = GSS_acceptSecContext(mechToken);
-                if (accept_token == null) {
-                    valid = false;
-                }
 
                 // verify MIC
                 if (!GSSUtil.useMSInterop() && valid) {
--- a/src/share/classes/sun/security/provider/certpath/OCSPResponse.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/classes/sun/security/provider/certpath/OCSPResponse.java	Thu Nov 13 11:15:01 2008 -0800
@@ -151,6 +151,10 @@
 
     private SingleResponse singleResponse;
 
+    // Maximum clock skew in milliseconds (10 minutes) allowed when checking
+    // validity of OCSP responses
+    private static final long MAX_CLOCK_SKEW = 600000;
+
     // an array of all of the CRLReasons (used in SingleResponse)
     private static CRLReason[] values = CRLReason.values();
 
@@ -583,7 +587,9 @@
                 }
             }
 
-            Date now = new Date();
+            long now = System.currentTimeMillis();
+            Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW);
+            Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW);
             if (DEBUG != null) {
                 String until = "";
                 if (nextUpdate != null) {
@@ -593,8 +599,8 @@
                     thisUpdate + until);
             }
             // Check that the test date is within the validity interval
-            if ((thisUpdate != null && now.before(thisUpdate)) ||
-                (nextUpdate != null && now.after(nextUpdate))) {
+            if ((thisUpdate != null && nowPlusSkew.before(thisUpdate)) ||
+                (nextUpdate != null && nowMinusSkew.after(nextUpdate))) {
 
                 if (DEBUG != null) {
                     DEBUG.println("Response is unreliable: its validity " +
--- a/src/share/native/java/util/zip/zip_util.c	Fri Nov 07 11:45:22 2008 -0800
+++ b/src/share/native/java/util/zip/zip_util.c	Thu Nov 13 11:15:01 2008 -0800
@@ -273,8 +273,8 @@
 /*
  * Searches for end of central directory (END) header. The contents of
  * the END header will be read and placed in endbuf. Returns the file
- * position of the END header, otherwise returns 0 if the END header
- * was not found or -1 if an error occurred.
+ * position of the END header, otherwise returns -1 if the END header
+ * was not found or an error occurred.
  */
 static jlong
 findEND(jzfile *zip, void *endbuf)
@@ -314,7 +314,7 @@
             }
         }
     }
-    return 0; /* END header not found */
+    return -1; /* END header not found */
 }
 
 /*
@@ -460,9 +460,8 @@
 
 /*
  * Reads zip file central directory. Returns the file position of first
- * CEN header, otherwise returns 0 if central directory not found or -1
- * if an error occurred. If zip->msg != NULL then the error was a zip
- * format error and zip->msg has the error text.
+ * CEN header, otherwise returns -1 if an error occured. If zip->msg != NULL
+ * then the error was a zip format error and zip->msg has the error text.
  * Always pass in -1 for knownTotal; it's used for a recursive call.
  */
 static jlong
@@ -488,9 +487,9 @@
 
     /* Get position of END header */
     if ((endpos = findEND(zip, endbuf)) == -1)
-        return -1; /* system error */
+        return -1; /* no END header or system error */
 
-    if (endpos == 0) return 0;  /* END header not found */
+    if (endpos == 0) return 0;  /* only END header present */
 
     freeCEN(zip);
 
--- a/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java	Thu Nov 13 11:15:01 2008 -0800
@@ -23,7 +23,7 @@
 
 /**
  * @test
- * @bug 6756771
+ * @bug 6756771 6755625
  * @summary  com.sun.net.httpserver.HttpServer should handle POSTs larger than 2Gig
  */
 
@@ -44,34 +44,16 @@
 {
     static final long POST_SIZE = 4L * 1024L * 1024L * 1024L; // 4Gig
 
-    /* Remove when CR 6755625 is fixed */
-    static final String requestHeaders =  ((new StringBuilder())
-        .append("POST /flis/ HTTP/1.1\r\n")
-        .append("User-Agent: Java/1.7.0\r\n")
-        .append("Host: localhost\r\n")
-        .append("Accept: text/html, image/gif, image/jpeg,")
-        .append(        " *; q=.2, */*; q=.2\r\n")
-        .append("Content-Length: 4294967296\r\n\r\n")).toString();
-
     void test(String[] args) throws IOException {
         HttpServer httpServer = startHttpServer();
         int port = httpServer.getAddress().getPort();
         try {
-          /* Uncomment & when CR 6755625 is fixed, remove socket code
             URL url = new URL("http://localhost:" + port + "/flis/");
             HttpURLConnection uc = (HttpURLConnection)url.openConnection();
             uc.setDoOutput(true);
             uc.setRequestMethod("POST");
             uc.setFixedLengthStreamingMode(POST_SIZE);
             OutputStream os = uc.getOutputStream();
-          */
-
-            Socket socket = new Socket("localhost", port);
-            OutputStream os = socket.getOutputStream();
-            PrintStream ps = new PrintStream(os);
-            debug("Request: " + requestHeaders);
-            ps.print(requestHeaders);
-            ps.flush();
 
             /* create a 32K byte array with data to POST */
             int thirtyTwoK = 32 * 1024;
@@ -84,18 +66,12 @@
                 os.write(ba);
             }
 
-          /* Uncomment & when CR 6755625 is fixed, remove socket code
             os.close();
             InputStream is = uc.getInputStream();
             while(is.read(ba) != -1);
             is.close();
-           */
 
-           InputStream is = socket.getInputStream();
-           is.read();
-           socket.close();
-
-           pass();
+            pass();
         } finally {
             httpServer.stop(0);
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/security/cert/CertPathValidatorException/GetMessage.java	Thu Nov 13 11:15:01 2008 -0800
@@ -0,0 +1,63 @@
+/*
+ * 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 6765046
+ * @summary check that getMessage(cause) returns cause.toString if cause != null
+ */
+
+import java.security.cert.CertPathValidatorException;
+
+public class GetMessage {
+    private static volatile boolean failed = false;
+    public static void main(String[] args) throws Exception {
+
+        Throwable[] causes = {
+                new Throwable(),
+                new Throwable("message"),
+                new Throwable("message", new Throwable()) };
+
+        for (Throwable cause: causes) {
+            CertPathValidatorException cpve =
+                new CertPathValidatorException(cause);
+
+            // from CertPathValidatorException(Throwable cause) spec:
+            // The detail message is set to (cause==null ? null : cause.toString() )
+            // (which typically contains the class and detail message of cause).
+            String expMsg = (cause == null ? null : cause.toString());
+            String actualMsg = cpve.getMessage();
+
+            boolean msgsEqual =
+                (expMsg == null ? actualMsg == null : expMsg.equals(actualMsg));
+            if (!msgsEqual) {
+                System.out.println("expected message:" + expMsg);
+                System.out.println("getMessage():" + actualMsg);
+                failed = true;
+            }
+        }
+        if (failed) {
+            throw new Exception("Some tests FAILED");
+        }
+    }
+}
--- a/test/java/util/zip/TestEmptyZip.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/test/java/util/zip/TestEmptyZip.java	Thu Nov 13 11:15:01 2008 -0800
@@ -39,35 +39,24 @@
             throw new Exception("failed to delete " + zipName);
         }
 
-        // Verify 0-length file cannot be read
         f.createNewFile();
-        ZipFile zf = null;
         try {
-            zf = new ZipFile(f);
-            fail();
-        } catch (Exception ex) {
-            check(ex.getMessage().contains("zip file is empty"));
+            // Verify 0-length file cannot be read
+            checkCannotRead(f);
+
+            // Verify non-zip file cannot be read
+            OutputStream out = new FileOutputStream(f);
+            try {
+                out.write("class Foo { }".getBytes());
+            } finally {
+                out.close();
+            }
+            checkCannotRead(f);
+
         } finally {
-            if (zf != null) {
-                zf.close();
-            }
+            f.delete();
         }
 
-        ZipInputStream zis = null;
-        try {
-            zis = new ZipInputStream(new FileInputStream(f));
-            ZipEntry ze = zis.getNextEntry();
-            check(ze == null);
-        } catch (Exception ex) {
-            unexpected(ex);
-        } finally {
-            if (zis != null) {
-                zis.close();
-            }
-        }
-
-        f.delete();
-
         // Verify 0-entries file can be written
         write(f);
 
@@ -78,6 +67,29 @@
         f.delete();
     }
 
+    static void checkCannotRead(File f) throws IOException {
+        try {
+            new ZipFile(f).close();
+            fail();
+        } catch (ZipException ze) {
+            if (f.length() == 0) {
+                check(ze.getMessage().contains("zip file is empty"));
+            } else {
+                pass();
+            }
+        }
+        ZipInputStream zis = null;
+        try {
+            zis = new ZipInputStream(new FileInputStream(f));
+            ZipEntry ze = zis.getNextEntry();
+            check(ze == null);
+        } catch (IOException ex) {
+            unexpected(ex);
+        } finally {
+            if (zis != null) zis.close();
+        }
+    }
+
     static void write(File f) throws Exception {
         ZipOutputStream zos = null;
         try {
--- a/test/javax/management/Introspector/AnnotationTest.java	Fri Nov 07 11:45:22 2008 -0800
+++ b/test/javax/management/Introspector/AnnotationTest.java	Thu Nov 13 11:15:01 2008 -0800
@@ -39,7 +39,8 @@
 
 /*
   This test checks that annotations produce Descriptor entries as
-  specified in javax.management.DescriptorKey.  It does two things:
+  specified in javax.management.DescriptorKey and javax.management.DescriptorField.
+  It does the following:
 
   - An annotation consisting of an int and a String, each with an
     appropriate @DescriptorKey annotation, is placed on every program
@@ -61,6 +62,10 @@
     The test checks that in each case the corresponding Descriptor
     appears in the appropriate place inside the MBean's MBeanInfo.
 
+ - A @DescriptorFields annotation defining two fields is placed in the
+   same places and again the test checks that the two fields appear
+   in the corresponding MBean*Info objects.
+
   - An annotation consisting of enough other types to ensure coverage
     is placed on a getter.  The test checks that the generated
     MBeanAttributeInfo contains the corresponding Descriptor.  The tested
@@ -78,12 +83,6 @@
 public class AnnotationTest {
     private static String failed = null;
 
-//    @Retention(RetentionPolicy.RUNTIME) @Inherited
-//    @Target(ElementType.METHOD)
-//    public static @interface DescriptorKey {
-//        String value();
-//    }
-
     @Retention(RetentionPolicy.RUNTIME)
     public static @interface Pair {
         @DescriptorKey("x")
@@ -112,11 +111,12 @@
         boolean[] booleanArrayValue();
     }
 
-    /* We use the annotation @Pair(x = 3, y = "foo") everywhere, and this is
-       the Descriptor that it should produce: */
+    /* We use the annotations @Pair(x = 3, y = "foo")
+       and @DescriptorFields({"foo=bar", "baz="}) everywhere, and this is
+       the Descriptor that they should produce: */
     private static Descriptor expectedDescriptor =
-        new ImmutableDescriptor(new String[] {"x", "y"},
-                                new Object[] {3, "foo"});
+        new ImmutableDescriptor(new String[] {"x", "y", "foo", "baz"},
+                                new Object[] {3, "foo", "bar", ""});
 
     private static Descriptor expectedFullDescriptor =
         new ImmutableDescriptor(new String[] {
@@ -136,8 +136,10 @@
                                 });
 
     @Pair(x = 3, y = "foo")
+    @DescriptorFields({"foo=bar", "baz="})
     public static interface ThingMBean {
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         @Full(classValue=Full.class,
               enumValue=RetentionPolicy.RUNTIME,
               booleanValue=false,
@@ -149,32 +151,47 @@
         int getReadOnly();
 
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         void setWriteOnly(int x);
 
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         int getReadWrite1();
         void setReadWrite1(int x);
 
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         int getReadWrite2();
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         void setReadWrite2(int x);
 
         int getReadWrite3();
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         void setReadWrite3(int x);
 
         @Pair(x = 3, y = "foo")
-        int operation(@Pair(x = 3, y = "foo") int p1,
-                      @Pair(x = 3, y = "foo") int p2);
+        @DescriptorFields({"foo=bar", "baz="})
+        int operation(@Pair(x = 3, y = "foo")
+                      @DescriptorFields({"foo=bar", "baz="})
+                      int p1,
+                      @Pair(x = 3, y = "foo")
+                      @DescriptorFields({"foo=bar", "baz="})
+                      int p2);
     }
 
     public static class Thing implements ThingMBean {
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         public Thing() {}
 
         @Pair(x = 3, y = "foo")
-        public Thing(@Pair(x = 3, y = "foo") int p1) {}
+        @DescriptorFields({"foo=bar", "baz="})
+        public Thing(
+                @Pair(x = 3, y = "foo")
+                @DescriptorFields({"foo=bar", "baz="})
+                int p1) {}
 
         public int getReadOnly() {return 0;}
 
@@ -193,14 +210,20 @@
     }
 
     @Pair(x = 3, y = "foo")
+    @DescriptorFields({"foo=bar", "baz="})
     public static interface ThingMXBean extends ThingMBean {}
 
     public static class ThingImpl implements ThingMXBean {
         @Pair(x = 3, y = "foo")
+        @DescriptorFields({"foo=bar", "baz="})
         public ThingImpl() {}
 
         @Pair(x = 3, y = "foo")
-        public ThingImpl(@Pair(x = 3, y = "foo") int p1) {}
+        @DescriptorFields({"foo=bar", "baz="})
+        public ThingImpl(
+                @Pair(x = 3, y = "foo")
+                @DescriptorFields({"foo=bar", "baz="})
+                int p1) {}
 
         public int getReadOnly() {return 0;}
 
@@ -218,6 +241,79 @@
         public int operation(int p1, int p2) {return 0;}
     }
 
+    @Retention(RetentionPolicy.RUNTIME)
+    public static @interface DefaultTest {
+        @DescriptorKey(value = "string1", omitIfDefault = true)
+        String string1() default "";
+        @DescriptorKey(value = "string2", omitIfDefault = true)
+        String string2() default "tiddly pom";
+        @DescriptorKey(value = "int", omitIfDefault = true)
+        int intx() default 23;
+        @DescriptorKey(value = "intarray1", omitIfDefault = true)
+        int[] intArray1() default {};
+        @DescriptorKey(value = "intarray2", omitIfDefault = true)
+        int[] intArray2() default {1, 2};
+        @DescriptorKey(value = "stringarray1", omitIfDefault = true)
+        String[] stringArray1() default {};
+        @DescriptorKey(value = "stringarray2", omitIfDefault = true)
+        String[] stringArray2() default {"foo", "bar"};
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public static @interface Expect {
+        String[] value() default {};
+    }
+
+    public static interface DefaultMBean {
+        @DefaultTest
+        @Expect()
+        public void a();
+
+        @DefaultTest(string1="")
+        @Expect()
+        public void b();
+
+        @DefaultTest(string1="nondefault")
+        @Expect("string1=nondefault")
+        public void c();
+
+        @DefaultTest(string2="tiddly pom")
+        @Expect()
+        public void d();
+
+        @DefaultTest(intx=23)
+        @Expect()
+        public void e();
+
+        @DefaultTest(intx=34)
+        @Expect("int=34")
+        public void f();
+
+        @DefaultTest(intArray1={})
+        @Expect()
+        public void g();
+
+        @DefaultTest(intArray1={2,3})
+        @Expect("intarray1=[2, 3]")
+        public void h();
+
+        @DefaultTest(intArray2={})
+        @Expect("intarray2=[]")
+        public void i();
+
+        @DefaultTest(stringArray1={})
+        @Expect()
+        public void j();
+
+        @DefaultTest(stringArray1={"foo"})
+        @Expect("stringarray1=[foo]")
+        public void k();
+
+        @DefaultTest(stringArray2={})
+        @Expect("stringarray2=[]")
+        public void l();
+    }
+
     public static void main(String[] args) throws Exception {
         System.out.println("Testing that annotations are correctly " +
                            "reflected in Descriptor entries");
@@ -225,20 +321,62 @@
         MBeanServer mbs =
             java.lang.management.ManagementFactory.getPlatformMBeanServer();
         ObjectName on = new ObjectName("a:b=c");
+
         Thing thing = new Thing();
         mbs.registerMBean(thing, on);
         check(mbs, on);
         mbs.unregisterMBean(on);
+
         ThingImpl thingImpl = new ThingImpl();
         mbs.registerMBean(thingImpl, on);
+        Descriptor d = mbs.getMBeanInfo(on).getDescriptor();
+        if (!d.getFieldValue("mxbean").equals("true")) {
+            System.out.println("NOT OK: expected MXBean");
+            failed = "Expected MXBean";
+        }
         check(mbs, on);
 
+        System.out.println();
+        System.out.println("Testing that omitIfDefault works");
+        DefaultMBean defaultImpl = (DefaultMBean) Proxy.newProxyInstance(
+                DefaultMBean.class.getClassLoader(),
+                new Class<?>[] {DefaultMBean.class},
+                new InvocationHandler(){
+                    public Object invoke(Object proxy, Method method, Object[] args) {
+                        return null;
+                    }
+                });
+        DynamicMBean mbean = new StandardMBean(defaultImpl, DefaultMBean.class);
+        MBeanOperationInfo[] ops = mbean.getMBeanInfo().getOperations();
+        for (MBeanOperationInfo op : ops) {
+            String name = op.getName();
+            Expect expect =
+                    DefaultMBean.class.getMethod(name).getAnnotation(Expect.class);
+            Descriptor opd = op.getDescriptor();
+            List<String> fields = new ArrayList<String>();
+            for (String fieldName : opd.getFieldNames()) {
+                Object value = opd.getFieldValue(fieldName);
+                String s = Arrays.deepToString(new Object[] {value});
+                s = s.substring(1, s.length() - 1);
+                fields.add(fieldName + "=" + s);
+            }
+            Descriptor opds = new ImmutableDescriptor(fields.toArray(new String[0]));
+            Descriptor expd = new ImmutableDescriptor(expect.value());
+            if (opds.equals(expd))
+                System.out.println("OK: op " + name + ": " + opds);
+            else {
+                String failure = "Bad descriptor for op " + name + ": " +
+                        "expected " + expd + ", got " + opds;
+                System.out.println("NOT OK: " + failure);
+                failed = failure;
+            }
+        }
+        System.out.println();
+
         if (failed == null)
             System.out.println("Test passed");
-        else if (true)
+        else
             throw new Exception("TEST FAILED: " + failed);
-        else
-            System.out.println("Test disabled until 6221321 implemented");
     }
 
     private static void check(MBeanServer mbs, ObjectName on) throws Exception {
@@ -295,151 +433,4 @@
         for (DescriptorRead x : xx)
             check(x);
     }
-
-    public static class AnnotatedMBean extends StandardMBean {
-        <T> AnnotatedMBean(T resource, Class<T> interfaceClass, boolean mx) {
-            super(resource, interfaceClass, mx);
-        }
-
-        private static final String[] attrPrefixes = {"get", "set", "is"};
-
-        protected void cacheMBeanInfo(MBeanInfo info) {
-            MBeanAttributeInfo[] attrs = info.getAttributes();
-            MBeanOperationInfo[] ops = info.getOperations();
-
-            for (int i = 0; i < attrs.length; i++) {
-                MBeanAttributeInfo attr = attrs[i];
-                String name = attr.getName();
-                Descriptor d = attr.getDescriptor();
-                Method m;
-                if ((m = getMethod("get" + name)) != null)
-                    d = ImmutableDescriptor.union(d, descriptorFor(m));
-                if (attr.getType().equals("boolean") &&
-                        (m = getMethod("is" + name)) != null)
-                    d = ImmutableDescriptor.union(d, descriptorFor(m));
-                if ((m = getMethod("set" + name, attr)) != null)
-                    d = ImmutableDescriptor.union(d, descriptorFor(m));
-                if (!d.equals(attr.getDescriptor())) {
-                    attrs[i] =
-                        new MBeanAttributeInfo(name, attr.getType(),
-                            attr.getDescription(), attr.isReadable(),
-                            attr.isWritable(), attr.isIs(), d);
-                }
-            }
-
-            for (int i = 0; i < ops.length; i++) {
-                MBeanOperationInfo op = ops[i];
-                String name = op.getName();
-                Descriptor d = op.getDescriptor();
-                MBeanParameterInfo[] params = op.getSignature();
-                Method m = getMethod(name, params);
-                if (m != null) {
-                    d = ImmutableDescriptor.union(d, descriptorFor(m));
-                    Annotation[][] annots = m.getParameterAnnotations();
-                    for (int pi = 0; pi < params.length; pi++) {
-                        MBeanParameterInfo param = params[pi];
-                        Descriptor pd =
-                                ImmutableDescriptor.union(param.getDescriptor(),
-                                    descriptorFor(annots[pi]));
-                        params[pi] = new MBeanParameterInfo(param.getName(),
-                                param.getType(), param.getDescription(), pd);
-                    }
-                    op = new MBeanOperationInfo(op.getName(),
-                            op.getDescription(), params, op.getReturnType(),
-                            op.getImpact(), d);
-                    if (!ops[i].equals(op))
-                        ops[i] = op;
-                }
-            }
-
-            Descriptor id = descriptorFor(getMBeanInterface());
-            info = new MBeanInfo(info.getClassName(), info.getDescription(),
-                    attrs, info.getConstructors(), ops, info.getNotifications(),
-                    ImmutableDescriptor.union(id, info.getDescriptor()));
-            super.cacheMBeanInfo(info);
-        }
-
-        private Descriptor descriptorFor(AnnotatedElement x) {
-            Annotation[] annots = x.getAnnotations();
-            return descriptorFor(annots);
-        }
-
-        private Descriptor descriptorFor(Annotation[] annots) {
-            if (annots.length == 0)
-                return ImmutableDescriptor.EMPTY_DESCRIPTOR;
-            Map<String, Object> descriptorMap = new HashMap<String, Object>();
-            for (Annotation a : annots) {
-                Class<? extends Annotation> c = a.annotationType();
-                Method[] elements = c.getMethods();
-                for (Method element : elements) {
-                    DescriptorKey key =
-                        element.getAnnotation(DescriptorKey.class);
-                    if (key != null) {
-                        String name = key.value();
-                        Object value;
-                        try {
-                            value = element.invoke(a);
-                        } catch (Exception e) {
-                            // we don't expect this
-                            throw new RuntimeException(e);
-                        }
-                        Object oldValue = descriptorMap.put(name, value);
-                        if (oldValue != null && !oldValue.equals(value)) {
-                            final String msg =
-                                "Inconsistent values for descriptor field " +
-                                name + " from annotations: " + value + " :: " +
-                                oldValue;
-                            throw new IllegalArgumentException(msg);
-                        }
-                    }
-                }
-            }
-            if (descriptorMap.isEmpty())
-                return ImmutableDescriptor.EMPTY_DESCRIPTOR;
-            else
-                return new ImmutableDescriptor(descriptorMap);
-        }
-
-        private Method getMethod(String name, MBeanFeatureInfo... params) {
-            Class<?> intf = getMBeanInterface();
-            ClassLoader loader = intf.getClassLoader();
-            Class[] classes = new Class[params.length];
-            for (int i = 0; i < params.length; i++) {
-                MBeanFeatureInfo param = params[i];
-                Descriptor d = param.getDescriptor();
-                String type = (String) d.getFieldValue("originalType");
-                if (type == null) {
-                    if (param instanceof MBeanAttributeInfo)
-                        type = ((MBeanAttributeInfo) param).getType();
-                    else
-                        type = ((MBeanParameterInfo) param).getType();
-                }
-                Class<?> c = primitives.get(type);
-                if (c == null) {
-                    try {
-                        c = Class.forName(type, false, loader);
-                    } catch (ClassNotFoundException e) {
-                        return null;
-                    }
-                }
-                classes[i] = c;
-            }
-            try {
-                return intf.getMethod(name, classes);
-            } catch (Exception e) {
-                return null;
-            }
-        }
-
-        private static final Map<String, Class<?>> primitives =
-                new HashMap<String, Class<?>>();
-        static {
-            for (Class<?> c :
-                    new Class[] {boolean.class, byte.class, short.class,
-                                 int.class, long.class, float.class,
-                                 double.class, char.class, void.class}) {
-                primitives.put(c.getName(), c);
-            }
-        }
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/MBeanServer/AttributeListMapTest.java	Thu Nov 13 11:15:01 2008 -0800
@@ -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	Thu Nov 13 11:15:01 2008 -0800
@@ -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/context/ContextForwarderTest.java	Thu Nov 13 11:15:01 2008 -0800
@@ -0,0 +1,103 @@
+/*
+ * 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 5072267
+ * @summary Test that a context forwarder can be created and then installed.
+ * @author Eamonn McManus
+ */
+
+/* The specific thing we're testing for is that the forwarder can be created
+ * with a null "next", and then installed with a real "next".  An earlier
+ * defect meant that in this case the simulated jmx.context// namespace had a
+ * null handler that never changed.
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.management.ClientContext;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+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;
+import javax.management.remote.MBeanServerForwarder;
+
+public class ContextForwarderTest {
+    private static String failure;
+
+    public static void main(String[] args) throws Exception {
+        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
+        Map<String, String> env = new HashMap<String, String>();
+        env.put(JMXConnectorServer.CONTEXT_FORWARDER, "false");
+        JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
+                url, env, mbs);
+        MBeanServerForwarder sysMBSF = cs.getSystemMBeanServerForwarder();
+        MBeanServerForwarder mbsf = ClientContext.newContextForwarder(mbs, sysMBSF);
+        sysMBSF.setMBeanServer(mbsf);
+
+        int localCount = mbs.getMBeanCount();
+
+        cs.start();
+        try {
+            JMXConnector cc = JMXConnectorFactory.connect(cs.getAddress());
+            MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+            mbsc = ClientContext.withContext(mbsc, "foo", "bar");
+            int contextCount = mbsc.getMBeanCount();
+            if (localCount + 1 != contextCount) {
+                fail("Local MBean count %d, context MBean count %d",
+                        localCount, contextCount);
+            }
+            Set<ObjectName> localNames =
+                    new TreeSet<ObjectName>(mbs.queryNames(null, null));
+            ObjectName contextNamespaceObjectName =
+                    new ObjectName(ClientContext.NAMESPACE + "//:type=JMXNamespace");
+            if (!localNames.add(contextNamespaceObjectName))
+                fail("Local names already contained context namespace handler");
+            Set<ObjectName> contextNames = mbsc.queryNames(null, null);
+            if (!localNames.equals(contextNames)) {
+                fail("Name set differs locally and in context: " +
+                        "local: %s; context: %s", localNames, contextNames);
+            }
+        } finally {
+            cs.stop();
+        }
+        if (failure != null)
+            throw new Exception("TEST FAILED: " + failure);
+        else
+            System.out.println("TEST PASSED");
+    }
+
+    private static void fail(String msg, Object... params) {
+        failure = String.format(msg, params);
+        System.out.println("FAIL: " + failure);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/ContextTest.java	Thu Nov 13 11:15:01 2008 -0800
@@ -0,0 +1,534 @@
+/*
+ * 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 ContextTest
+ * @bug 5072267
+ * @summary Test client contexts.
+ * @author Eamonn McManus
+ * TODO: Try registering with a null name replaced by preRegister (for example
+ * from the MLet class) and see if it now works.
+ */
+
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.ClientContext;
+import javax.management.DynamicMBean;
+import javax.management.JMX;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+import javax.management.loading.MLet;
+import javax.management.namespace.JMXNamespace;
+
+import javax.management.remote.MBeanServerForwarder;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+
+public class ContextTest {
+    private static String failure;
+    private static final Map<String, String> emptyContext = emptyMap();
+
+    public static interface ShowContextMBean {
+        public Map<String, String> getContext();
+        public Map<String, String> getCreationContext();
+        public Set<String> getCalledOps();
+        public String getThing();
+        public void setThing(String x);
+        public int add(int x, int y);
+    }
+
+    public static class ShowContext
+            extends NotificationBroadcasterSupport
+            implements ShowContextMBean, MBeanRegistration {
+        private final Map<String, String> creationContext;
+        private final Set<String> calledOps = new HashSet<String>();
+
+        public ShowContext() {
+            creationContext = getContext();
+        }
+
+        public Map<String, String> getContext() {
+            return ClientContext.getContext();
+        }
+
+        public Map<String, String> getCreationContext() {
+            return creationContext;
+        }
+
+        public Set<String> getCalledOps() {
+            return calledOps;
+        }
+
+        public String getThing() {
+            return "x";
+        }
+
+        public void setThing(String x) {
+        }
+
+        public int add(int x, int y) {
+            return x + y;
+        }
+
+        public ObjectName preRegister(MBeanServer server, ObjectName name) {
+            assertEquals("preRegister context", creationContext, getContext());
+            calledOps.add("preRegister");
+            return name;
+        }
+
+        public void postRegister(Boolean registrationDone) {
+            assertEquals("postRegister context", creationContext, getContext());
+            calledOps.add("postRegister");
+        }
+
+        // The condition checked here is not guaranteed universally true,
+        // but is true every time we unregister an instance of this MBean
+        // in this test.
+        public void preDeregister() throws Exception {
+            assertEquals("preDeregister context", creationContext, getContext());
+        }
+
+        public void postDeregister() {
+            assertEquals("postDeregister context", creationContext, getContext());
+        }
+
+        // Same remark as for preDeregister
+        @Override
+        public MBeanNotificationInfo[] getNotificationInfo() {
+            calledOps.add("getNotificationInfo");
+            return super.getNotificationInfo();
+        }
+
+        @Override
+        public void addNotificationListener(
+                NotificationListener listener, NotificationFilter filter, Object handback) {
+            calledOps.add("addNotificationListener");
+            super.addNotificationListener(listener, filter, handback);
+        }
+
+        @Override
+        public void removeNotificationListener(
+                NotificationListener listener)
+        throws ListenerNotFoundException {
+            calledOps.add("removeNL1");
+            super.removeNotificationListener(listener);
+        }
+
+        @Override
+        public void removeNotificationListener(
+                NotificationListener listener, NotificationFilter filter, Object handback)
+        throws ListenerNotFoundException {
+            calledOps.add("removeNL3");
+            super.removeNotificationListener(listener, filter, handback);
+        }
+    }
+
+    private static class LogRecord {
+        final String op;
+        final Object[] params;
+        final Map<String, String> context;
+        LogRecord(String op, Object[] params, Map<String, String> context) {
+            this.op = op;
+            this.params = params;
+            this.context = context;
+        }
+
+        @Override
+        public String toString() {
+            return op + Arrays.deepToString(params) + " " + context;
+        }
+    }
+
+    /*
+     * InvocationHandler that forwards all methods to a contained object
+     * but also records each forwarded method.  This allows us to check
+     * that the appropriate methods were called with the appropriate
+     * parameters.  It's similar to what's typically available in
+     * Mock Object frameworks.
+     */
+    private static class LogIH implements InvocationHandler {
+        private final Object wrapped;
+        Queue<LogRecord> log = new LinkedList<LogRecord>();
+
+        LogIH(Object wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+            if (method.getDeclaringClass() != Object.class) {
+                LogRecord lr =
+                    new LogRecord(
+                        method.getName(), args, ClientContext.getContext());
+                log.add(lr);
+            }
+            try {
+                return method.invoke(wrapped, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    private static <T> T newSnoop(Class<T> wrappedClass, LogIH logIH) {
+        return wrappedClass.cast(Proxy.newProxyInstance(
+                wrappedClass.getClassLoader(),
+                new Class<?>[] {wrappedClass},
+                logIH));
+    }
+
+    public static void main(String[] args) throws Exception {
+        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+        System.out.println(mbs.queryNames(null, null));
+        ObjectName name = new ObjectName("a:b=c");
+        mbs.registerMBean(new ShowContext(), name);
+        final ShowContextMBean show =
+                JMX.newMBeanProxy(mbs, name, ShowContextMBean.class);
+
+        // Test local setting and getting within the MBeanServer
+
+        assertEquals("initial context", emptyContext, show.getContext());
+        ClientContext.doWithContext(singletonMap("foo", "bar"), new Callable<Void>() {
+            public Void call() {
+                assertEquals("context in doWithContext",
+                        singletonMap("foo", "bar"), show.getContext());
+                return null;
+            }
+        });
+        assertEquals("initial context after doWithContext",
+                emptyContext, show.getContext());
+        String got = ClientContext.doWithContext(
+                singletonMap("foo", "baz"), new Callable<String>() {
+            public String call() {
+                return ClientContext.getContext().get("foo");
+            }
+        });
+        assertEquals("value extracted from context", "baz", got);
+
+        Map<String, String> combined = ClientContext.doWithContext(
+                singletonMap("foo", "baz"), new Callable<Map<String, String>>() {
+            public Map<String, String> call() throws Exception {
+                return ClientContext.doWithContext(
+                        singletonMap("fred", "jim"),
+                        new Callable<Map<String, String>>() {
+                    public Map<String, String> call() {
+                        return ClientContext.getContext();
+                    }
+                });
+            }
+        });
+        assertEquals("nested doWithContext context",
+                singletonMap("fred", "jim"), combined);
+
+        final String ugh = "a!\u00c9//*=:\"% ";
+        ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable<Void>() {
+            public Void call() {
+                assertEquals("context with tricky encoding",
+                        singletonMap(ugh, ugh), show.getContext());
+                return null;
+            }
+        });
+        Map<String, String> ughMap = new TreeMap<String, String>();
+        ughMap.put(ugh, ugh);
+        ughMap.put("fred", "jim");
+        // Since this is a TreeMap and "fred" is greater than ugh (which begins
+        // with "a"), we will see the encoding of ugh first in the output string.
+        String encoded = ClientContext.encode(ughMap);
+        String expectedUghCoding = "a%21%C3%89%2F%2F%2A%3D%3A%22%25+";
+        String expectedUghMapCoding =
+                ClientContext.NAMESPACE + "//" + expectedUghCoding + "=" +
+                expectedUghCoding + ";fred=jim";
+        assertEquals("hairy context encoded as string",
+                expectedUghMapCoding, encoded);
+
+        // Wrap the MBeanServer with a context MBSF so we can test withContext.
+        // Also check the simulated namespace directly.
+
+        LogIH mbsIH = new LogIH(mbs);
+        MBeanServer snoopMBS = newSnoop(MBeanServer.class, mbsIH);
+        MBeanServerForwarder ctxMBS =
+                ClientContext.newContextForwarder(snoopMBS, null);
+
+        // The MBSF returned by ClientContext is actually a compound of two
+        // forwarders, but that is supposed to be hidden by changing the
+        // behaviour of get/setMBeanServer.  Check that it is indeed so.
+        assertEquals("next MBS of context forwarder",
+                snoopMBS, ctxMBS.getMBeanServer());
+        // If the above assertion fails you may get a confusing message
+        // because the toString() of the two objects is likely to be the same
+        // so it will look as if they should be equal.
+        ctxMBS.setMBeanServer(null);
+        assertEquals("next MBS of context forwarder after setting it null",
+                null, ctxMBS.getMBeanServer());
+        ctxMBS.setMBeanServer(snoopMBS);
+
+        // The MBSF should look the same as the original MBeanServer except
+        // that it has the JMXNamespace for the simulated namespace.
+
+        Set<ObjectName> origNames = mbs.queryNames(null, null);
+        Set<ObjectName> mbsfNames = ctxMBS.queryNames(null, null);
+        assertEquals("number of MBeans returned by queryNames within forwarder",
+                origNames.size() + 1, mbsfNames.size());
+        assertEquals("MBeanCount within forwarder",
+                mbsfNames.size(), ctxMBS.getMBeanCount());
+        assertCalled(mbsIH, "queryNames", emptyContext);
+        assertCalled(mbsIH, "getMBeanCount", emptyContext);
+
+        ObjectName ctxNamespaceName = new ObjectName(
+                ClientContext.NAMESPACE + "//:" + JMXNamespace.TYPE_ASSIGNMENT);
+        origNames.add(ctxNamespaceName);
+        assertEquals("MBeans within forwarder", origNames, mbsfNames);
+        Set<String> domains = new HashSet<String>(Arrays.asList(ctxMBS.getDomains()));
+        assertEquals("domains include context namespace MBean",
+                true, domains.contains(ClientContext.NAMESPACE + "//"));
+        assertCalled(mbsIH, "getDomains", emptyContext);
+
+        // Now test ClientContext.withContext.
+
+        MBeanServer ughMBS = ClientContext.withContext(ctxMBS, ugh, ugh);
+
+        ShowContextMBean ughshow =
+                JMX.newMBeanProxy(ughMBS, name, ShowContextMBean.class);
+        Map<String, String> ughCtx = ughshow.getContext();
+        Map<String, String> ughExpect = singletonMap(ugh, ugh);
+        assertEquals("context seen by MBean accessed within namespace",
+                ughExpect, ughCtx);
+        assertCalled(mbsIH, "getAttribute", ughExpect, name, "Context");
+
+        MBeanServer cmbs = ClientContext.withContext(
+                ctxMBS, "mickey", "mouse");
+        ShowContextMBean cshow =
+                JMX.newMBeanProxy(cmbs, name, ShowContextMBean.class);
+        assertEquals("context seen by MBean accessed within namespace",
+                singletonMap("mickey", "mouse"), cshow.getContext());
+
+        MBeanServer ccmbs = ClientContext.withContext(
+                cmbs, "donald", "duck");
+        ShowContextMBean ccshow =
+                JMX.newMBeanProxy(ccmbs, name, ShowContextMBean.class);
+        Map<String, String> disney = new HashMap<String, String>();
+        disney.put("mickey", "mouse");
+        disney.put("donald", "duck");
+        assertEquals("context seen by MBean in nested namespace",
+                disney, ccshow.getContext());
+
+        // Test that all MBS ops produce reasonable results
+
+        ObjectName logger = new ObjectName("a:type=Logger");
+        DynamicMBean showMBean =
+                new StandardMBean(new ShowContext(), ShowContextMBean.class);
+        LogIH mbeanLogIH = new LogIH(showMBean);
+        DynamicMBean logMBean = newSnoop(DynamicMBean.class, mbeanLogIH);
+        ObjectInstance loggerOI = ccmbs.registerMBean(logMBean, logger);
+        assertEquals("ObjectName returned by createMBean",
+                logger, loggerOI.getObjectName());
+
+        // We get an getMBeanInfo call to determine the className in the
+        // ObjectInstance to return from registerMBean.
+        assertCalled(mbeanLogIH, "getMBeanInfo", disney);
+
+        ccmbs.getAttribute(logger, "Thing");
+        assertCalled(mbeanLogIH, "getAttribute", disney);
+
+        ccmbs.getAttributes(logger, new String[] {"Thing", "Context"});
+        assertCalled(mbeanLogIH, "getAttributes", disney);
+
+        ccmbs.setAttribute(logger, new Attribute("Thing", "bar"));
+        assertCalled(mbeanLogIH, "setAttribute", disney);
+
+        ccmbs.setAttributes(logger, new AttributeList(
+                Arrays.asList(new Attribute("Thing", "baz"))));
+        assertCalled(mbeanLogIH, "setAttributes", disney);
+
+        ccmbs.getMBeanInfo(logger);
+        assertCalled(mbeanLogIH, "getMBeanInfo", disney);
+
+        Set<ObjectName> names = ccmbs.queryNames(null, null);
+        Set<ObjectName> expectedNames = new HashSet<ObjectName>(
+                Collections.singleton(MBeanServerDelegate.DELEGATE_NAME));
+        assertEquals("context namespace query includes expected names",
+                true, names.containsAll(expectedNames));
+
+        Set<ObjectName> nsNames = ccmbs.queryNames(new ObjectName("*//:*"), null);
+        Set<ObjectName> expectedNsNames = new HashSet<ObjectName>(
+                Arrays.asList(
+                new ObjectName(ClientContext.NAMESPACE +
+                ObjectName.NAMESPACE_SEPARATOR + ":" +
+                JMXNamespace.TYPE_ASSIGNMENT)));
+        assertEquals("context namespace query includes namespace MBean",
+                true, nsNames.containsAll(expectedNsNames));
+
+
+
+        Set<ObjectInstance> insts = ccmbs.queryMBeans(
+                MBeanServerDelegate.DELEGATE_NAME, null);
+        assertEquals("size of set from MBeanServerDelegate query", 1, insts.size());
+        assertEquals("ObjectName from MBeanServerDelegate query",
+                MBeanServerDelegate.DELEGATE_NAME,
+                insts.iterator().next().getObjectName());
+
+        ObjectName createdName = new ObjectName("a:type=Created");
+        ObjectInstance createdOI =
+                ccmbs.createMBean(ShowContext.class.getName(), createdName);
+        assertEquals("class name from createMBean",
+                ShowContext.class.getName(), createdOI.getClassName());
+        assertEquals("ObjectName from createMBean",
+                createdName, createdOI.getObjectName());
+        assertEquals("context within createMBean",
+                disney, ccmbs.getAttribute(createdName, "CreationContext"));
+
+        NotificationListener nothingListener = new NotificationListener() {
+            public void handleNotification(Notification n, Object h) {}
+        };
+        ccmbs.addNotificationListener(createdName, nothingListener, null, null);
+        ccmbs.removeNotificationListener(createdName, nothingListener, null, null);
+        ccmbs.addNotificationListener(createdName, nothingListener, null, null);
+        ccmbs.removeNotificationListener(createdName, nothingListener);
+        Set<String> expectedOps = new HashSet<String>(Arrays.asList(
+                "preRegister", "postRegister", "addNotificationListener",
+                "removeNL1", "removeNL3", "getNotificationInfo"));
+        assertEquals("operations called on MBean",
+                expectedOps, ccmbs.getAttribute(createdName, "CalledOps"));
+
+        assertEquals("ClassLoader for MBean",
+                ShowContext.class.getClassLoader(),
+                ccmbs.getClassLoaderFor(createdName));
+
+        assertEquals("isRegistered", true, ccmbs.isRegistered(createdName));
+        assertEquals("isInstanceOf", true, ccmbs.isInstanceOf(createdName,
+                ShowContext.class.getName()));
+        assertEquals("isInstanceOf", false, ccmbs.isInstanceOf(createdName,
+                DynamicMBean.class.getName()));
+        ccmbs.unregisterMBean(createdName);
+        assertEquals("isRegistered after unregister",
+                false, ccmbs.isRegistered(createdName));
+
+        MLet mlet = new MLet();
+        ObjectName defaultMLetName = new ObjectName("DefaultDomain:type=MLet");
+
+        ccmbs.registerMBean(mlet, defaultMLetName);
+
+        assertEquals("getClassLoader", mlet, ccmbs.getClassLoader(defaultMLetName));
+
+        assertEquals("number of MBean operations", 0, mbeanLogIH.log.size());
+
+        // Test that contexts still work when we can't combine two encoded contexts.
+        // Here, we wrap cmbs (mickey=mouse) so that ccmbs2 (donald=duck) cannot
+        // see that it already contains a context and therefore cannot combine
+        // into mickey=mouse;donald=duck.  We don't actually use the snoop
+        // capabilities of the returned object -- we just want an opaque
+        // MBeanServer wrapper
+        MBeanServer cmbs2 = newSnoop(MBeanServer.class, new LogIH(cmbs));
+        MBeanServer ccmbs2 = ClientContext.withContext(cmbs2, "donald", "duck");
+        assertEquals("context when combination is impossible",
+                disney, ccmbs2.getAttribute(name, "Context"));
+
+        // Test failure cases of ClientContext.encode
+        final List<Map<String, String>> badEncodeArgs =
+                Arrays.asList(
+                    null,
+                    Collections.<String,String>singletonMap(null, "foo"),
+                    Collections.<String,String>singletonMap("foo", null));
+        for (Map<String, String> bad : badEncodeArgs) {
+            try {
+                String oops = ClientContext.encode(bad);
+                failed("ClientContext.encode(" + bad + ") should have failed: "
+                        + oops);
+            } catch (Exception e) {
+                assertEquals("Exception for ClientContext.encode(" + bad + ")",
+                        IllegalArgumentException.class, e.getClass());
+            }
+        }
+
+        // ADD NEW TESTS HERE ^^^
+
+        if (failure != null)
+            throw new Exception(failure);
+    }
+
+    private static void assertEquals(String what, Object x, Object y) {
+        if (!equal(x, y))
+            failed(what + ": expected " + string(x) + "; got " + string(y));
+    }
+
+    private static boolean equal(Object x, Object y) {
+        if (x == y)
+            return true;
+        if (x == null || y == null)
+            return false;
+        if (x.getClass().isArray())
+            return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
+        return x.equals(y);
+    }
+
+    private static String string(Object x) {
+        String s = Arrays.deepToString(new Object[] {x});
+        return s.substring(1, s.length() - 1);
+    }
+
+    private static void assertCalled(
+            LogIH logIH, String op, Map<String, String> expectedContext) {
+        assertCalled(logIH, op, expectedContext, (Object[]) null);
+    }
+
+    private static void assertCalled(
+            LogIH logIH, String op, Map<String, String> expectedContext,
+            Object... params) {
+        LogRecord lr = logIH.log.remove();
+        assertEquals("called operation", op, lr.op);
+        if (params != null)
+            assertEquals("operation parameters", params, lr.params);
+        assertEquals("operation context", expectedContext, lr.context);
+    }
+
+    private static void failed(String why) {
+        failure = why;
+        new Throwable("FAILED: " + why).printStackTrace(System.out);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/LocaleAwareBroadcasterTest.java	Thu Nov 13 11:15:01 2008 -0800
@@ -0,0 +1,328 @@
+/*
+ * 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 5072267
+ * @summary Test that an MBean can handle localized Notification messages.
+ * @author Eamonn McManus
+ */
+
+import java.util.Collections;
+import java.util.ListResourceBundle;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import javax.management.ClientContext;
+import javax.management.JMX;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.SendNotification;
+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 LocaleAwareBroadcasterTest {
+    static final ObjectName mbeanName = ObjectName.valueOf("d:type=LocaleAware");
+
+    static final String
+            messageKey = "broken.window",
+            defaultMessage = "broken window",
+            frenchMessage = "fen\u00eatre bris\u00e9e",
+            irishMessage = "fuinneog briste";
+
+    public static class Bundle extends ListResourceBundle {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[][] {
+                {messageKey, defaultMessage},
+            };
+        }
+    }
+
+    public static class Bundle_fr extends ListResourceBundle {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[][] {
+                {messageKey, frenchMessage},
+            };
+        }
+    }
+
+    public static class Bundle_ga extends ListResourceBundle {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[][] {
+                {messageKey, irishMessage},
+            };
+        }
+    }
+
+    static volatile String failure;
+
+    public static interface LocaleAwareMBean {
+        public void sendNotification(Notification n);
+    }
+
+    public static class LocaleAware
+            implements LocaleAwareMBean, NotificationEmitter, SendNotification {
+
+        private final ConcurrentMap<Locale, NotificationBroadcasterSupport>
+                localeToEmitter = newConcurrentMap();
+
+        public void sendNotification(Notification n) {
+            for (Map.Entry<Locale, NotificationBroadcasterSupport> entry :
+                    localeToEmitter.entrySet()) {
+                Notification localizedNotif =
+                        localizeNotification(n, entry.getKey());
+                entry.getValue().sendNotification(localizedNotif);
+            }
+        }
+
+        public void addNotificationListener(
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback)
+                throws IllegalArgumentException {
+            Locale locale = ClientContext.getLocale();
+            NotificationBroadcasterSupport broadcaster;
+            broadcaster = localeToEmitter.get(locale);
+            if (broadcaster == null) {
+                broadcaster = new NotificationBroadcasterSupport();
+                NotificationBroadcasterSupport old =
+                        localeToEmitter.putIfAbsent(locale, broadcaster);
+                if (old != null)
+                    broadcaster = old;
+            }
+            broadcaster.addNotificationListener(listener, filter, handback);
+        }
+
+        public void removeNotificationListener(NotificationListener listener)
+                throws ListenerNotFoundException {
+            Locale locale = ClientContext.getLocale();
+            NotificationBroadcasterSupport broadcaster =
+                    localeToEmitter.get(locale);
+            if (broadcaster == null)
+                throw new ListenerNotFoundException();
+            broadcaster.removeNotificationListener(listener);
+        }
+
+        public void removeNotificationListener(
+                NotificationListener listener,
+                NotificationFilter filter,
+