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