1 /*
   2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6175517 6278707 6318827 6305746 6392303 6600709 8010285
  27  * @summary General MXBean test.
  28  * @author Eamonn McManus
  29  * @author Jaroslav Bachorik
  30  * @modules java.desktop
  31  *          java.management
  32  * @run clean MXBeanTest MerlinMXBean TigerMXBean
  33  * @run build MXBeanTest MerlinMXBean TigerMXBean
  34  * @run main MXBeanTest
  35  */
  36 
  37 import java.lang.reflect.Array;
  38 import java.lang.reflect.Field;
  39 import java.lang.reflect.InvocationHandler;
  40 import java.lang.reflect.Method;
  41 import java.lang.reflect.Proxy;
  42 import java.util.Arrays;
  43 import java.util.Collection;
  44 import java.util.HashMap;
  45 import java.util.Iterator;
  46 import java.util.Map;
  47 import java.util.SortedMap;
  48 import javax.management.JMX;
  49 import javax.management.MBeanAttributeInfo;
  50 import javax.management.MBeanInfo;
  51 import javax.management.MBeanOperationInfo;
  52 import javax.management.MBeanParameterInfo;
  53 import javax.management.MBeanServer;
  54 import javax.management.MBeanServerConnection;
  55 import javax.management.MBeanServerFactory;
  56 import javax.management.MBeanServerInvocationHandler;
  57 import javax.management.NotCompliantMBeanException;
  58 import javax.management.ObjectName;
  59 import javax.management.StandardMBean;
  60 import javax.management.openmbean.ArrayType;
  61 import javax.management.openmbean.CompositeData;
  62 import javax.management.openmbean.CompositeDataInvocationHandler;
  63 import javax.management.openmbean.OpenType;
  64 import javax.management.openmbean.SimpleType;
  65 import javax.management.openmbean.TabularData;
  66 import javax.management.openmbean.TabularType;
  67 import javax.management.remote.JMXConnector;
  68 import javax.management.remote.JMXConnectorFactory;
  69 import javax.management.remote.JMXConnectorServer;
  70 import javax.management.remote.JMXConnectorServerFactory;
  71 import javax.management.remote.JMXServiceURL;
  72 
  73 public class MXBeanTest {
  74     public static void main(String[] args) throws Exception {
  75         testInterface(MerlinMXBean.class, false);
  76         testInterface(TigerMXBean.class, false);
  77         testInterface(MerlinMXBean.class, true);
  78         testInterface(TigerMXBean.class, true);
  79         testExplicitMXBean();
  80         testSubclassMXBean();
  81         testIndirectMXBean();
  82         testNonCompliantMXBean("Private", new Private());
  83         testNonCompliantMXBean("NonCompliant", new NonCompliant());
  84 
  85         if (failures == 0)
  86             System.out.println("Test passed");
  87         else
  88             throw new Exception("TEST FAILURES: " + failures);
  89     }
  90 
  91     private static int failures = 0;
  92 
  93     private static interface PrivateMXBean {
  94         public int[] getInts();
  95     }
  96 
  97     public static class Private implements PrivateMXBean {
  98         public int[] getInts() {
  99             return new int[]{1,2,3};
 100         }
 101     }
 102 
 103     public static interface NonCompliantMXBean {
 104         public boolean getInt();
 105         public boolean isInt();
 106         public void setInt(int a);
 107         public void setInt(long b);
 108     }
 109 
 110     public static class NonCompliant implements NonCompliantMXBean {
 111         public boolean getInt() {
 112             return false;
 113         }
 114 
 115         public boolean isInt() {
 116             return true;
 117         }
 118 
 119         public void setInt(int a) {
 120         }
 121 
 122         public void setInt(long b) {
 123         }
 124     }
 125 
 126     public static interface ExplicitMXBean {
 127         public int[] getInts();
 128     }
 129     public static class Explicit implements ExplicitMXBean {
 130         public int[] getInts() {
 131             return new int[] {1, 2, 3};
 132         }
 133     }
 134     public static class Subclass
 135         extends StandardMBean
 136         implements ExplicitMXBean {
 137         public Subclass() {
 138             super(ExplicitMXBean.class, true);
 139         }
 140 
 141         public int[] getInts() {
 142             return new int[] {1, 2, 3};
 143         }
 144     }
 145     public static interface IndirectInterface extends ExplicitMXBean {}
 146     public static class Indirect implements IndirectInterface {
 147         public int[] getInts() {
 148             return new int[] {1, 2, 3};
 149         }
 150     }
 151 
 152     private static void testNonCompliantMXBean(String type, Object bean) throws Exception {
 153         System.out.println(type + " MXBean test...");
 154         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 155         ObjectName on = new ObjectName("test:type=" + type);
 156         try {
 157             mbs.registerMBean(bean, on);
 158             failure(bean.getClass().getInterfaces()[0].getName() + " is not a compliant "
 159                 + "MXBean interface");
 160         } catch (NotCompliantMBeanException e) {
 161             success("Non-compliant MXBean not registered");
 162         }
 163     }
 164 
 165     private static void testExplicitMXBean() throws Exception {
 166         System.out.println("Explicit MXBean test...");
 167         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 168         ObjectName on = new ObjectName("test:type=Explicit");
 169         Explicit explicit = new Explicit();
 170         mbs.registerMBean(explicit, on);
 171         testMXBean(mbs, on);
 172     }
 173 
 174     private static void testSubclassMXBean() throws Exception {
 175         System.out.println("Subclass MXBean test...");
 176         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 177         ObjectName on = new ObjectName("test:type=Subclass");
 178         Subclass subclass = new Subclass();
 179         mbs.registerMBean(subclass, on);
 180         testMXBean(mbs, on);
 181     }
 182 
 183     private static void testIndirectMXBean() throws Exception {
 184         System.out.println("Indirect MXBean test...");
 185         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 186         ObjectName on = new ObjectName("test:type=Indirect");
 187         Indirect indirect = new Indirect();
 188         mbs.registerMBean(indirect, on);
 189         testMXBean(mbs, on);
 190     }
 191 
 192     private static void testMXBean(MBeanServer mbs, ObjectName on)
 193             throws Exception {
 194         MBeanInfo mbi = mbs.getMBeanInfo(on);
 195         MBeanAttributeInfo[] attrs = mbi.getAttributes();
 196         int nattrs = attrs.length;
 197         if (mbi.getAttributes().length != 1)
 198             failure("wrong number of attributes: " + attrs);
 199         else {
 200             MBeanAttributeInfo mbai = attrs[0];
 201             if (mbai.getName().equals("Ints")
 202                 && mbai.isReadable() && !mbai.isWritable()
 203                 && mbai.getDescriptor().getFieldValue("openType")
 204                     .equals(new ArrayType<int[]>(SimpleType.INTEGER, true))
 205                 && attrs[0].getType().equals("[I"))
 206                 success("MBeanAttributeInfo");
 207             else
 208                 failure("MBeanAttributeInfo: " + mbai);
 209         }
 210 
 211         int[] ints = (int[]) mbs.getAttribute(on, "Ints");
 212         if (equal(ints, new int[] {1, 2, 3}, null))
 213             success("getAttribute");
 214         else
 215             failure("getAttribute: " + Arrays.toString(ints));
 216 
 217         ExplicitMXBean proxy =
 218             JMX.newMXBeanProxy(mbs, on, ExplicitMXBean.class);
 219         int[] pints = proxy.getInts();
 220         if (equal(pints, new int[] {1, 2, 3}, null))
 221             success("getAttribute through proxy");
 222         else
 223             failure("getAttribute through proxy: " + Arrays.toString(pints));
 224     }
 225 
 226     private static class NamedMXBeans extends HashMap<ObjectName, Object> {
 227         private static final long serialVersionUID = 0;
 228 
 229         NamedMXBeans(MBeanServerConnection mbsc) {
 230             this.mbsc = mbsc;
 231         }
 232 
 233         MBeanServerConnection getMBeanServerConnection() {
 234             return mbsc;
 235         }
 236 
 237         private final MBeanServerConnection mbsc;
 238     }
 239 
 240     /* This is the core of the test.  Given the MXBean interface c, we
 241        make an MXBean object that implements that interface by
 242        constructing a dynamic proxy.  If the interface defines an
 243        attribute Foo (with getFoo and setFoo methods), then it must
 244        also contain a field (constant) Foo of the same type, and a
 245        field (constant) FooType that is an OpenType.  The field Foo is
 246        a reference value for this case.  We check that the attribute
 247        does indeed have the given OpenType.  The dynamically-created
 248        MXBean will return the reference value from the getFoo()
 249        method, and we check that that value survives the mapping to
 250        open values and back when the attribute is accessed through an
 251        MXBean proxy.  The MXBean will also check in its setFoo method
 252        that the value being set is equal to the reference value, which
 253        tests that the mapping and unmapping also works in the other
 254        direction.  The interface should define an operation opFoo with
 255        two parameters and a return value all of the same type as the
 256        attribute.  The MXBean will check that the two parameters are
 257        equal to the reference value, and will return that value.  The
 258        test checks that calling the operation through an MXBean proxy
 259        returns the reference value, again after mapping to and back
 260        from open values.
 261 
 262        If any field (constant) in the MXBean interface has a name that
 263        ends with ObjectName, say FooObjectName, then its value must be
 264        a String containing an ObjectName value.  There must be a field
 265        (constant) called Foo that is a valid MXBean, and that MXBean
 266        will be registered in the MBean Server with the given name before
 267        the test starts.  This enables us to test that inter-MXBean
 268        references are correctly converted to ObjectNames and back.
 269      */
 270     private static <T> void testInterface(Class<T> c, boolean nullTest)
 271             throws Exception {
 272 
 273         System.out.println("Testing " + c.getName() +
 274                            (nullTest ? " for null values" : "") + "...");
 275 
 276         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 277 
 278         JMXServiceURL url = new JMXServiceURL("rmi", null, 0);
 279         JMXConnectorServer cs =
 280             JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
 281         cs.start();
 282         JMXServiceURL addr = cs.getAddress();
 283         JMXConnector cc = JMXConnectorFactory.connect(addr);
 284         MBeanServerConnection mbsc = cc.getMBeanServerConnection();
 285 
 286         NamedMXBeans namedMXBeans = new NamedMXBeans(mbsc);
 287         InvocationHandler ih =
 288             nullTest ? new MXBeanNullImplInvocationHandler(c, namedMXBeans) :
 289                        new MXBeanImplInvocationHandler(c, namedMXBeans);
 290         T impl = c.cast(Proxy.newProxyInstance(c.getClassLoader(),
 291                                                new Class[] {c},
 292                                                ih));
 293         ObjectName on = new ObjectName("test:type=" + c.getName());
 294         mbs.registerMBean(impl, on);
 295 
 296         System.out.println("Register any MXBeans...");
 297 
 298         Field[] fields = c.getFields();
 299         for (Field field : fields) {
 300             String n = field.getName();
 301             if (n.endsWith("ObjectName")) {
 302                 String objectNameString = (String) field.get(null);
 303                 String base = n.substring(0, n.length() - 10);
 304                 Field f = c.getField(base);
 305                 Object mxbean = f.get(null);
 306                 ObjectName objectName =
 307                     ObjectName.getInstance(objectNameString);
 308                 mbs.registerMBean(mxbean, objectName);
 309                 namedMXBeans.put(objectName, mxbean);
 310             }
 311         }
 312 
 313         try {
 314             testInterface(c, mbsc, on, namedMXBeans, nullTest);
 315         } finally {
 316             try {
 317                 cc.close();
 318             } finally {
 319                 cs.stop();
 320             }
 321         }
 322     }
 323 
 324     private static <T> void testInterface(Class<T> c,
 325                                           MBeanServerConnection mbsc,
 326                                           ObjectName on,
 327                                           NamedMXBeans namedMXBeans,
 328                                           boolean nullTest)
 329             throws Exception {
 330 
 331         System.out.println("Type check...");
 332 
 333         MBeanInfo mbi = mbsc.getMBeanInfo(on);
 334         MBeanAttributeInfo[] mbais = mbi.getAttributes();
 335         for (int i = 0; i < mbais.length; i++) {
 336             MBeanAttributeInfo mbai = mbais[i];
 337             String name = mbai.getName();
 338             Field typeField = c.getField(name + "Type");
 339             OpenType typeValue = (OpenType) typeField.get(null);
 340             OpenType openType =
 341                 (OpenType) mbai.getDescriptor().getFieldValue("openType");
 342             if (typeValue.equals(openType))
 343                 success("attribute " + name);
 344             else {
 345                 final String msg =
 346                     "Wrong type attribute " + name + ": " +
 347                     openType + " should be " + typeValue;
 348                 failure(msg);
 349             }
 350         }
 351 
 352         MBeanOperationInfo[] mbois = mbi.getOperations();
 353         for (int i = 0; i < mbois.length; i++) {
 354             MBeanOperationInfo mboi = mbois[i];
 355             String oname = mboi.getName();
 356             if (!oname.startsWith("op"))
 357                 throw new Error();
 358             OpenType retType =
 359                 (OpenType) mboi.getDescriptor().getFieldValue("openType");
 360             MBeanParameterInfo[] params = mboi.getSignature();
 361             MBeanParameterInfo p1i = params[0];
 362             MBeanParameterInfo p2i = params[1];
 363             OpenType p1Type =
 364                 (OpenType) p1i.getDescriptor().getFieldValue("openType");
 365             OpenType p2Type =
 366                 (OpenType) p2i.getDescriptor().getFieldValue("openType");
 367             if (!retType.equals(p1Type) || !p1Type.equals(p2Type)) {
 368                 final String msg =
 369                     "Parameter and return open types should all be same " +
 370                     "but are not: " + retType + " " + oname + "(" + p1Type +
 371                     ", " + p2Type + ")";
 372                 failure(msg);
 373                 continue;
 374             }
 375             String name = oname.substring(2);
 376             Field typeField = c.getField(name + "Type");
 377             OpenType typeValue = (OpenType) typeField.get(null);
 378             if (typeValue.equals(retType))
 379                 success("operation " + oname);
 380             else {
 381                 final String msg =
 382                     "Wrong type operation " + oname + ": " +
 383                     retType + " should be " + typeValue;
 384                 failure(msg);
 385             }
 386         }
 387 
 388 
 389         System.out.println("Mapping check...");
 390 
 391         Object proxy =
 392             JMX.newMXBeanProxy(mbsc, on, c);
 393 
 394         Method[] methods = c.getMethods();
 395         for (int i = 0; i < methods.length; i++) {
 396             final Method method = methods[i];
 397             if (method.getDeclaringClass() != c)
 398                 continue; // skip hashCode() etc inherited from Object
 399             final String mname = method.getName();
 400             final int what = getType(method);
 401             final String name = getName(method);
 402             final Field refField = c.getField(name);
 403             if (nullTest && refField.getType().isPrimitive())
 404                 continue;
 405             final Field openTypeField = c.getField(name + "Type");
 406             final OpenType openType = (OpenType) openTypeField.get(null);
 407             final Object refValue = nullTest ? null : refField.get(null);
 408             Object setValue = refValue;
 409             try {
 410                 Field onField = c.getField(name + "ObjectName");
 411                 String refName = (String) onField.get(null);
 412                 ObjectName refObjName = ObjectName.getInstance(refName);
 413                 Class<?> mxbeanInterface = refField.getType();
 414                 setValue = nullTest ? null :
 415                     JMX.newMXBeanProxy(mbsc, refObjName, mxbeanInterface);
 416             } catch (Exception e) {
 417                 // no xObjectName field, setValue == refValue
 418             }
 419             boolean ok = true;
 420             try {
 421                 switch (what) {
 422                 case GET:
 423                     final Object gotOpen = mbsc.getAttribute(on, name);
 424                     if (nullTest) {
 425                         if (gotOpen != null) {
 426                             failure(mname + " got non-null value " +
 427                                     gotOpen);
 428                             ok = false;
 429                         }
 430                     } else if (!openType.isValue(gotOpen)) {
 431                         if (gotOpen instanceof TabularData) {
 432                             // detail the mismatch
 433                             TabularData gotTabular = (TabularData) gotOpen;
 434                             compareTabularType((TabularType) openType,
 435                                                gotTabular.getTabularType());
 436                         }
 437                         failure(mname + " got open data " + gotOpen +
 438                                 " not valid for open type " + openType);
 439                         ok = false;
 440                     }
 441                     final Object got = method.invoke(proxy, (Object[]) null);
 442                     if (!equal(refValue, got, namedMXBeans)) {
 443                         failure(mname + " got " + string(got) +
 444                                 ", should be " + string(refValue));
 445                         ok = false;
 446                     }
 447                     break;
 448 
 449                 case SET:
 450                     method.invoke(proxy, new Object[] {setValue});
 451                     break;
 452 
 453                 case OP:
 454                     final Object opped =
 455                         method.invoke(proxy, new Object[] {setValue, setValue});
 456                     if (!equal(refValue, opped, namedMXBeans)) {
 457                         failure(
 458                                 mname + " got " + string(opped) +
 459                                 ", should be " + string(refValue)
 460                                 );
 461                         ok = false;
 462                     }
 463                     break;
 464 
 465                 default:
 466                     throw new Error();
 467                 }
 468 
 469                 if (ok)
 470                     success(mname);
 471 
 472             } catch (Exception e) {
 473                 failure(mname, e);
 474             }
 475         }
 476     }
 477 
 478 
 479     private static void success(String what) {
 480         System.out.println("OK: " + what);
 481     }
 482 
 483     private static void failure(String what) {
 484         System.out.println("FAILED: " + what);
 485         failures++;
 486     }
 487 
 488     private static void failure(String what, Exception e) {
 489         System.out.println("FAILED WITH EXCEPTION: " + what);
 490         e.printStackTrace(System.out);
 491         failures++;
 492     }
 493 
 494     private static class MXBeanImplInvocationHandler
 495             implements InvocationHandler {
 496         MXBeanImplInvocationHandler(Class intf, NamedMXBeans namedMXBeans) {
 497             this.intf = intf;
 498             this.namedMXBeans = namedMXBeans;
 499         }
 500 
 501         public Object invoke(Object proxy, Method method, Object[] args)
 502                 throws Throwable {
 503             final String mname = method.getName();
 504             final int what = getType(method);
 505             final String name = getName(method);
 506             final Field refField = intf.getField(name);
 507             final Object refValue = getRefValue(refField);
 508 
 509             switch (what) {
 510             case GET:
 511                 assert args == null;
 512                 return refValue;
 513 
 514             case SET:
 515                 assert args.length == 1;
 516                 Object setValue = args[0];
 517                 if (!equal(refValue, setValue, namedMXBeans)) {
 518                     final String msg =
 519                         mname + "(" + string(setValue) +
 520                         ") does not match ref: " + string(refValue);
 521                     throw new IllegalArgumentException(msg);
 522                 }
 523                 return null;
 524 
 525             case OP:
 526                 assert args.length == 2;
 527                 Object arg1 = args[0];
 528                 Object arg2 = args[1];
 529                 if (!equal(arg1, arg2, namedMXBeans)) {
 530                     final String msg =
 531                         mname + "(" + string(arg1) + ", " + string(arg2) +
 532                         "): args not equal";
 533                     throw new IllegalArgumentException(msg);
 534                 }
 535                 if (!equal(refValue, arg1, namedMXBeans)) {
 536                     final String msg =
 537                         mname + "(" + string(arg1) + ", " + string(arg2) +
 538                         "): args do not match ref: " + string(refValue);
 539                     throw new IllegalArgumentException(msg);
 540                 }
 541                 return refValue;
 542             default:
 543                 throw new Error();
 544             }
 545         }
 546 
 547         Object getRefValue(Field refField) throws Exception {
 548             return refField.get(null);
 549         }
 550 
 551         private final Class intf;
 552         private final NamedMXBeans namedMXBeans;
 553     }
 554 
 555     private static class MXBeanNullImplInvocationHandler
 556             extends MXBeanImplInvocationHandler {
 557         MXBeanNullImplInvocationHandler(Class intf, NamedMXBeans namedMXBeans) {
 558             super(intf, namedMXBeans);
 559         }
 560 
 561         @Override
 562         Object getRefValue(Field refField) throws Exception {
 563             Class<?> type = refField.getType();
 564             if (type.isPrimitive())
 565                 return super.getRefValue(refField);
 566             else
 567                 return null;
 568         }
 569     }
 570 
 571 
 572     private static final String[] prefixes = {
 573         "get", "set", "op",
 574     };
 575     private static final int GET = 0, SET = 1, OP = 2;
 576 
 577     private static String getName(Method m) {
 578         return getName(m.getName());
 579     }
 580 
 581     private static String getName(String n) {
 582         for (int i = 0; i < prefixes.length; i++) {
 583             if (n.startsWith(prefixes[i]))
 584                 return n.substring(prefixes[i].length());
 585         }
 586         throw new Error();
 587     }
 588 
 589     private static int getType(Method m) {
 590         return getType(m.getName());
 591     }
 592 
 593     private static int getType(String n) {
 594         for (int i = 0; i < prefixes.length; i++) {
 595             if (n.startsWith(prefixes[i]))
 596                 return i;
 597         }
 598         throw new Error();
 599     }
 600 
 601     static boolean equal(Object o1, Object o2, NamedMXBeans namedMXBeans) {
 602         if (o1 == o2)
 603             return true;
 604         if (o1 == null || o2 == null)
 605             return false;
 606         if (o1.getClass().isArray()) {
 607             if (!o2.getClass().isArray())
 608                 return false;
 609             return deepEqual(o1, o2, namedMXBeans);
 610         }
 611         if (o1 instanceof Map) {
 612             if (!(o2 instanceof Map))
 613                 return false;
 614             return equalMap((Map) o1, (Map) o2, namedMXBeans);
 615         }
 616         if (o1 instanceof CompositeData && o2 instanceof CompositeData) {
 617             return compositeDataEqual((CompositeData) o1, (CompositeData) o2,
 618                                       namedMXBeans);
 619         }
 620         if (Proxy.isProxyClass(o1.getClass())) {
 621             if (Proxy.isProxyClass(o2.getClass()))
 622                 return proxyEqual(o1, o2, namedMXBeans);
 623             InvocationHandler ih = Proxy.getInvocationHandler(o1);
 624 //            if (ih instanceof MXBeanInvocationHandler) {
 625 //                return proxyEqualsObject((MXBeanInvocationHandler) ih,
 626 //                                         o2, namedMXBeans);
 627             if (ih instanceof MBeanServerInvocationHandler) {
 628                 return true;
 629             } else if (ih instanceof CompositeDataInvocationHandler) {
 630                 return o2.equals(o1);
 631                 // We assume the other object has a reasonable equals method
 632             }
 633         } else if (Proxy.isProxyClass(o2.getClass()))
 634             return equal(o2, o1, namedMXBeans);
 635         return o1.equals(o2);
 636     }
 637 
 638     // We'd use Arrays.deepEquals except we want the test to work on 1.4
 639     // Note this code assumes no selfreferential arrays
 640     // (as does Arrays.deepEquals)
 641     private static boolean deepEqual(Object a1, Object a2,
 642                                      NamedMXBeans namedMXBeans) {
 643         int len = Array.getLength(a1);
 644         if (len != Array.getLength(a2))
 645             return false;
 646         for (int i = 0; i < len; i++) {
 647             Object e1 = Array.get(a1, i);
 648             Object e2 = Array.get(a2, i);
 649             if (!equal(e1, e2, namedMXBeans))
 650                 return false;
 651         }
 652         return true;
 653     }
 654 
 655     private static boolean equalMap(Map<?,?> m1, Map<?,?> m2,
 656                                     NamedMXBeans namedMXBeans) {
 657         if (m1.size() != m2.size())
 658             return false;
 659         if ((m1 instanceof SortedMap) != (m2 instanceof SortedMap))
 660             return false;
 661         for (Object k1 : m1.keySet()) {
 662             if (!m2.containsKey(k1))
 663                 return false;
 664             if (!equal(m1.get(k1), m2.get(k1), namedMXBeans))
 665                 return false;
 666         }
 667         return true;
 668     }
 669 
 670     // This is needed to work around a bug (5095277)
 671     // in CompositeDataSupport.equals
 672     private static boolean compositeDataEqual(CompositeData cd1,
 673                                               CompositeData cd2,
 674                                               NamedMXBeans namedMXBeans) {
 675         if (cd1 == cd2)
 676             return true;
 677         if (!cd1.getCompositeType().equals(cd2.getCompositeType()))
 678             return false;
 679         Collection v1 = cd1.values();
 680         Collection v2 = cd2.values();
 681         if (v1.size() != v2.size())
 682             return false; // should not happen
 683         for (Iterator i1 = v1.iterator(), i2 = v2.iterator();
 684              i1.hasNext(); ) {
 685             if (!equal(i1.next(), i2.next(), namedMXBeans))
 686                 return false;
 687         }
 688         return true;
 689     }
 690 
 691     // Also needed for 5095277
 692     private static boolean proxyEqual(Object proxy1, Object proxy2,
 693                                       NamedMXBeans namedMXBeans) {
 694         if (proxy1.getClass() != proxy2.getClass())
 695             return proxy1.equals(proxy2);
 696         InvocationHandler ih1 = Proxy.getInvocationHandler(proxy1);
 697         InvocationHandler ih2 = Proxy.getInvocationHandler(proxy2);
 698         if (!(ih1 instanceof CompositeDataInvocationHandler)
 699             || !(ih2 instanceof CompositeDataInvocationHandler))
 700             return proxy1.equals(proxy2);
 701         CompositeData cd1 =
 702             ((CompositeDataInvocationHandler) ih1).getCompositeData();
 703         CompositeData cd2 =
 704             ((CompositeDataInvocationHandler) ih2).getCompositeData();
 705         return compositeDataEqual(cd1, cd2, namedMXBeans);
 706     }
 707 
 708 //    private static boolean proxyEqualsObject(MXBeanInvocationHandler ih,
 709 //                                             Object o,
 710 //                                             NamedMXBeans namedMXBeans) {
 711 //        if (namedMXBeans.getMBeanServerConnection() !=
 712 //            ih.getMBeanServerConnection())
 713 //            return false;
 714 //
 715 //        ObjectName on = ih.getObjectName();
 716 //        Object named = namedMXBeans.get(on);
 717 //        if (named == null)
 718 //            return false;
 719 //        return (o == named && ih.getMXBeanInterface().isInstance(named));
 720 //    }
 721 
 722     /* I wanted to call this method toString(Object), but oddly enough
 723        this meant that I couldn't call it from the inner class
 724        MXBeanImplInvocationHandler, because the inherited Object.toString()
 725        prevented that.  */
 726     static String string(Object o) {
 727         if (o == null)
 728             return "null";
 729         if (o instanceof String)
 730             return '"' + (String) o + '"';
 731         if (o instanceof Collection)
 732             return deepToString((Collection) o);
 733         if (o.getClass().isArray())
 734             return deepToString(o);
 735         return o.toString();
 736     }
 737 
 738     private static String deepToString(Object o) {
 739         StringBuffer buf = new StringBuffer();
 740         buf.append("[");
 741         int len = Array.getLength(o);
 742         for (int i = 0; i < len; i++) {
 743             if (i > 0)
 744                 buf.append(", ");
 745             Object e = Array.get(o, i);
 746             buf.append(string(e));
 747         }
 748         buf.append("]");
 749         return buf.toString();
 750     }
 751 
 752     private static String deepToString(Collection c) {
 753         return deepToString(c.toArray());
 754     }
 755 
 756     private static void compareTabularType(TabularType t1, TabularType t2) {
 757         if (t1.equals(t2)) {
 758             System.out.println("same tabular type");
 759             return;
 760         }
 761         if (t1.getClassName().equals(t2.getClassName()))
 762             System.out.println("same class name");
 763         if (t1.getDescription().equals(t2.getDescription()))
 764             System.out.println("same description");
 765         else {
 766             System.out.println("t1 description: " + t1.getDescription());
 767             System.out.println("t2 description: " + t2.getDescription());
 768         }
 769         if (t1.getIndexNames().equals(t2.getIndexNames()))
 770             System.out.println("same index names");
 771         if (t1.getRowType().equals(t2.getRowType()))
 772             System.out.println("same row type");
 773     }
 774 }