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