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