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