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
36import java.lang.reflect.Array;
37import java.lang.reflect.Field;
38import java.lang.reflect.InvocationHandler;
39import java.lang.reflect.Method;
40import java.lang.reflect.Proxy;
41import java.util.Arrays;
42import java.util.Collection;
43import java.util.HashMap;
44import java.util.Iterator;
45import java.util.Map;
46import java.util.SortedMap;
47import javax.management.JMX;
48import javax.management.MBeanAttributeInfo;
49import javax.management.MBeanInfo;
50import javax.management.MBeanOperationInfo;
51import javax.management.MBeanParameterInfo;
52import javax.management.MBeanServer;
53import javax.management.MBeanServerConnection;
54import javax.management.MBeanServerFactory;
55import javax.management.MBeanServerInvocationHandler;
56import javax.management.NotCompliantMBeanException;
57import javax.management.ObjectName;
58import javax.management.StandardMBean;
59import javax.management.openmbean.ArrayType;
60import javax.management.openmbean.CompositeData;
61import javax.management.openmbean.CompositeDataInvocationHandler;
62import javax.management.openmbean.OpenType;
63import javax.management.openmbean.SimpleType;
64import javax.management.openmbean.TabularData;
65import javax.management.openmbean.TabularType;
66import javax.management.remote.JMXConnector;
67import javax.management.remote.JMXConnectorFactory;
68import javax.management.remote.JMXConnectorServer;
69import javax.management.remote.JMXConnectorServerFactory;
70import javax.management.remote.JMXServiceURL;
71
72public 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}
774