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