1 /*
   2  * Copyright (c) 2004, 2019, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.management;
  27 
  28 import com.sun.jmx.mbeanserver.Util;
  29 import java.io.InvalidObjectException;
  30 import java.lang.reflect.Array;
  31 import java.util.Arrays;
  32 import java.util.Comparator;
  33 import java.util.Map;
  34 import java.util.SortedMap;
  35 import java.util.TreeMap;
  36 
  37 /**
  38  * An immutable descriptor.
  39  * @since 1.6
  40  */
  41 public class ImmutableDescriptor implements Descriptor {
  42     private static final long serialVersionUID = 8853308591080540165L;
  43 
  44     /**
  45      * The names of the fields in this ImmutableDescriptor with their
  46      * original case.  The names must be in alphabetical order as determined
  47      * by {@link String#CASE_INSENSITIVE_ORDER}.
  48      */
  49     private final String[] names;
  50     /**
  51      * The values of the fields in this ImmutableDescriptor.  The
  52      * elements in this array match the corresponding elements in the
  53      * {@code names} array.
  54      */
  55     @SuppressWarnings("serial") // Conditionally serializable
  56     private final Object[] values;
  57 
  58     private transient int hashCode = -1;
  59 
  60     /**
  61      * An empty descriptor.
  62      */
  63     public static final ImmutableDescriptor EMPTY_DESCRIPTOR =
  64             new ImmutableDescriptor();
  65 
  66     /**
  67      * Construct a descriptor containing the given fields and values.
  68      *
  69      * @param fieldNames the field names
  70      * @param fieldValues the field values
  71      * @throws IllegalArgumentException if either array is null, or
  72      * if the arrays have different sizes, or
  73      * if a field name is null or empty, or if the same field name
  74      * appears more than once.
  75      */
  76     public ImmutableDescriptor(String[] fieldNames, Object[] fieldValues) {
  77         this(makeMap(fieldNames, fieldValues));
  78     }
  79 
  80     /**
  81      * Construct a descriptor containing the given fields.  Each String
  82      * must be of the form {@code fieldName=fieldValue}.  The field name
  83      * ends at the first {@code =} character; for example if the String
  84      * is {@code a=b=c} then the field name is {@code a} and its value
  85      * is {@code b=c}.
  86      *
  87      * @param fields the field names
  88      * @throws IllegalArgumentException if the parameter is null, or
  89      * if a field name is empty, or if the same field name appears
  90      * more than once, or if one of the strings does not contain
  91      * an {@code =} character.
  92      */
  93     public ImmutableDescriptor(String... fields) {
  94         this(makeMap(fields));
  95     }
  96 
  97     /**
  98      * <p>Construct a descriptor where the names and values of the fields
  99      * are the keys and values of the given Map.</p>
 100      *
 101      * @param fields the field names and values
 102      * @throws IllegalArgumentException if the parameter is null, or
 103      * if a field name is null or empty, or if the same field name appears
 104      * more than once (which can happen because field names are not case
 105      * sensitive).
 106      */
 107     public ImmutableDescriptor(Map<String, ?> fields) {
 108         if (fields == null)
 109             throw new IllegalArgumentException("Null Map");
 110         SortedMap<String, Object> map =
 111                 new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
 112         for (Map.Entry<String, ?> entry : fields.entrySet()) {
 113             String name = entry.getKey();
 114             if (name == null || name.isEmpty())
 115                 throw new IllegalArgumentException("Empty or null field name");
 116             if (map.containsKey(name))
 117                 throw new IllegalArgumentException("Duplicate name: " + name);
 118             map.put(name, entry.getValue());
 119         }
 120         int size = map.size();
 121         this.names = map.keySet().toArray(new String[size]);
 122         this.values = map.values().toArray(new Object[size]);
 123     }
 124 
 125     /**
 126      * This method can replace a deserialized instance of this
 127      * class with another instance.  For example, it might replace
 128      * a deserialized empty ImmutableDescriptor with
 129      * {@link #EMPTY_DESCRIPTOR}.
 130      *
 131      * @return the replacement object, which may be {@code this}.
 132      *
 133      * @throws InvalidObjectException if the read object has invalid fields.
 134      */
 135     private Object readResolve() throws InvalidObjectException {
 136 
 137         boolean bad = false;
 138         if (names == null || values == null || names.length != values.length)
 139             bad = true;
 140         if (!bad) {
 141             if (names.length == 0 && getClass() == ImmutableDescriptor.class)
 142                 return EMPTY_DESCRIPTOR;
 143             final Comparator<String> compare = String.CASE_INSENSITIVE_ORDER;
 144             String lastName = ""; // also catches illegal null name
 145             for (int i = 0; i < names.length; i++) {
 146                 if (names[i] == null ||
 147                         compare.compare(lastName, names[i]) >= 0) {
 148                     bad = true;
 149                     break;
 150                 }
 151                 lastName = names[i];
 152             }
 153         }
 154         if (bad)
 155             throw new InvalidObjectException("Bad names or values");
 156 
 157         return this;
 158     }
 159 
 160     private static SortedMap<String, ?> makeMap(String[] fieldNames,
 161                                                 Object[] fieldValues) {
 162         if (fieldNames == null || fieldValues == null)
 163             throw new IllegalArgumentException("Null array parameter");
 164         if (fieldNames.length != fieldValues.length)
 165             throw new IllegalArgumentException("Different size arrays");
 166         SortedMap<String, Object> map =
 167                 new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
 168         for (int i = 0; i < fieldNames.length; i++) {
 169             String name = fieldNames[i];
 170             if (name == null || name.isEmpty())
 171                 throw new IllegalArgumentException("Empty or null field name");
 172             Object old = map.put(name, fieldValues[i]);
 173             if (old != null) {
 174                 throw new IllegalArgumentException("Duplicate field name: " +
 175                                                    name);
 176             }
 177         }
 178         return map;
 179     }
 180 
 181     private static SortedMap<String, ?> makeMap(String[] fields) {
 182         if (fields == null)
 183             throw new IllegalArgumentException("Null fields parameter");
 184         String[] fieldNames = new String[fields.length];
 185         String[] fieldValues = new String[fields.length];
 186         for (int i = 0; i < fields.length; i++) {
 187             String field = fields[i];
 188             int eq = field.indexOf('=');
 189             if (eq < 0) {
 190                 throw new IllegalArgumentException("Missing = character: " +
 191                                                    field);
 192             }
 193             fieldNames[i] = field.substring(0, eq);
 194             // makeMap will catch the case where the name is empty
 195             fieldValues[i] = field.substring(eq + 1);
 196         }
 197         return makeMap(fieldNames, fieldValues);
 198     }
 199 
 200     /**
 201      * <p>Return an {@code ImmutableDescriptor} whose contents are the union of
 202      * the given descriptors.  Every field name that appears in any of
 203      * the descriptors will appear in the result with the
 204      * value that it has when the method is called.  Subsequent changes
 205      * to any of the descriptors do not affect the ImmutableDescriptor
 206      * returned here.</p>
 207      *
 208      * <p>In the simplest case, there is only one descriptor and the
 209      * returned {@code ImmutableDescriptor} is a copy of its fields at the
 210      * time this method is called:</p>
 211      *
 212      * <pre>
 213      * Descriptor d = something();
 214      * ImmutableDescriptor copy = ImmutableDescriptor.union(d);
 215      * </pre>
 216      *
 217      * @param descriptors the descriptors to be combined.  Any of the
 218      * descriptors can be null, in which case it is skipped.
 219      *
 220      * @return an {@code ImmutableDescriptor} that is the union of the given
 221      * descriptors.  The returned object may be identical to one of the
 222      * input descriptors if it is an ImmutableDescriptor that contains all of
 223      * the required fields.
 224      *
 225      * @throws IllegalArgumentException if two Descriptors contain the
 226      * same field name with different associated values.  Primitive array
 227      * values are considered the same if they are of the same type with
 228      * the same elements.  Object array values are considered the same if
 229      * {@link Arrays#deepEquals(Object[],Object[])} returns true.
 230      */
 231     public static ImmutableDescriptor union(Descriptor... descriptors) {
 232         // Optimize the case where exactly one Descriptor is non-Empty
 233         // and it is immutable - we can just return it.
 234         int index = findNonEmpty(descriptors, 0);
 235         if (index < 0)
 236             return EMPTY_DESCRIPTOR;
 237         if (descriptors[index] instanceof ImmutableDescriptor
 238                 && findNonEmpty(descriptors, index + 1) < 0)
 239             return (ImmutableDescriptor) descriptors[index];
 240 
 241         Map<String, Object> map =
 242             new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
 243         ImmutableDescriptor biggestImmutable = EMPTY_DESCRIPTOR;
 244         for (Descriptor d : descriptors) {
 245             if (d != null) {
 246                 String[] names;
 247                 if (d instanceof ImmutableDescriptor) {
 248                     ImmutableDescriptor id = (ImmutableDescriptor) d;
 249                     names = id.names;
 250                     if (id.getClass() == ImmutableDescriptor.class
 251                             && names.length > biggestImmutable.names.length)
 252                         biggestImmutable = id;
 253                 } else
 254                     names = d.getFieldNames();
 255                 for (String n : names) {
 256                     Object v = d.getFieldValue(n);
 257                     Object old = map.put(n, v);
 258                     if (old != null) {
 259                         boolean equal;
 260                         if (old.getClass().isArray()) {
 261                             equal = Arrays.deepEquals(new Object[] {old},
 262                                                       new Object[] {v});
 263                         } else
 264                             equal = old.equals(v);
 265                         if (!equal) {
 266                             final String msg =
 267                                 "Inconsistent values for descriptor field " +
 268                                 n + ": " + old + " :: " + v;
 269                             throw new IllegalArgumentException(msg);
 270                         }
 271                     }
 272                 }
 273             }
 274         }
 275         if (biggestImmutable.names.length == map.size())
 276             return biggestImmutable;
 277         return new ImmutableDescriptor(map);
 278     }
 279 
 280     private static boolean isEmpty(Descriptor d) {
 281         if (d == null)
 282             return true;
 283         else if (d instanceof ImmutableDescriptor)
 284             return ((ImmutableDescriptor) d).names.length == 0;
 285         else
 286             return (d.getFieldNames().length == 0);
 287     }
 288 
 289     private static int findNonEmpty(Descriptor[] ds, int start) {
 290         for (int i = start; i < ds.length; i++) {
 291             if (!isEmpty(ds[i]))
 292                 return i;
 293         }
 294         return -1;
 295     }
 296 
 297     private int fieldIndex(String name) {
 298         return Arrays.binarySearch(names, name, String.CASE_INSENSITIVE_ORDER);
 299     }
 300 
 301     public final Object getFieldValue(String fieldName) {
 302         checkIllegalFieldName(fieldName);
 303         int i = fieldIndex(fieldName);
 304         if (i < 0)
 305             return null;
 306         Object v = values[i];
 307         if (v == null || !v.getClass().isArray())
 308             return v;
 309         if (v instanceof Object[])
 310             return ((Object[]) v).clone();
 311         // clone the primitive array, could use an 8-way if/else here
 312         int len = Array.getLength(v);
 313         Object a = Array.newInstance(v.getClass().getComponentType(), len);
 314         System.arraycopy(v, 0, a, 0, len);
 315         return a;
 316     }
 317 
 318     public final String[] getFields() {
 319         String[] result = new String[names.length];
 320         for (int i = 0; i < result.length; i++) {
 321             Object value = values[i];
 322             if (value == null)
 323                 value = "";
 324             else if (!(value instanceof String))
 325                 value = "(" + value + ")";
 326             result[i] = names[i] + "=" + value;
 327         }
 328         return result;
 329     }
 330 
 331     public final Object[] getFieldValues(String... fieldNames) {
 332         if (fieldNames == null)
 333             return values.clone();
 334         Object[] result = new Object[fieldNames.length];
 335         for (int i = 0; i < fieldNames.length; i++) {
 336             String name = fieldNames[i];
 337             if (name != null && !name.isEmpty())
 338                 result[i] = getFieldValue(name);
 339         }
 340         return result;
 341     }
 342 
 343     public final String[] getFieldNames() {
 344         return names.clone();
 345     }
 346 
 347     /**
 348      * Compares this descriptor to the given object.  The objects are equal if
 349      * the given object is also a Descriptor, and if the two Descriptors have
 350      * the same field names (possibly differing in case) and the same
 351      * associated values.  The respective values for a field in the two
 352      * Descriptors are equal if the following conditions hold:
 353      *
 354      * <ul>
 355      * <li>If one value is null then the other must be too.</li>
 356      * <li>If one value is a primitive array then the other must be a primitive
 357      * array of the same type with the same elements.</li>
 358      * <li>If one value is an object array then the other must be too and
 359      * {@link Arrays#deepEquals(Object[],Object[])} must return true.</li>
 360      * <li>Otherwise {@link Object#equals(Object)} must return true.</li>
 361      * </ul>
 362      *
 363      * @param o the object to compare with.
 364      *
 365      * @return {@code true} if the objects are the same; {@code false}
 366      * otherwise.
 367      *
 368      */
 369     // Note: this Javadoc is copied from javax.management.Descriptor
 370     //       due to 6369229.
 371     @Override
 372     public boolean equals(Object o) {
 373         if (o == this)
 374             return true;
 375         if (!(o instanceof Descriptor))
 376             return false;
 377         String[] onames;
 378         if (o instanceof ImmutableDescriptor) {
 379             onames = ((ImmutableDescriptor) o).names;
 380         } else {
 381             onames = ((Descriptor) o).getFieldNames();
 382             Arrays.sort(onames, String.CASE_INSENSITIVE_ORDER);
 383         }
 384         if (names.length != onames.length)
 385             return false;
 386         for (int i = 0; i < names.length; i++) {
 387             if (!names[i].equalsIgnoreCase(onames[i]))
 388                 return false;
 389         }
 390         Object[] ovalues;
 391         if (o instanceof ImmutableDescriptor)
 392             ovalues = ((ImmutableDescriptor) o).values;
 393         else
 394             ovalues = ((Descriptor) o).getFieldValues(onames);
 395         return Arrays.deepEquals(values, ovalues);
 396     }
 397 
 398     /**
 399      * <p>Returns the hash code value for this descriptor.  The hash
 400      * code is computed as the sum of the hash codes for each field in
 401      * the descriptor.  The hash code of a field with name {@code n}
 402      * and value {@code v} is {@code n.toLowerCase().hashCode() ^ h}.
 403      * Here {@code h} is the hash code of {@code v}, computed as
 404      * follows:</p>
 405      *
 406      * <ul>
 407      * <li>If {@code v} is null then {@code h} is 0.</li>
 408      * <li>If {@code v} is a primitive array then {@code h} is computed using
 409      * the appropriate overloading of {@code java.util.Arrays.hashCode}.</li>
 410      * <li>If {@code v} is an object array then {@code h} is computed using
 411      * {@link Arrays#deepHashCode(Object[])}.</li>
 412      * <li>Otherwise {@code h} is {@code v.hashCode()}.</li>
 413      * </ul>
 414      *
 415      * @return A hash code value for this object.
 416      *
 417      */
 418     // Note: this Javadoc is copied from javax.management.Descriptor
 419     //       due to 6369229.
 420     @Override
 421     public int hashCode() {
 422         if (hashCode == -1) {
 423             hashCode = Util.hashCode(names, values);
 424         }
 425         return hashCode;
 426     }
 427 
 428     @Override
 429     public String toString() {
 430         StringBuilder sb = new StringBuilder("{");
 431         for (int i = 0; i < names.length; i++) {
 432             if (i > 0)
 433                 sb.append(", ");
 434             sb.append(names[i]).append("=");
 435             Object v = values[i];
 436             if (v != null && v.getClass().isArray()) {
 437                 String s = Arrays.deepToString(new Object[] {v});
 438                 s = s.substring(1, s.length() - 1); // remove [...]
 439                 v = s;
 440             }
 441             sb.append(String.valueOf(v));
 442         }
 443         return sb.append("}").toString();
 444     }
 445 
 446     /**
 447      * Returns true if all of the fields have legal values given their
 448      * names.  This method always returns true, but a subclass can
 449      * override it to return false when appropriate.
 450      *
 451      * @return true if the values are legal.
 452      *
 453      * @exception RuntimeOperationsException if the validity checking fails.
 454      * The method returns false if the descriptor is not valid, but throws
 455      * this exception if the attempt to determine validity fails.
 456      */
 457     public boolean isValid() {
 458         return true;
 459     }
 460 
 461     /**
 462      * <p>Returns a descriptor which is equal to this descriptor.
 463      * Changes to the returned descriptor will have no effect on this
 464      * descriptor, and vice versa.</p>
 465      *
 466      * <p>This method returns the object on which it is called.
 467      * A subclass can override it
 468      * to return another object provided the contract is respected.
 469      *
 470      * @exception RuntimeOperationsException for illegal value for field Names
 471      * or field Values.
 472      * If the descriptor construction fails for any reason, this exception will
 473      * be thrown.
 474      */
 475     @Override
 476     public Descriptor clone() {
 477         return this;
 478     }
 479 
 480     /**
 481      * This operation is unsupported since this class is immutable.  If
 482      * this call would change a mutable descriptor with the same contents,
 483      * then a {@link RuntimeOperationsException} wrapping an
 484      * {@link UnsupportedOperationException} is thrown.  Otherwise,
 485      * the behavior is the same as it would be for a mutable descriptor:
 486      * either an exception is thrown because of illegal parameters, or
 487      * there is no effect.
 488      */
 489     public final void setFields(String[] fieldNames, Object[] fieldValues)
 490         throws RuntimeOperationsException {
 491         if (fieldNames == null || fieldValues == null)
 492             illegal("Null argument");
 493         if (fieldNames.length != fieldValues.length)
 494             illegal("Different array sizes");
 495         for (int i = 0; i < fieldNames.length; i++)
 496             checkIllegalFieldName(fieldNames[i]);
 497         for (int i = 0; i < fieldNames.length; i++)
 498             setField(fieldNames[i], fieldValues[i]);
 499     }
 500 
 501     /**
 502      * This operation is unsupported since this class is immutable.  If
 503      * this call would change a mutable descriptor with the same contents,
 504      * then a {@link RuntimeOperationsException} wrapping an
 505      * {@link UnsupportedOperationException} is thrown.  Otherwise,
 506      * the behavior is the same as it would be for a mutable descriptor:
 507      * either an exception is thrown because of illegal parameters, or
 508      * there is no effect.
 509      */
 510     public final void setField(String fieldName, Object fieldValue)
 511         throws RuntimeOperationsException {
 512         checkIllegalFieldName(fieldName);
 513         int i = fieldIndex(fieldName);
 514         if (i < 0)
 515             unsupported();
 516         Object value = values[i];
 517         if ((value == null) ?
 518                 (fieldValue != null) :
 519                 !value.equals(fieldValue))
 520             unsupported();
 521     }
 522 
 523     /**
 524      * Removes a field from the descriptor.
 525      *
 526      * @param fieldName String name of the field to be removed.
 527      * If the field name is illegal or the field is not found,
 528      * no exception is thrown.
 529      *
 530      * @exception RuntimeOperationsException if a field of the given name
 531      * exists and the descriptor is immutable.  The wrapped exception will
 532      * be an {@link UnsupportedOperationException}.
 533      */
 534     public final void removeField(String fieldName) {
 535         if (fieldName != null && fieldIndex(fieldName) >= 0)
 536             unsupported();
 537     }
 538 
 539     static Descriptor nonNullDescriptor(Descriptor d) {
 540         if (d == null)
 541             return EMPTY_DESCRIPTOR;
 542         else
 543             return d;
 544     }
 545 
 546     private static void checkIllegalFieldName(String name) {
 547         if (name == null || name.isEmpty())
 548             illegal("Null or empty field name");
 549     }
 550 
 551     private static void unsupported() {
 552         UnsupportedOperationException uoe =
 553             new UnsupportedOperationException("Descriptor is read-only");
 554         throw new RuntimeOperationsException(uoe);
 555     }
 556 
 557     private static void illegal(String message) {
 558         IllegalArgumentException iae = new IllegalArgumentException(message);
 559         throw new RuntimeOperationsException(iae);
 560     }
 561 }