1/*
2 * Copyright (c) 2004, 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 6204469 6273765
27 * @summary Test various aspects of the Descriptor interface
28 * @author Eamonn McManus
29 *
30 * @run clean DescriptorTest
31 * @run build DescriptorTest
32 * @run main DescriptorTest
33 */
34
35import java.io.*;
36import java.lang.reflect.*;
37import java.util.*;
38import javax.management.*;
39
40public class DescriptorTest {
41    private static String failureMessage;
42
43    // Warning: many tests here know the contents of these variables
44    // so if you change them you must change the tests
45    private static final String[] testFieldNames = {
46        "a", "C", "aa", "int", "nul",
47    };
48    private static final Object[] testFieldValues = {
49        "b", "D", "bb", 5, null,
50    };
51    private static final String[] testFieldStrings = {
52        "a=b", "C=D", "aa=bb", "int=(5)", "nul=",
53    };
54
55    public static void main(String[] args) throws Exception {
56        genericTests(ImmutableDescriptor.class);
57        genericTests(javax.management.modelmbean.DescriptorSupport.class);
58        if (failureMessage != null)
59            throw new Exception("TEST FAILED: " + failureMessage);
60        else
61            System.out.println("Test passed");
62    }
63
64    private static void genericTests(Class<? extends Descriptor> descrClass) {
65        System.out.println("--- generic tests for " + descrClass.getName() +
66                           " ---");
67        for (Case<Class<? extends Descriptor>, ?, ?> test :
68                 genericDescriptorTests)
69            test.run(descrClass);
70    }
71
72    /*
73      Testing has three parts.  We take the input parameter, of type P,
74      and give it to the "prepare" method.  That returns us a test
75      parameter, of type T.  We give that to the "test" method.  That
76      in turn returns us a check value, of type C.  We give this to the
77      "check" method.  If the "check" method returns null, the test passes.
78      If the "check" method returns a string, that string explains the
79      test failure.  If any of the methods throws an exception, the
80      test fails.
81     */
82    private static abstract class Case<P, T, C> {
83        Case(String name) {
84            this.name = name;
85        }
86
87        void run(P p) {
88            System.out.println("test: " + name);
89            try {
90                T t = prepare(p);
91                C c = test(t);
92                String failed = check(c);
93                if (failed != null) {
94                    System.out.println("FAILED: " + name + ": " + failed);
95                    failureMessage = failed;
96                }
97            } catch (Exception e) {
98                System.out.println("FAILED: " + name + ": exception:");
99                e.printStackTrace(System.out);
100                failureMessage = e.toString();
101            }
102        }
103
104        abstract T prepare(P p) throws Exception;
105        abstract C test(T t) throws Exception;
106        abstract String check(C c) throws Exception;
107
108        private final String name;
109    }
110
111    /*
112      Test case where the preparation step consists of constructing an
113      instance of the given Descriptor subclass containing test values,
114      then giving that to the "test" method.
115    */
116    private static abstract class ProtoCase<C>
117            extends Case<Class<? extends Descriptor>, Descriptor, C> {
118
119        ProtoCase(String name) {
120            super(name);
121        }
122
123        Descriptor prepare(Class<? extends Descriptor> descrClass)
124                throws Exception {
125            Constructor<? extends Descriptor> con =
126                descrClass.getConstructor(String[].class, Object[].class);
127            return con.newInstance(testFieldNames, testFieldValues);
128        }
129    }
130
131    /*
132      Test case where the "test" method must return a value of type C
133      which we will compare against the testValue parameter given to
134      the test constructor.
135    */
136    private static abstract class ValueProtoCase<C> extends ProtoCase<C> {
137        ValueProtoCase(String name, C testValue) {
138            super(name);
139            this.testValue = testValue;
140        }
141
142        String check(C c) {
143            final boolean array = (testValue instanceof Object[]);
144            final boolean equal =
145                array ?
146                    Arrays.deepEquals((Object[]) testValue, (Object[]) c) :
147                    testValue.equals(c);
148            if (equal)
149                return null;
150            return "wrong value: " + string(c) + " should be " +
151                string(testValue);
152        }
153
154        private final C testValue;
155    }
156
157    /*
158      Test case where the dontChange method does some operation on the
159      test Descriptor that is not supposed to change the contents of
160      the Descriptor.  This should work for both mutable and immutable
161      Descriptors, since immutable Descriptors are supposed to do
162      nothing (rather than throw an exception) for mutation operations
163      that would not in fact change the contents.
164    */
165    private static abstract class UnchangedCase extends ProtoCase<Descriptor> {
166        UnchangedCase(String name) {
167            super(name);
168        }
169
170        Descriptor test(Descriptor d) {
171            dontChange(d);
172            return d;
173        }
174
175        String check(Descriptor d) {
176            String[] dnames = d.getFieldNames();
177            if (!strings(dnames).equals(strings(testFieldNames)))
178                return "descriptor names changed: " + strings(dnames);
179            Object[] values = d.getFieldValues(testFieldNames);
180            if (values.length != testFieldValues.length)
181                return "getFieldValues: bogus length: " + values.length;
182            for (int i = 0; i < values.length; i++) {
183                Object expected = testFieldValues[i];
184                Object found = values[i];
185                if ((expected == null) ?
186                        found != null :
187                        !expected.equals(found))
188                    return "descriptor value changed: " + testFieldNames[i] +
189                        " was " + expected + " now " + found;
190            }
191            return null;
192        }
193
194        abstract void dontChange(Descriptor d);
195    }
196
197    /*
198      Test case where the change(d) method attempts to make some
199      change to the Descriptor d.  The behaviour depends on whether
200      the Descriptor is mutable or not.  If the Descriptor is
201      immutable, then the change attempt must throw a
202      RuntimeOperationsException wrapping an
203      UnsupportedOperationException.  If the Descriptor is mutable,
204      then the change attempt must succeed, and the Descriptor must
205      then look like the fieldsAndValues parameter to the constructor.
206      This is simply an alternating set of field names and corresponding
207      values.  So for example if it is
208
209      "a", "b", "x", 5
210
211      that represents a Descriptor with fields "a" and "x" whose
212      corresponding values are "x" and Integer.valueOf(5).
213    */
214    private static abstract class ChangedCase extends ProtoCase<Object> {
215        ChangedCase(String name, Object... fieldsAndValues) {
216            super(name);
217            if (fieldsAndValues.length % 2 != 0)
218                throw new AssertionError("test wrong: odd fieldsAndValues");
219            this.fieldsAndValues = fieldsAndValues;
220            this.immutableTest = new UnsupportedExceptionCase(name) {
221                void provoke(Descriptor d) {
222                    ChangedCase.this.change(d);
223                }
224            };
225        }
226
227        Object test(Descriptor d) {
228            if (immutable(d))
229                return immutableTest.test(d);
230            else {
231                change(d);
232                return d;
233            }
234        }
235
236        String check(Object c) {
237            if (c instanceof Exception)
238                return immutableTest.check((Exception) c);
239            else if (!(c instanceof Descriptor)) {
240                return "test returned strange value: " +
241                        c.getClass() + ": " + c;
242            } else {
243                Descriptor d = (Descriptor) c;
244                String[] names = new String[fieldsAndValues.length / 2];
245                Object[] expected = new Object[names.length];
246                for (int i = 0; i < fieldsAndValues.length; i += 2) {
247                    names[i / 2] = (String) fieldsAndValues[i];
248                    expected[i / 2] = fieldsAndValues[i + 1];
249                }
250                String[] foundNames = d.getFieldNames();
251                if (!strings(foundNames).equals(strings(names))) {
252                    return "wrong field names after change: found " +
253                        strings(foundNames) + ", expected " + strings(names);
254                }
255                Object[] found = d.getFieldValues(names);
256                if (!Arrays.deepEquals(expected, found)) {
257                    return "wrong value after change: for fields " +
258                        Arrays.asList(names) + " values are " +
259                        Arrays.asList(found) + ", should be " +
260                        Arrays.asList(expected);
261                }
262                return null;
263            }
264        }
265
266        abstract void change(Descriptor d);
267
268        private final Object[] fieldsAndValues;
269        private final ExceptionCase immutableTest;
270    }
271
272    /*
273      Test case where an operation provoke(d) on the test Descriptor d
274      is supposed to provoke an exception.  The exception must be a
275      RuntimeOperationsException wrapping another exception whose type
276      is determined by the exceptionClass() method.
277    */
278    private static abstract class ExceptionCase extends ProtoCase<Exception> {
279
280        ExceptionCase(String name) {
281            super(name);
282        }
283
284        Exception test(Descriptor d) {
285            try {
286                provoke(d);
287                return null;
288            } catch (Exception e) {
289                return e;
290            }
291        }
292
293        String check(Exception e) {
294            if (e == null)
295                return "did not throw exception: " + expected();
296            if (!(e instanceof RuntimeOperationsException)) {
297                StringWriter sw = new StringWriter();
298                PrintWriter pw = new PrintWriter(sw);
299                e.printStackTrace(pw);
300                pw.flush();
301                return "wrong exception: " + expected() + ": found: " + sw;
302            }
303            Throwable cause = e.getCause();
304            if (!exceptionClass().isInstance(cause))
305                return "wrong wrapped exception: " + cause + ": " + expected();
306            return null;
307        }
308
309        String expected() {
310            return "expected " + RuntimeOperationsException.class.getName() +
311                " wrapping " + exceptionClass().getName();
312        }
313
314        abstract Class<? extends Exception> exceptionClass();
315        abstract void provoke(Descriptor d);
316    }
317
318    private static abstract class IllegalExceptionCase extends ExceptionCase {
319        IllegalExceptionCase(String name) {
320            super(name);
321        }
322
323        Class<IllegalArgumentException> exceptionClass() {
324            return IllegalArgumentException.class;
325        }
326    }
327
328    private static abstract class UnsupportedExceptionCase
329            extends ExceptionCase {
330        UnsupportedExceptionCase(String name) {
331            super(name);
332        }
333
334        Class<UnsupportedOperationException> exceptionClass() {
335            return UnsupportedOperationException.class;
336        }
337    }
338
339    /*
340      List of test cases.  We will run through these once for
341      ImmutableDescriptor and once for DescriptorSupport.
342
343      Expect a compiler [unchecked] warning for this initialization.
344      Writing
345
346          new Case<Class<? extends Descriptor>, ?, ?>[] = {...}
347
348      would cause a compiler error since you can't have arrays of
349      parameterized types unless all the parameters are just "?".
350      This hack with varargs gives us a compiler warning instead.
351      Writing just:
352
353          new Case<?, ?, ?>[] = {...}
354
355      would compile here, but not where we call test.run, since you
356      cannot pass an object to the run(P) method if P is "?".
357    */
358    private static final Case<Class<? extends Descriptor>, ?, ?>
359            genericDescriptorTests[] = constantArray(
360
361        // TEST VALUES RETURNED BY GETTERS
362
363        new Case<Class<? extends Descriptor>, Descriptor, Object[]>(
364                "getFieldValues on empty Descriptor") {
365            Descriptor prepare(Class<? extends Descriptor> c)
366                    throws Exception {
367                Constructor<? extends Descriptor> con =
368                        c.getConstructor(String[].class);
369                return con.newInstance(new Object[] {new String[0]});
370            }
371            Object[] test(Descriptor d) {
372                return d.getFieldValues("foo", "bar");
373            }
374            String check(Object[] v) {
375                if (v.length == 2 && v[0] == null && v[1] == null)
376                    return null;
377                return "value should be array with null elements: " +
378                        Arrays.deepToString(v);
379            }
380        },
381
382        new ValueProtoCase<Set<String>>("getFieldNames",
383                                        strings(testFieldNames)) {
384            Set<String> test(Descriptor d) {
385                return set(d.getFieldNames());
386            }
387        },
388        new ValueProtoCase<Set<String>>("getFields",
389                                        strings(testFieldStrings)) {
390            Set<String> test(Descriptor d) {
391                return set(d.getFields());
392            }
393        },
394        new ValueProtoCase<Object>("getFieldValue with exact case", "b") {
395            Object test(Descriptor d) {
396                return d.getFieldValue("a");
397            }
398        },
399        new ValueProtoCase<Object>("getFieldValue with lower case for upper",
400                                   "D") {
401            Object test(Descriptor d) {
402                return d.getFieldValue("c");
403            }
404        },
405        new ValueProtoCase<Object>("getFieldValue with upper case for lower",
406                                   "bb") {
407            Object test(Descriptor d) {
408                return d.getFieldValue("AA");
409            }
410        },
411        new ValueProtoCase<Object>("getFieldValue with mixed case for lower",
412                                   "bb") {
413            Object test(Descriptor d) {
414                return d.getFieldValue("aA");
415            }
416        },
417        new ValueProtoCase<Set<?>>("getFieldValues with null arg",
418                                   set(testFieldValues)) {
419            Set<?> test(Descriptor d) {
420                return set(d.getFieldValues((String[]) null));
421            }
422        },
423        new ValueProtoCase<Object[]>("getFieldValues with not all values",
424                                     new Object[] {"b", "D", 5}) {
425            Object[] test(Descriptor d) {
426                return d.getFieldValues("a", "c", "int");
427            }
428        },
429        new ValueProtoCase<Object[]>("getFieldValues with all values " +
430                                     "lower case",
431                                     new Object[]{"bb", "D", "b", 5}) {
432            Object[] test(Descriptor d) {
433                return d.getFieldValues("aa", "c", "a", "int");
434            }
435        },
436        new ValueProtoCase<Object[]>("getFieldValues with all values " +
437                                     "upper case",
438                                     new Object[] {5, "b", "D", "bb"}) {
439            Object[] test(Descriptor d) {
440                return d.getFieldValues("int", "A", "C", "AA");
441            }
442        },
443        new ValueProtoCase<Object[]>("getFieldValues with null name",
444                                     new Object[] {null}) {
445            Object[] test(Descriptor d) {
446                return d.getFieldValues((String) null);
447            }
448        },
449        new ValueProtoCase<Object[]>("getFieldValues with empty name",
450                                     new Object[] {null}) {
451            Object[] test(Descriptor d) {
452                return d.getFieldValues("");
453            }
454        },
455        new ValueProtoCase<Object[]>("getFieldValues with no names",
456                                     new Object[0]) {
457            Object[] test(Descriptor d) {
458                return d.getFieldValues();
459            }
460        },
461
462        // TEST OPERATIONS THAT DON'T CHANGE THE DESCRIPTOR
463        // Even for immutable descriptors, these are allowed
464
465        new UnchangedCase("removeField with nonexistent field") {
466            void dontChange(Descriptor d) {
467                d.removeField("noddy");
468            }
469        },
470        new UnchangedCase("removeField with null field") {
471            void dontChange(Descriptor d) {
472                d.removeField(null);
473            }
474        },
475        new UnchangedCase("removeField with empty field") {
476            void dontChange(Descriptor d) {
477                d.removeField("");
478            }
479        },
480        new UnchangedCase("setField leaving string unchanged") {
481            void dontChange(Descriptor d) {
482                d.setField("a", "b");
483            }
484        },
485        new UnchangedCase("setField leaving int unchanged") {
486            void dontChange(Descriptor d) {
487                d.setField("int", 5);
488            }
489        },
490        // We do not test whether you can do a setField/s with an
491        // unchanged value but the case of the name different.
492        // From the spec, that should probably be illegal, but
493        // it's such a corner case that we leave it alone.
494
495        new UnchangedCase("setFields with empty arrays") {
496            void dontChange(Descriptor d) {
497                d.setFields(new String[0], new Object[0]);
498            }
499        },
500        new UnchangedCase("setFields with unchanged values") {
501            void dontChange(Descriptor d) {
502                d.setFields(new String[] {"a", "int"},
503                            new Object[] {"b", 5});
504            }
505        },
506
507        // TEST OPERATIONS THAT DO CHANGE THE DESCRIPTOR
508        // For immutable descriptors, these should provoke an exception
509
510        new ChangedCase("removeField with exact case",
511                        "a", "b", "C", "D", "int", 5, "nul", null) {
512            void change(Descriptor d) {
513                d.removeField("aa");
514            }
515        },
516        new ChangedCase("removeField with upper case for lower",
517                        "a", "b", "C", "D", "int", 5, "nul", null) {
518            void change(Descriptor d) {
519                d.removeField("AA");
520            }
521        },
522        new ChangedCase("removeField with lower case for upper",
523                        "a", "b", "aa", "bb", "int", 5, "nul", null) {
524            void change(Descriptor d) {
525                d.removeField("c");
526            }
527        },
528        new ChangedCase("setField keeping lower case",
529                        "a", "x", "C", "D", "aa", "bb", "int", 5,
530                        "nul", null) {
531            void change(Descriptor d) {
532                d.setField("a", "x");
533            }
534        },
535
536        // spec says we should conserve the original case of the field name:
537        new ChangedCase("setField changing lower case to upper",
538                        "a", "x", "C", "D", "aa", "bb", "int", 5,
539                        "nul", null) {
540            void change(Descriptor d) {
541                d.setField("A", "x");
542            }
543        },
544        new ChangedCase("setField changing upper case to lower",
545                        "a", "b", "C", "x", "aa", "bb", "int", 5,
546                        "nul", null) {
547            void change(Descriptor d) {
548                d.setField("c", "x");
549            }
550        },
551        new ChangedCase("setField adding new field",
552                        "a", "b", "C", "D", "aa", "bb", "int", 5, "xX", "yY",
553                        "nul", null) {
554            void change(Descriptor d) {
555                d.setField("xX", "yY");
556            }
557        },
558        new ChangedCase("setField changing type of field",
559                        "a", true, "C", "D", "aa", "bb", "int", 5,
560                        "nul", null) {
561            void change(Descriptor d) {
562                d.setField("a", true);
563            }
564        },
565        new ChangedCase("setField changing non-null to null",
566                        "a", null, "C", "D", "aa", "bb", "int", 5,
567                        "nul", null) {
568            void change(Descriptor d) {
569                d.setField("a", null);
570            }
571        },
572        new ChangedCase("setField changing null to non-null",
573                        "a", "b", "C", "D", "aa", "bb", "int", 5,
574                        "nul", 3.14) {
575            void change(Descriptor d) {
576                d.setField("nul", 3.14);
577            }
578        },
579
580        // TEST EXCEPTION BEHAVIOUR COMMON BETWEEN MUTABLE AND IMMUTABLE
581
582        new IllegalExceptionCase("getFieldValue with null name") {
583            void provoke(Descriptor d) {
584                d.getFieldValue(null);
585            }
586        },
587        new IllegalExceptionCase("getFieldValue with empty name") {
588            void provoke(Descriptor d) {
589                d.getFieldValue("");
590            }
591        },
592        new IllegalExceptionCase("setField with null name") {
593            void provoke(Descriptor d) {
594                d.setField(null, "x");
595            }
596        },
597        new IllegalExceptionCase("setField with empty name") {
598            void provoke(Descriptor d) {
599                d.setField("", "x");
600            }
601        },
602        new IllegalExceptionCase("setFields with null fieldNames") {
603            void provoke(Descriptor d) {
604                d.setFields(null, new Object[] {"X"});
605            }
606        },
607        new IllegalExceptionCase("setFields with null fieldValues") {
608            void provoke(Descriptor d) {
609                d.setFields(new String[] {"X"}, null);
610            }
611        },
612        new IllegalExceptionCase("setFields with null fieldNames and " +
613                                 "fieldValues") {
614            void provoke(Descriptor d) {
615                d.setFields(null, null);
616            }
617        },
618        new IllegalExceptionCase("setFields with more fieldNames than " +
619                                 "fieldValues") {
620            void provoke(Descriptor d) {
621                d.setFields(new String[] {"A", "B"}, new String[] {"C"});
622            }
623        },
624        new IllegalExceptionCase("setFields with more fieldValues than " +
625                                 "fieldNames") {
626            void provoke(Descriptor d) {
627                d.setFields(new String[] {"A"}, new String[] {"B", "C"});
628            }
629        },
630        new IllegalExceptionCase("setFields with null element of fieldNames") {
631            void provoke(Descriptor d) {
632                d.setFields(new String[] {null}, new String[] {"X"});
633            }
634        }
635
636    );
637
638    static <T> T[] constantArray(T... array) {
639        return array;
640    }
641
642    static String string(Object x) {
643        if (x instanceof Object[])
644            return Arrays.asList((Object[]) x).toString();
645        else
646            return String.valueOf(x);
647    }
648
649    static Set<String> strings(String... values) {
650        return new TreeSet<String>(Arrays.asList(values));
651    }
652
653    static <T> Set<T> set(T[] values) {
654        return new HashSet<T>(Arrays.asList(values));
655    }
656
657    static boolean immutable(Descriptor d) {
658        return (d instanceof ImmutableDescriptor);
659        // good enough for our purposes
660    }
661}
662