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