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