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 
  35 import java.io.*;
  36 import java.lang.reflect.*;
  37 import java.util.*;
  38 import javax.management.*;
  39 
  40 public 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 }