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      */
ImmutableDescriptor(String[] fieldNames, Object[] fieldValues)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      */
ImmutableDescriptor(String... fields)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      */
ImmutableDescriptor(Map<String, ?> fields)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      */
readResolve()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 
makeMap(String[] fieldNames, Object[] fieldValues)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 
makeMap(String[] fields)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      */
union(Descriptor... descriptors)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 
isEmpty(Descriptor d)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 
findNonEmpty(Descriptor[] ds, int start)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 
fieldIndex(String name)297     private int fieldIndex(String name) {
298         return Arrays.binarySearch(names, name, String.CASE_INSENSITIVE_ORDER);
299     }
300 
getFieldValue(String fieldName)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 
getFields()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 
getFieldValues(String... fieldNames)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 
getFieldNames()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
equals(Object o)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
hashCode()421     public int hashCode() {
422         if (hashCode == -1) {
423             hashCode = Util.hashCode(names, values);
424         }
425         return hashCode;
426     }
427 
428     @Override
toString()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      */
isValid()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
clone()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      */
setFields(String[] fieldNames, Object[] fieldValues)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      */
setField(String fieldName, Object fieldValue)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      */
removeField(String fieldName)534     public final void removeField(String fieldName) {
535         if (fieldName != null && fieldIndex(fieldName) >= 0)
536             unsupported();
537     }
538 
nonNullDescriptor(Descriptor d)539     static Descriptor nonNullDescriptor(Descriptor d) {
540         if (d == null)
541             return EMPTY_DESCRIPTOR;
542         else
543             return d;
544     }
545 
checkIllegalFieldName(String name)546     private static void checkIllegalFieldName(String name) {
547         if (name == null || name.isEmpty())
548             illegal("Null or empty field name");
549     }
550 
unsupported()551     private static void unsupported() {
552         UnsupportedOperationException uoe =
553             new UnsupportedOperationException("Descriptor is read-only");
554         throw new RuntimeOperationsException(uoe);
555     }
556 
illegal(String message)557     private static void illegal(String message) {
558         IllegalArgumentException iae = new IllegalArgumentException(message);
559         throw new RuntimeOperationsException(iae);
560     }
561 }
562