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 trueif 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 }
561