001 /*
002 * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.management;
027
028 import java.io.InvalidObjectException;
029 import java.lang.reflect.Array;
030 import java.util.Arrays;
031 import java.util.Comparator;
032 import java.util.Map;
033 import java.util.SortedMap;
034 import java.util.TreeMap;
035
036 /**
037 * An immutable descriptor.
038 * @since 1.6
039 */
040 public class ImmutableDescriptor implements Descriptor {
041 private static final long serialVersionUID = 8853308591080540165L;
042
043 /**
044 * The names of the fields in this ImmutableDescriptor with their
045 * original case. The names must be in alphabetical order as determined
046 * by {@link String#CASE_INSENSITIVE_ORDER}.
047 */
048 private final String[] names;
049 /**
050 * The values of the fields in this ImmutableDescriptor. The
051 * elements in this array match the corresponding elements in the
052 * {@code names} array.
053 */
054 private final Object[] values;
055
056 private transient int hashCode = -1;
057
058 /**
059 * An empty descriptor.
060 */
061 public static final ImmutableDescriptor EMPTY_DESCRIPTOR = new ImmutableDescriptor();
062
063 /**
064 * Construct a descriptor containing the given fields and values.
065 *
066 * @throws IllegalArgumentException if either array is null, or
067 * if the arrays have different sizes, or
068 * if a field name is null or empty, or if the same field name
069 * appears more than once.
070 */
071 public ImmutableDescriptor(String[] fieldNames, Object[] fieldValues) {
072 this (makeMap(fieldNames, fieldValues));
073 }
074
075 /**
076 * Construct a descriptor containing the given fields. Each String
077 * must be of the form {@code fieldName=fieldValue}. The field name
078 * ends at the first {@code =} character; for example if the String
079 * is {@code a=b=c} then the field name is {@code a} and its value
080 * is {@code b=c}.
081 *
082 * @throws IllegalArgumentException if the parameter is null, or
083 * if a field name is empty, or if the same field name appears
084 * more than once, or if one of the strings does not contain
085 * an {@code =} character.
086 */
087 public ImmutableDescriptor(String... fields) {
088 this (makeMap(fields));
089 }
090
091 /**
092 * <p>Construct a descriptor where the names and values of the fields
093 * are the keys and values of the given Map.</p>
094 *
095 * @throws IllegalArgumentException if the parameter is null, or
096 * if a field name is null or empty, or if the same field name appears
097 * more than once (which can happen because field names are not case
098 * sensitive).
099 */
100 public ImmutableDescriptor(Map<String, ?> fields) {
101 if (fields == null)
102 throw new IllegalArgumentException("Null Map");
103 SortedMap<String, Object> map = new TreeMap<String, Object>(
104 String.CASE_INSENSITIVE_ORDER);
105 for (Map.Entry<String, ?> entry : fields.entrySet()) {
106 String name = entry.getKey();
107 if (name == null || name.equals(""))
108 throw new IllegalArgumentException(
109 "Empty or null field name");
110 if (map.containsKey(name))
111 throw new IllegalArgumentException("Duplicate name: "
112 + name);
113 map.put(name, entry.getValue());
114 }
115 int size = map.size();
116 this .names = map.keySet().toArray(new String[size]);
117 this .values = map.values().toArray(new Object[size]);
118 }
119
120 /**
121 * This method can replace a deserialized instance of this
122 * class with another instance. For example, it might replace
123 * a deserialized empty ImmutableDescriptor with
124 * {@link #EMPTY_DESCRIPTOR}.
125 *
126 * @return the replacement object, which may be {@code this}.
127 *
128 * @throws InvalidObjectException if the read object has invalid fields.
129 */
130 private Object readResolve() throws InvalidObjectException {
131 if (names.length == 0
132 && getClass() == ImmutableDescriptor.class)
133 return EMPTY_DESCRIPTOR;
134
135 boolean bad = false;
136 if (names == null || values == null
137 || names.length != values.length)
138 bad = true;
139 if (!bad) {
140 final Comparator<String> compare = String.CASE_INSENSITIVE_ORDER;
141 String lastName = ""; // also catches illegal null name
142 for (int i = 0; i < names.length; i++) {
143 if (names[i] == null
144 || compare.compare(lastName, names[i]) >= 0) {
145 bad = true;
146 break;
147 }
148 lastName = names[i];
149 }
150 }
151 if (bad)
152 throw new InvalidObjectException("Bad names or values");
153
154 return this ;
155 }
156
157 private static SortedMap<String, ?> makeMap(String[] fieldNames,
158 Object[] fieldValues) {
159 if (fieldNames == null || fieldValues == null)
160 throw new IllegalArgumentException("Null array parameter");
161 if (fieldNames.length != fieldValues.length)
162 throw new IllegalArgumentException("Different size arrays");
163 SortedMap<String, Object> map = new TreeMap<String, Object>(
164 String.CASE_INSENSITIVE_ORDER);
165 for (int i = 0; i < fieldNames.length; i++) {
166 String name = fieldNames[i];
167 if (name == null || name.equals(""))
168 throw new IllegalArgumentException(
169 "Empty or null field name");
170 Object old = map.put(name, fieldValues[i]);
171 if (old != null) {
172 throw new IllegalArgumentException(
173 "Duplicate field name: " + name);
174 }
175 }
176 return map;
177 }
178
179 private static SortedMap<String, ?> makeMap(String[] fields) {
180 if (fields == null)
181 throw new IllegalArgumentException("Null fields parameter");
182 String[] fieldNames = new String[fields.length];
183 String[] fieldValues = new String[fields.length];
184 for (int i = 0; i < fields.length; i++) {
185 String field = fields[i];
186 int eq = field.indexOf('=');
187 if (eq < 0) {
188 throw new IllegalArgumentException(
189 "Missing = character: " + field);
190 }
191 fieldNames[i] = field.substring(0, eq);
192 // makeMap will catch the case where the name is empty
193 fieldValues[i] = field.substring(eq + 1);
194 }
195 return makeMap(fieldNames, fieldValues);
196 }
197
198 /**
199 * <p>Return an {@code ImmutableDescriptor} whose contents are the union of
200 * the given descriptors. Every field name that appears in any of
201 * the descriptors will appear in the result with the
202 * value that it has when the method is called. Subsequent changes
203 * to any of the descriptors do not affect the ImmutableDescriptor
204 * returned here.</p>
205 *
206 * <p>In the simplest case, there is only one descriptor and the
207 * returned {@code ImmutableDescriptor} is a copy of its fields at the
208 * time this method is called:</p>
209 *
210 * <pre>
211 * Descriptor d = something();
212 * ImmutableDescriptor copy = ImmutableDescriptor.union(d);
213 * </pre>
214 *
215 * @param descriptors the descriptors to be combined. Any of the
216 * descriptors can be null, in which case it is skipped.
217 *
218 * @return an {@code ImmutableDescriptor} that is the union of the given
219 * descriptors. The returned object may be identical to one of the
220 * input descriptors if it is an ImmutableDescriptor that contains all of
221 * the required fields.
222 *
223 * @throws IllegalArgumentException if two Descriptors contain the
224 * same field name with different associated values. Primitive array
225 * values are considered the same if they are of the same type with
226 * the same elements. Object array values are considered the same if
227 * {@link Arrays#deepEquals(Object[],Object[])} returns true.
228 */
229 public static ImmutableDescriptor union(Descriptor... descriptors) {
230 // Optimize the case where exactly one Descriptor is non-Empty
231 // and it is immutable - we can just return it.
232 int index = findNonEmpty(descriptors, 0);
233 if (index < 0)
234 return EMPTY_DESCRIPTOR;
235 if (descriptors[index] instanceof ImmutableDescriptor
236 && findNonEmpty(descriptors, index + 1) < 0)
237 return (ImmutableDescriptor) descriptors[index];
238
239 Map<String, Object> map = new TreeMap<String, Object>(
240 String.CASE_INSENSITIVE_ORDER);
241 ImmutableDescriptor biggestImmutable = EMPTY_DESCRIPTOR;
242 for (Descriptor d : descriptors) {
243 if (d != null) {
244 String[] names;
245 if (d instanceof ImmutableDescriptor) {
246 ImmutableDescriptor id = (ImmutableDescriptor) d;
247 names = id.names;
248 if (id.getClass() == ImmutableDescriptor.class
249 && names.length > biggestImmutable.names.length)
250 biggestImmutable = id;
251 } else
252 names = d.getFieldNames();
253 for (String n : names) {
254 Object v = d.getFieldValue(n);
255 Object old = map.put(n, v);
256 if (old != null) {
257 boolean equal;
258 if (old.getClass().isArray()) {
259 equal = Arrays.deepEquals(
260 new Object[] { old },
261 new Object[] { v });
262 } else
263 equal = old.equals(v);
264 if (!equal) {
265 final String msg = "Inconsistent values for descriptor field "
266 + n + ": " + old + " :: " + v;
267 throw new IllegalArgumentException(msg);
268 }
269 }
270 }
271 }
272 }
273 if (biggestImmutable.names.length == map.size())
274 return biggestImmutable;
275 return new ImmutableDescriptor(map);
276 }
277
278 private static boolean isEmpty(Descriptor d) {
279 if (d == null)
280 return true;
281 else if (d instanceof ImmutableDescriptor)
282 return ((ImmutableDescriptor) d).names.length == 0;
283 else
284 return (d.getFieldNames().length == 0);
285 }
286
287 private static int findNonEmpty(Descriptor[] ds, int start) {
288 for (int i = start; i < ds.length; i++) {
289 if (!isEmpty(ds[i]))
290 return i;
291 }
292 return -1;
293 }
294
295 private int fieldIndex(String name) {
296 return Arrays.binarySearch(names, name,
297 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(),
313 len);
314 System.arraycopy(v, 0, a, 0, len);
315 return a;
316 }
317
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
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.equals(""))
338 result[i] = getFieldValue(name);
339 }
340 return result;
341 }
342
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:</p>
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 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 public int hashCode() {
420 if (hashCode == -1) {
421 int hash = 0;
422 for (int i = 0; i < names.length; i++) {
423 Object v = values[i];
424 int h;
425 if (v == null)
426 h = 0;
427 else if (v instanceof Object[])
428 h = Arrays.deepHashCode((Object[]) v);
429 else if (v.getClass().isArray()) {
430 h = Arrays.deepHashCode(new Object[] { v }) - 31;
431 // hashcode of a list containing just v is
432 // v.hashCode() + 31, see List.hashCode()
433 } else
434 h = v.hashCode();
435 hash += names[i].toLowerCase().hashCode() ^ h;
436 }
437 hashCode = hash;
438 }
439 return hashCode;
440 }
441
442 public String toString() {
443 StringBuilder sb = new StringBuilder("{");
444 for (int i = 0; i < names.length; i++) {
445 if (i > 0)
446 sb.append(", ");
447 sb.append(names[i]).append("=");
448 Object v = values[i];
449 if (v != null && v.getClass().isArray()) {
450 String s = Arrays.deepToString(new Object[] { v });
451 s = s.substring(1, s.length() - 1); // remove [...]
452 v = s;
453 }
454 sb.append(String.valueOf(v));
455 }
456 return sb.append("}").toString();
457 }
458
459 /**
460 * Returns true if all of the fields have legal values given their
461 * names. This method always returns true, but a subclass can
462 * override it to return false when appropriate.
463 *
464 * @return true if the values are legal.
465 *
466 * @exception RuntimeOperationsException if the validity checking fails.
467 * The method returns false if the descriptor is not valid, but throws
468 * this exception if the attempt to determine validity fails.
469 */
470 public boolean isValid() {
471 return true;
472 }
473
474 /**
475 * <p>Returns a descriptor which is equal to this descriptor.
476 * Changes to the returned descriptor will have no effect on this
477 * descriptor, and vice versa.</p>
478 *
479 * <p>This method returns the object on which it is called.
480 * A subclass can override it
481 * to return another object provided the contract is respected.
482 *
483 * @exception RuntimeOperationsException for illegal value for field Names
484 * or field Values.
485 * If the descriptor construction fails for any reason, this exception will
486 * be thrown.
487 */
488 public Descriptor clone() {
489 return this ;
490 }
491
492 /**
493 * This operation is unsupported since this class is immutable. If
494 * this call would change a mutable descriptor with the same contents,
495 * then a {@link RuntimeOperationsException} wrapping an
496 * {@link UnsupportedOperationException} is thrown. Otherwise,
497 * the behavior is the same as it would be for a mutable descriptor:
498 * either an exception is thrown because of illegal parameters, or
499 * there is no effect.
500 */
501 public final void setFields(String[] fieldNames,
502 Object[] fieldValues) throws RuntimeOperationsException {
503 if (fieldNames == null || fieldValues == null)
504 illegal("Null argument");
505 if (fieldNames.length != fieldValues.length)
506 illegal("Different array sizes");
507 for (int i = 0; i < fieldNames.length; i++)
508 checkIllegalFieldName(fieldNames[i]);
509 for (int i = 0; i < fieldNames.length; i++)
510 setField(fieldNames[i], fieldValues[i]);
511 }
512
513 /**
514 * This operation is unsupported since this class is immutable. If
515 * this call would change a mutable descriptor with the same contents,
516 * then a {@link RuntimeOperationsException} wrapping an
517 * {@link UnsupportedOperationException} is thrown. Otherwise,
518 * the behavior is the same as it would be for a mutable descriptor:
519 * either an exception is thrown because of illegal parameters, or
520 * there is no effect.
521 */
522 public final void setField(String fieldName, Object fieldValue)
523 throws RuntimeOperationsException {
524 checkIllegalFieldName(fieldName);
525 int i = fieldIndex(fieldName);
526 if (i < 0)
527 unsupported();
528 Object value = values[i];
529 if ((value == null) ? (fieldValue != null) : !value
530 .equals(fieldValue))
531 unsupported();
532 }
533
534 /**
535 * Removes a field from the descriptor.
536 *
537 * @param fieldName String name of the field to be removed.
538 * If the field name is illegal or the field is not found,
539 * no exception is thrown.
540 *
541 * @exception RuntimeOperationsException if a field of the given name
542 * exists and the descriptor is immutable. The wrapped exception will
543 * be an {@link UnsupportedOperationException}.
544 */
545 public final void removeField(String fieldName) {
546 if (fieldName != null && fieldIndex(fieldName) >= 0)
547 unsupported();
548 }
549
550 static Descriptor nonNullDescriptor(Descriptor d) {
551 if (d == null)
552 return EMPTY_DESCRIPTOR;
553 else
554 return d;
555 }
556
557 private static void checkIllegalFieldName(String name) {
558 if (name == null || name.equals(""))
559 illegal("Null or empty field name");
560 }
561
562 private static void unsupported() {
563 UnsupportedOperationException uoe = new UnsupportedOperationException(
564 "Descriptor is read-only");
565 throw new RuntimeOperationsException(uoe);
566 }
567
568 private static void illegal(String message) {
569 IllegalArgumentException iae = new IllegalArgumentException(
570 message);
571 throw new RuntimeOperationsException(iae);
572 }
573 }
|