1 /* 2 * Copyright (c) 2004, 2013, 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 }