blob: 686ca4ecc60c070acd113bf541082cd6d7e967c0 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package javax.management;
27
28import java.io.InvalidObjectException;
29import java.lang.reflect.Array;
30import java.util.Arrays;
31import java.util.Comparator;
32import java.util.Map;
33import java.util.SortedMap;
34import java.util.TreeMap;
35
36/**
37 * An immutable descriptor.
38 * @since 1.6
39 */
40public 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}