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