6757225: MXBean: Incorrect type names for parametrized types, dealing with arrays
authoremcmanus
Wed, 08 Oct 2008 18:38:25 +0200
changeset 1446 605e30465515
parent 1341 16ff0dbfd27e
child 1447 95d5bd511ec2
6757225: MXBean: Incorrect type names for parametrized types, dealing with arrays Reviewed-by: sjiang
jdk/src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java
jdk/src/share/classes/javax/management/event/EventClient.java
jdk/src/share/classes/javax/management/event/FetchingEventRelay.java
jdk/src/share/classes/javax/management/monitor/Monitor.java
jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java
jdk/test/javax/management/mxbean/TypeNameTest.java
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java	Mon Oct 06 09:17:35 2008 -0700
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/MXBeanIntrospector.java	Wed Oct 08 18:38:25 2008 +0200
@@ -32,6 +32,7 @@
 import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Map;
 import java.util.WeakHashMap;
@@ -390,7 +391,31 @@
         if (type instanceof Class)
             return ((Class) type).getName();
         else
-            return type.toString();
+            return genericTypeString(type);
+    }
+
+    private static String genericTypeString(Type type) {
+        if (type instanceof Class<?>) {
+            Class<?> c = (Class<?>) type;
+            if (c.isArray())
+                return genericTypeString(c.getComponentType()) + "[]";
+            else
+                return c.getName();
+        } else if (type instanceof GenericArrayType) {
+            GenericArrayType gat = (GenericArrayType) type;
+            return genericTypeString(gat.getGenericComponentType()) + "[]";
+        } else if (type instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType) type;
+            StringBuilder sb = new StringBuilder();
+            sb.append(genericTypeString(pt.getRawType())).append("<");
+            String sep = "";
+            for (Type t : pt.getActualTypeArguments()) {
+                sb.append(sep).append(genericTypeString(t));
+                sep = ", ";
+            }
+            return sb.append(">").toString();
+        } else
+            return "???";
     }
 
     private final PerInterfaceMap<ConvertingMethod>
--- a/jdk/src/share/classes/javax/management/event/EventClient.java	Mon Oct 06 09:17:35 2008 -0700
+++ b/jdk/src/share/classes/javax/management/event/EventClient.java	Wed Oct 08 18:38:25 2008 +0200
@@ -265,12 +265,20 @@
             public ScheduledThreadPoolExecutor createThreadPool(ThreadGroup group) {
                 ThreadFactory daemonThreadFactory = new DaemonThreadFactory(
                         "JMX EventClient lease renewer %d");
-                ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(
-                        20, daemonThreadFactory);
-                exec.setKeepAliveTime(1, TimeUnit.SECONDS);
-                exec.allowCoreThreadTimeOut(true);
-                exec.setRemoveOnCancelPolicy(true);
-                return exec;
+                ScheduledThreadPoolExecutor executor =
+                        new ScheduledThreadPoolExecutor(20, daemonThreadFactory);
+                executor.setKeepAliveTime(1, TimeUnit.SECONDS);
+                executor.allowCoreThreadTimeOut(true);
+                executor.setRemoveOnCancelPolicy(true);
+                // 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.
+                // Since the job references the LeaseRenewer which references
+                // 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.
+                return executor;
             }
         };
         return leaseRenewerThreadPool.getThreadPoolExecutor(create);
@@ -381,7 +389,7 @@
             listenerId =
                     eventClientDelegate.addListener(clientId, name, filter);
         } catch (EventClientNotFoundException ecnfe) {
-            final IOException ioe = new IOException();
+            final IOException ioe = new IOException(ecnfe.getMessage());
             ioe.initCause(ecnfe);
             throw ioe;
         }
@@ -488,7 +496,7 @@
             listenerId =
                     eventClientDelegate.addSubscriber(clientId, name, filter);
         } catch (EventClientNotFoundException ecnfe) {
-            final IOException ioe = new IOException();
+            final IOException ioe = new IOException(ecnfe.getMessage());
             ioe.initCause(ecnfe);
             throw ioe;
         }
--- a/jdk/src/share/classes/javax/management/event/FetchingEventRelay.java	Mon Oct 06 09:17:35 2008 -0700
+++ b/jdk/src/share/classes/javax/management/event/FetchingEventRelay.java	Wed Oct 08 18:38:25 2008 +0200
@@ -91,7 +91,7 @@
      * the fetching.
      *
      * @param delegate The {@code EventClientDelegateMBean} to work with.
-     * @param executor Used to do the fetching. A new thread is created if
+     * @param fetchExecutor Used to do the fetching. A new thread is created if
      * {@code null}.
      * @throws IOException If failed to work with the {@code delegate}.
      * @throws MBeanException if unable to add a client to the remote
@@ -101,12 +101,12 @@
      * @throws IllegalArgumentException If {@code delegate} is {@code null}.
      */
     public FetchingEventRelay(EventClientDelegateMBean delegate,
-            Executor executor) throws IOException, MBeanException {
+            Executor fetchExecutor) throws IOException, MBeanException {
         this(delegate,
                 DEFAULT_BUFFER_SIZE,
                 DEFAULT_WAITING_TIMEOUT,
                 DEFAULT_MAX_NOTIFICATIONS,
-                executor);
+                fetchExecutor);
     }
 
     /**
@@ -120,7 +120,7 @@
      * @param timeout The waiting time in millseconds when fetching
      * notifications from an {@code EventClientDelegateMBean}.
      * @param maxNotifs The maximum notifications to fetch every time.
-     * @param executor Used to do the fetching. A new thread is created if
+     * @param fetchExecutor Used to do the fetching. A new thread is created if
      * {@code null}.
      * @throws IOException if failed to communicate with the {@code delegate}.
      * @throws MBeanException if unable to add a client to the remote
@@ -133,12 +133,12 @@
             int bufferSize,
             long timeout,
             int maxNotifs,
-            Executor executor) throws IOException, MBeanException {
+            Executor fetchExecutor) throws IOException, MBeanException {
         this(delegate,
                 bufferSize,
                 timeout,
                 maxNotifs,
-                executor,
+                fetchExecutor,
                 FetchingEventForwarder.class.getName(),
                 new Object[] {bufferSize},
                 new String[] {int.class.getName()});
@@ -155,7 +155,7 @@
      * @param timeout The waiting time in millseconds when fetching
      * notifications from an {@code EventClientDelegateMBean}.
      * @param maxNotifs The maximum notifications to fetch every time.
-     * @param executor Used to do the fetching.
+     * @param fetchExecutor Used to do the fetching.
      * @param forwarderName the class name of a user specific EventForwarder
      * to create in server to forward notifications to this object. The class
      * should be a subclass of the class {@link FetchingEventForwarder}.
@@ -174,7 +174,7 @@
             int bufferSize,
             long timeout,
             int maxNotifs,
-            Executor executor,
+            Executor fetchExecutor,
             String forwarderName,
             Object[] params,
             String[] sig) throws IOException, MBeanException {
@@ -184,11 +184,11 @@
                     bufferSize+" "+
                     timeout+" "+
                     maxNotifs+" "+
-                    executor+" "+
+                    fetchExecutor+" "+
                     forwarderName+" ");
         }
 
-        if(delegate == null) {
+        if (delegate == null) {
             throw new NullPointerException("Null EventClientDelegateMBean!");
         }
 
@@ -212,16 +212,16 @@
         this.timeout = timeout;
         this.maxNotifs = maxNotifs;
 
-        if (executor == null) {
-            ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(1,
-                    daemonThreadFactory);
-            stpe.setKeepAliveTime(1, TimeUnit.SECONDS);
-            stpe.allowCoreThreadTimeOut(true);
-            executor = stpe;
-            this.defaultExecutor = stpe;
+        if (fetchExecutor == null) {
+            ScheduledThreadPoolExecutor executor =
+                    new ScheduledThreadPoolExecutor(1, daemonThreadFactory);
+            executor.setKeepAliveTime(1, TimeUnit.SECONDS);
+            executor.allowCoreThreadTimeOut(true);
+            fetchExecutor = executor;
+            this.defaultExecutor = executor;
         } else
             this.defaultExecutor = null;
-        this.executor = executor;
+        this.fetchExecutor = fetchExecutor;
 
         startSequenceNumber = 0;
         fetchingJob = new MyJob();
@@ -258,7 +258,7 @@
 
     private class MyJob extends RepeatedSingletonJob {
         public MyJob() {
-            super(executor);
+            super(fetchExecutor);
         }
 
         public boolean isSuspended() {
@@ -368,7 +368,7 @@
     private String clientId;
     private boolean stopped = false;
 
-    private final Executor executor;
+    private final Executor fetchExecutor;
     private final ExecutorService defaultExecutor;
     private final MyJob fetchingJob;
 
--- a/jdk/src/share/classes/javax/management/monitor/Monitor.java	Mon Oct 06 09:17:35 2008 -0700
+++ b/jdk/src/share/classes/javax/management/monitor/Monitor.java	Wed Oct 08 18:38:25 2008 +0200
@@ -181,7 +181,7 @@
     /**
      * Executor Service.
      */
-    private static final ExecutorService executor;
+    private static final ThreadPoolExecutor executor;
     static {
         final String maximumPoolSizeSysProp = "jmx.x.monitor.maximum.pool.size";
         final String maximumPoolSizeStr = AccessController.doPrivileged(
@@ -218,7 +218,7 @@
                 TimeUnit.SECONDS,
                 new LinkedBlockingQueue<Runnable>(),
                 new DaemonThreadFactory("Executor"));
-        ((ThreadPoolExecutor)executor).allowCoreThreadTimeOut(true);
+        executor.allowCoreThreadTimeOut(true);
     }
 
     /**
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Mon Oct 06 09:17:35 2008 -0700
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Wed Oct 08 18:38:25 2008 +0200
@@ -71,9 +71,8 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -421,12 +420,12 @@
             public ThreadPoolExecutor createThreadPool(ThreadGroup group) {
                 ThreadFactory daemonThreadFactory = new DaemonThreadFactory(
                         "JMX RMIConnector listener dispatch %d");
-                ThreadPoolExecutor exec = new ThreadPoolExecutor(
+                ThreadPoolExecutor executor = new ThreadPoolExecutor(
                         1, 10, 1, TimeUnit.SECONDS,
-                        new LinkedBlockingDeque<Runnable>(),
+                        new LinkedBlockingQueue<Runnable>(),
                         daemonThreadFactory);
-                exec.allowCoreThreadTimeOut(true);
-                return exec;
+                executor.allowCoreThreadTimeOut(true);
+                return executor;
             }
         };
         return listenerDispatchThreadPool.getThreadPoolExecutor(create);
@@ -1503,7 +1502,7 @@
             super(period);
         }
 
-        public void gotIOException (IOException ioe) throws IOException {
+        public void gotIOException(IOException ioe) throws IOException {
             if (ioe instanceof NoSuchObjectException) {
                 // need to restart
                 super.gotIOException(ioe);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/mxbean/TypeNameTest.java	Wed Oct 08 18:38:25 2008 +0200
@@ -0,0 +1,97 @@
+/*
+ * 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 6757225
+ * @summary Test that type names in MXBeans match their spec.
+ * @author Eamonn McManus
+ */
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.Map;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+
+public class TypeNameTest {
+    public static interface TestMXBean {
+        public int getInt();
+        public String IntName = "int";
+
+        public Map<String, Integer> getMapSI();
+        public String MapSIName = "java.util.Map<java.lang.String, java.lang.Integer>";
+
+        public Map<String, int[]> getMapSInts();
+        public String MapSIntsName = "java.util.Map<java.lang.String, int[]>";
+
+        public List<List<int[]>> getListListInts();
+        public String ListListIntsName = "java.util.List<java.util.List<int[]>>";
+    }
+
+    private static InvocationHandler nullIH = new InvocationHandler() {
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            return null;
+        }
+    };
+
+    static String failure;
+
+    public static void main(String[] args) throws Exception {
+        TestMXBean testImpl = (TestMXBean) Proxy.newProxyInstance(
+                TestMXBean.class.getClassLoader(), new Class<?>[] {TestMXBean.class}, nullIH);
+        Object mxbean = new StandardMBean(testImpl, TestMXBean.class, true);
+        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+        ObjectName name = new ObjectName("a:b=c");
+        mbs.registerMBean(mxbean, name);
+        MBeanInfo mbi = mbs.getMBeanInfo(name);
+        MBeanAttributeInfo[] mbais = mbi.getAttributes();
+        for (MBeanAttributeInfo mbai : mbais) {
+            String attrName = mbai.getName();
+            String attrTypeName = (String) mbai.getDescriptor().getFieldValue("originalType");
+            String fieldName = attrName + "Name";
+            Field nameField = TestMXBean.class.getField(fieldName);
+            String expectedTypeName = (String) nameField.get(null);
+            if (expectedTypeName.equals(attrTypeName)) {
+                System.out.println("OK: " + attrName + ": " + attrTypeName);
+            } else {
+                failure = "For attribute " + attrName + " expected type name \"" +
+                        expectedTypeName + "\", found type name \"" + attrTypeName +
+                        "\"";
+                System.out.println("FAIL: " + failure);
+            }
+        }
+        if (failure == null)
+            System.out.println("TEST PASSED");
+        else
+            throw new Exception("TEST FAILED: " + failure);
+    }
+}