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 6204469
  27  * @summary Test that Open MBean attributes and parameters check constraints
  28  * @author Eamonn McManus
  29  * @modules java.management
  30  * @run clean ConstraintTest
  31  * @run build ConstraintTest
  32  * @run main ConstraintTest
  33  */
  34 
  35 import java.util.*;
  36 import javax.management.*;
  37 import javax.management.openmbean.*;
  38 
  39 public class ConstraintTest {
  40     private static String failure;
  41 
  42     public static void main(String[] args) throws Exception {
  43         for (Object[][] test : tests) {
  44             if (test.length != 4) {
  45                 throw new Exception("Test element has wrong length: " +
  46                                     Arrays.deepToString(test));
  47             }
  48 
  49             if (test[0].length != 4) {
  50                 throw new Exception("Test constraints should have size 4: " +
  51                                     Arrays.deepToString(test[0]));
  52             }
  53             Object defaultValue = test[0][0];
  54             Comparable<?> minValue = (Comparable<?>) test[0][1];
  55             Comparable<?> maxValue = (Comparable<?>) test[0][2];
  56             Object[] legalValues = (Object[]) test[0][3];
  57             System.out.println("test: defaultValue=" + defaultValue +
  58                                "; minValue=" + minValue +
  59                                "; maxValue=" + maxValue +
  60                                "; legalValues=" +
  61                                Arrays.deepToString(legalValues));
  62 
  63             if (test[1].length != 1) {
  64                 throw new Exception("OpenType list should have size 1: " +
  65                                     Arrays.deepToString(test[1]));
  66             }
  67             OpenType<?> openType = (OpenType<?>) test[1][0];
  68 
  69             Object[] valid = test[2];
  70             Object[] invalid = test[3];
  71 
  72             System.out.println("...valid=" + Arrays.deepToString(valid));
  73             System.out.println("...invalid=" + Arrays.deepToString(invalid));
  74 
  75             test(openType, defaultValue, minValue, maxValue, legalValues,
  76                  valid, invalid);
  77         }
  78 
  79         if (failure == null)
  80             System.out.println("Test passed");
  81         else
  82             throw new Exception("TEST FAILED: " + failure);
  83     }
  84 
  85     private static <T> void test(OpenType<T> openType, Object defaultValue,
  86                                  Comparable<?> minValue,
  87                                  Comparable<?> maxValue, Object[] legalValues,
  88                                  Object[] valid, Object[] invalid)
  89             throws Exception {
  90         /* This hack is needed to avoid grief from the parameter checking
  91            in the OpenMBean*InfoSupport constructors.  Since they are defined
  92            to check that the defaultValue etc are of the same type as the
  93            OpenType<T>, there is no way to pass a defaultValue etc when
  94            the type is OpenType<?>.  So either you have to write plain
  95            OpenType, and get unchecked warnings for every constructor
  96            invocation, or you do this, and get the unchecked warnings just
  97            here.  */
  98         test1(openType, (T) defaultValue, (Comparable<T>) minValue,
  99               (Comparable<T>) maxValue, (T[]) legalValues, valid, invalid);
 100     }
 101 
 102     private static <T> void test1(OpenType<T> openType, T defaultValue,
 103                                   Comparable<T> minValue,
 104                                   Comparable<T> maxValue, T[] legalValues,
 105                                   Object[] valid, Object[] invalid)
 106             throws Exception {
 107 
 108         if (legalValues != null && (minValue != null || maxValue != null))
 109             throw new Exception("Test case has both legals and min/max");
 110 
 111         if (defaultValue == null && minValue == null && maxValue == null &&
 112             legalValues == null) {
 113             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 114                                                    true, true, false),
 115                  valid, invalid);
 116             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 117                                                    true, true, false, nullD),
 118                  valid, invalid);
 119             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 120                                                    true, true, false, emptyD),
 121                  valid, invalid);
 122             test(new OpenMBeanParameterInfoSupport("name", "descr", openType),
 123                  valid, invalid);
 124             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 125                                                    nullD),
 126                  valid, invalid);
 127             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 128                                                    emptyD),
 129                  valid, invalid);
 130         }
 131 
 132         if (minValue == null && maxValue == null && legalValues == null) {
 133             Descriptor d = descriptor("defaultValue", defaultValue);
 134             test(new OpenMBeanAttributeInfoSupport("blah", "descr", openType,
 135                                                    true, true, false, d),
 136                  valid, invalid);
 137             test(new OpenMBeanAttributeInfoSupport("blah", "descr",
 138                                                    openType, true, true,
 139                                                    false, defaultValue),
 140                  valid, invalid);
 141             test(new OpenMBeanParameterInfoSupport("blah", "descr", openType,
 142                                                    d),
 143                  valid, invalid);
 144             test(new OpenMBeanParameterInfoSupport("blah", "descr", openType,
 145                                                    defaultValue),
 146                  valid, invalid);
 147         }
 148 
 149         if (legalValues == null) {
 150             Descriptor d = descriptor("defaultValue", defaultValue,
 151                                       "minValue", minValue,
 152                                       "maxValue", maxValue);
 153             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 154                                                    true, true, false, d),
 155                  valid, invalid);
 156             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 157                                                    true, true, false,
 158                                                    defaultValue,
 159                                                    minValue, maxValue),
 160                  valid, invalid);
 161             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 162                                                    d),
 163                  valid, invalid);
 164             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 165                                                    defaultValue,
 166                                                    minValue, maxValue),
 167                  valid, invalid);
 168         }
 169 
 170         if (minValue == null && maxValue == null) {
 171             // Legal values in descriptor can be either an array or a set
 172             Descriptor d1 = descriptor("defaultValue", defaultValue,
 173                                        "legalValues", legalValues);
 174             Descriptor d2;
 175             if (legalValues == null)
 176                 d2 = d1;
 177             else {
 178                 d2 = descriptor("defaultValue", defaultValue,
 179                                 "legalValues", arraySet(legalValues));
 180             }
 181             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 182                                                    true, true, false, d1),
 183                  valid, invalid);
 184             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 185                                                    true, true, false, d2),
 186                  valid, invalid);
 187             test(new OpenMBeanAttributeInfoSupport("name", "descr", openType,
 188                                                    true, true, false,
 189                                                    defaultValue, legalValues),
 190                  valid, invalid);
 191             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 192                                                    d1),
 193                  valid, invalid);
 194             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 195                                                    d2),
 196                  valid, invalid);
 197             test(new OpenMBeanParameterInfoSupport("name", "descr", openType,
 198                                                    defaultValue, legalValues),
 199                  valid, invalid);
 200         }
 201     }
 202 
 203     /* Test one of the objects.  Note that OpenMBeanAttributeInfo
 204        extends OpenMBeanParameterInfo, so OpenMBeanAttributeInfoSupport
 205        is-an OpenMBeanParameterInfo.  */
 206     private static void test(OpenMBeanParameterInfo info,
 207                              Object[] valid, Object[] invalid) {
 208         test1(info, valid, invalid);
 209 
 210         // Check that the constraints can be specified as strings
 211         // rather than objects
 212         if (info.getOpenType() instanceof SimpleType<?>) {
 213             Descriptor d = ((DescriptorRead) info).getDescriptor();
 214             String[] names = d.getFieldNames();
 215             Object[] values = d.getFieldValues(names);
 216             for (int i = 0; i < values.length; i++) {
 217                 if (values[i] == null)
 218                     continue;
 219                 if (names[i].equals("legalValues")) {
 220                     Collection<?> legals;
 221                     if (values[i] instanceof Collection<?>)
 222                         legals = (Collection<?>) values[i];
 223                     else
 224                         legals = Arrays.asList((Object[]) values[i]);
 225                     List<String> strings = new ArrayList<String>();
 226                     for (Object legal : legals)
 227                         strings.add(legal.toString());
 228                     values[i] = strings.toArray(new String[0]);
 229                 } else if (!(values[i] instanceof OpenType<?>))
 230                     values[i] = values[i].toString();
 231             }
 232             d = new ImmutableDescriptor(names, values);
 233             OpenType<?> ot = info.getOpenType();
 234             if (info instanceof OpenMBeanAttributeInfo) {
 235                 OpenMBeanAttributeInfo ai = (OpenMBeanAttributeInfo) info;
 236                 info = new OpenMBeanAttributeInfoSupport(info.getName(),
 237                                                          info.getDescription(),
 238                                                          info.getOpenType(),
 239                                                          ai.isReadable(),
 240                                                          ai.isWritable(),
 241                                                          ai.isIs(),
 242                                                          d);
 243             } else {
 244                 info = new OpenMBeanParameterInfoSupport(info.getName(),
 245                                                          info.getDescription(),
 246                                                          info.getOpenType(),
 247                                                          d);
 248             }
 249             test1(info, valid, invalid);
 250         }
 251     }
 252 
 253     private static void test1(OpenMBeanParameterInfo info,
 254                               Object[] valid, Object[] invalid) {
 255 
 256         for (Object x : valid) {
 257             if (!info.isValue(x)) {
 258                 fail("Object should be valid but is not: " + x + " for: " +
 259                      info);
 260             }
 261         }
 262 
 263         for (Object x : invalid) {
 264             if (info.isValue(x)) {
 265                 fail("Object should not be valid but is: " + x + " for: " +
 266                      info);
 267             }
 268         }
 269 
 270         /* If you specify e.g. minValue in a descriptor, then we arrange
 271            for getMinValue() to return the same value, and if you specify
 272            a minValue as a constructor parameter then we arrange for the
 273            descriptor to have a minValue entry.  Check that these values
 274            do in fact match.  */
 275 
 276         Descriptor d = ((DescriptorRead) info).getDescriptor();
 277 
 278         checkSameValue("defaultValue", info.getDefaultValue(),
 279                        d.getFieldValue("defaultValue"));
 280         checkSameValue("minValue", info.getMinValue(),
 281                        d.getFieldValue("minValue"));
 282         checkSameValue("maxValue", info.getMaxValue(),
 283                        d.getFieldValue("maxValue"));
 284         checkSameValues("legalValues", info.getLegalValues(),
 285                         d.getFieldValue("legalValues"));
 286     }
 287 
 288     private static void checkSameValue(String what, Object getterValue,
 289                                        Object descriptorValue) {
 290         if (getterValue == null) {
 291             if (descriptorValue != null) {
 292                 fail("Getter returned null but descriptor has entry for " +
 293                      what + ": " + descriptorValue);
 294             }
 295         } else if (descriptorValue == null) {
 296             fail("Getter returned value but descriptor has no entry for " +
 297                  what + ": " + getterValue);
 298         } else if (!getterValue.equals(descriptorValue) &&
 299                    !getterValue.toString().equals(descriptorValue)) {
 300             fail("For " + what + " getter returned " + getterValue +
 301                  " but descriptor entry is " + descriptorValue);
 302         }
 303     }
 304 
 305     private static void checkSameValues(String what, Set<?> getterValues,
 306                                         Object descriptorValues) {
 307         if (getterValues == null) {
 308             if (descriptorValues != null) {
 309                 fail("Getter returned null but descriptor has entry for " +
 310                      what + ": " + descriptorValues);
 311             }
 312         } else if (descriptorValues == null) {
 313             fail("Getter returned value but descriptor has no entry for " +
 314                  what + ": " + getterValues);
 315         } else {
 316             Set<?> descriptorValueSet;
 317             if (descriptorValues instanceof Set<?>)
 318                 descriptorValueSet = (Set<?>) descriptorValues;
 319             else
 320                 descriptorValueSet = arraySet((Object[]) descriptorValues);
 321             boolean same = true;
 322             if (getterValues.size() != descriptorValueSet.size())
 323                 same = false;
 324             else {
 325                 for (Object x : getterValues) {
 326                     if (!descriptorValueSet.contains(x)
 327                         && !descriptorValueSet.contains(x.toString())) {
 328                         same = false;
 329                         break;
 330                     }
 331                 }
 332             }
 333             if (!same) {
 334                 fail("For " + what + " getter returned " + getterValues +
 335                      " but descriptor entry is " + descriptorValueSet);
 336             }
 337         }
 338     }
 339 
 340     private static void fail(String why) {
 341         System.out.println("FAILED: " + why);
 342         failure = why;
 343     }
 344 
 345     private static Descriptor descriptor(Object... entries) {
 346         if (entries.length % 2 != 0)
 347             throw new RuntimeException("Odd length descriptor entries");
 348         String[] names = new String[entries.length / 2];
 349         Object[] values = new Object[entries.length / 2];
 350         for (int i = 0; i < entries.length; i += 2) {
 351             names[i / 2] = (String) entries[i];
 352             values[i / 2] = entries[i + 1];
 353         }
 354         return new ImmutableDescriptor(names, values);
 355     }
 356 
 357     private static <T> Set<T> arraySet(T[] array) {
 358         return new HashSet<T>(Arrays.asList(array));
 359     }
 360 
 361     private static final OpenType<?>
 362         ostring = SimpleType.STRING,
 363         oint = SimpleType.INTEGER,
 364         obool = SimpleType.BOOLEAN,
 365         olong = SimpleType.LONG,
 366         obyte = SimpleType.BYTE,
 367         ofloat = SimpleType.FLOAT,
 368         odouble = SimpleType.DOUBLE,
 369         ostringarray, ostringarray2;
 370     private static final CompositeType ocomposite;
 371     private static final CompositeData compositeData, compositeData2;
 372     static {
 373         try {
 374             ostringarray = new ArrayType<String[]>(1, ostring);
 375             ostringarray2 = new ArrayType<String[][]>(2, ostring);
 376             ocomposite =
 377                 new CompositeType("name", "descr",
 378                                   new String[] {"s", "i"},
 379                                   new String[] {"sdesc", "idesc"},
 380                                   new OpenType[] {ostring, oint});
 381             compositeData =
 382                 new CompositeDataSupport(ocomposite,
 383                                          new String[] {"s", "i"},
 384                                          new Object[] {"foo", 23});
 385             compositeData2 =
 386                 new CompositeDataSupport(ocomposite,
 387                                          new String[] {"s", "i"},
 388                                          new Object[] {"bar", -23});
 389         } catch (OpenDataException e) { // damn checked exceptions...
 390             throw new IllegalArgumentException(e.toString(), e);
 391         }
 392     }
 393 
 394     private static final Descriptor
 395         nullD = null,
 396         emptyD = ImmutableDescriptor.EMPTY_DESCRIPTOR;
 397 
 398     /* The elements of this array are grouped as follows.  Each
 399        element contains four Object[]s.  The first one is a set of
 400        four values: default value, min value, max value, legal values
 401        (an Object[]), some of which can be null.  These will be used
 402        to derive the OpenMBean*Info values to be tested.  The second
 403        is an array with one element that is the OpenType that will be
 404        given to the constructors of the OpenMBean*Infos.  The third
 405        element is a set of values that should be valid according to
 406        the constraints in the OpenMBean*Info.  The fourth is a set of
 407        values that should be invalid according to those
 408        constraints.  */
 409     private static final Object[][][] tests = {
 410 
 411         // Test cases when there are no constraints
 412         // Validity checking is limited to type of object
 413 
 414         {{null, null, null, null},
 415          {oint},
 416          {-1, 0, 1, Integer.MAX_VALUE, Integer.MIN_VALUE},
 417          {null, "noddy", 1.3, false, 3L, Long.MAX_VALUE, emptyD,
 418           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 419 
 420         {{null, null, null, null},
 421          {obool},
 422          {true, false},
 423          {null, "noddy", 1.3, 3, 3L, Long.MAX_VALUE, emptyD,
 424           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 425 
 426         {{null, null, null, null},
 427          {ostring},
 428          {"", "yes!"},
 429          {null, 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 430           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 431 
 432         {{null, null, null, null},
 433          {obyte},
 434          {Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0},
 435          {null, "noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 436           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 437 
 438         {{null, null, null, null},
 439          {ostringarray},
 440          {new String[0], new String[] {"hello", "world"}},
 441          {null, "noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 442           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 443 
 444         {{null, null, null, null},
 445          {ostringarray2},
 446          {new String[0][0], new String[][] {{"hello", "world"},
 447                                             {"goodbye", "cruel", "world"}}},
 448          {null, "noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 449           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 450 
 451         {{null, null, null, null},
 452          {ocomposite},
 453          {compositeData, compositeData2},
 454          {null, "noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 455           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 456 
 457         // Test cases where there is a default value, so null is allowed
 458 
 459         {{23, null, null, null},
 460          {oint},
 461          {null, -1, 0, 1, Integer.MAX_VALUE, Integer.MIN_VALUE},
 462          {"noddy", 1.3, false, 3L, Long.MAX_VALUE, emptyD,
 463           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 464 
 465         {{true, null, null, null},
 466          {obool},
 467          {null, true, false},
 468          {"noddy", 1.3, 3, 3L, Long.MAX_VALUE, emptyD,
 469           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 470 
 471         {{"foo", null, null, null},
 472          {ostring},
 473          {null, "", "yes!"},
 474          {1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 475           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 476 
 477         {{(byte) 23, null, null, null},
 478          {obyte},
 479          {null, Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0},
 480          {"noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 481           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 482 
 483         {{compositeData, null, null, null},
 484          {ocomposite},
 485          {null, compositeData, compositeData2},
 486          {"noddy", 1.3, 3, false, 3L, Long.MAX_VALUE, emptyD,
 487           new int[2], new Integer[2], new Integer[] {3}, new Integer[0]}},
 488 
 489         // Test cases where there is a min and/or max, with or without default
 490 
 491         {{23, 0, 50, null},
 492          {oint},
 493          {null, 0, 25, 50},
 494          {"noddy", -1, 51, Integer.MIN_VALUE, Integer.MAX_VALUE, 25L}},
 495 
 496         {{null, 0, 50, null},
 497          {oint},
 498          {0, 25, 50},
 499          {null, "noddy", -1, 51, Integer.MIN_VALUE, Integer.MAX_VALUE, 25L}},
 500 
 501         {{null, 0, null, null},
 502          {oint},
 503          {0, 25, 50, Integer.MAX_VALUE},
 504          {null, "noddy", -1, Integer.MIN_VALUE, 25L}},
 505 
 506         {{null, null, 50, null},
 507          {oint},
 508          {Integer.MIN_VALUE, -1, 0, 25, 50},
 509          {null, "noddy", 51, Integer.MAX_VALUE, 25L}},
 510 
 511         {{"go", "a", "z~", null},
 512          {ostring},
 513          {null, "a", "z~", "zzzz", "z!"},
 514          {"A", "~", "", -1}},
 515 
 516         // Test cases where there is a set of legal values
 517 
 518         {{23, null, null, new Integer[] {2, 3, 5, 7, 11, 13, 17, 23}},
 519          {oint},
 520          {null, 2, 11, 23},
 521          {"noddy", -1, 1, 51, Integer.MIN_VALUE, Integer.MAX_VALUE, 25L}},
 522 
 523         {{null, null, null, new CompositeData[] {compositeData}},
 524          {ocomposite},
 525          {compositeData},
 526          {null, compositeData2, "noddy"}},
 527 
 528         {{null, null, null, new Long[0]},
 529          {olong},
 530          {}, // constraint is impossible to satisfy!
 531          {null, 23L, "x", 23}},
 532     };
 533 }