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