jdk/src/java.management/share/classes/javax/management/ImmutableDescriptor.java
changeset 25859 3317bb8137f4
parent 23010 6dadb192ad81
child 29927 9cc3e111a1d8
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     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 }