001/* 002 * This file is part of the Jikes RVM project (http://jikesrvm.org). 003 * 004 * This file is licensed to You under the Eclipse Public License (EPL); 005 * You may not use this file except in compliance with the License. You 006 * may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/eclipse-1.0.php 009 * 010 * See the COPYRIGHT.txt file distributed with this work for information 011 * regarding copyright ownership. 012 */ 013package org.jikesrvm.classloader; 014 015import java.io.DataInputStream; 016import java.io.IOException; 017import java.lang.annotation.Annotation; 018import java.lang.reflect.Array; 019import java.lang.reflect.Method; 020import java.lang.reflect.Proxy; 021import java.lang.reflect.InvocationHandler; 022import java.util.Arrays; 023import org.jikesrvm.VM; 024import org.jikesrvm.runtime.Statics; 025import org.jikesrvm.util.ImmutableEntryHashMapRVM; 026import org.vmmagic.pragma.Uninterruptible; 027import org.vmmagic.pragma.Pure; 028import org.vmmagic.unboxed.Offset; 029 030/** 031 * Internal representation of an annotation. We use a proxy class to implement 032 * actual annotations {@link RVMClass}. 033 */ 034public final class RVMAnnotation { 035 /** 036 * The type of the annotation. This is an interface name that the 037 * annotation value will implement 038 */ 039 private final TypeReference type; 040 /** 041 * Members of this annotation 042 */ 043 private final AnnotationMember[] elementValuePairs; 044 /** 045 * Remembered unique annotations 046 */ 047 private static final ImmutableEntryHashMapRVM<RVMAnnotation, RVMAnnotation> 048 uniqueMap = new ImmutableEntryHashMapRVM<RVMAnnotation, RVMAnnotation>(); 049 050 /** 051 * The concrete annotation represented by this RVMAnnotation 052 */ 053 private Annotation value; 054 055 /** Encoding of when a result cannot be returned */ 056 private static final Object NO_VALUE = new Object(); 057 058 /** 059 * Construct a read annotation 060 * @param type the name of the type this annotation's value will 061 * implement 062 * @param elementValuePairs values for the fields in the annotation 063 * that override the defaults 064 */ 065 private RVMAnnotation(TypeReference type, AnnotationMember[] elementValuePairs) { 066 this.type = type; 067 this.elementValuePairs = elementValuePairs; 068 } 069 070 static RVMAnnotation readAnnotation(int[] constantPool, DataInputStream input, ClassLoader classLoader) 071 throws IOException, ClassNotFoundException { 072 TypeReference type; 073 // Read type 074 int typeIndex = input.readUnsignedShort(); 075 type = TypeReference.findOrCreate(classLoader, ClassFileReader.getUtf(constantPool, typeIndex)); 076 // Read values 077 int numAnnotationMembers = input.readUnsignedShort(); 078 AnnotationMember[] elementValuePairs = new AnnotationMember[numAnnotationMembers]; 079 for (int i = 0; i < numAnnotationMembers; i++) { 080 elementValuePairs[i] = AnnotationMember.readAnnotationMember(type, constantPool, input, classLoader); 081 } 082 // Arrays.sort(elementValuePairs); 083 RVMAnnotation result = new RVMAnnotation(type, elementValuePairs); 084 RVMAnnotation unique = uniqueMap.get(result); 085 if (unique != null) { 086 return unique; 087 } else { 088 uniqueMap.put(result, result); 089 return result; 090 } 091 } 092 093 /** 094 * Return the annotation represented by this RVMAnnotation. If this 095 * is the first time this annotation has been accessed the subclass 096 * of annotation this class represents needs creating. 097 * @return the annotation represented 098 */ 099 Annotation getValue() { 100 if (value == null) { 101 value = createValue(); 102 } 103 return value; 104 } 105 106 /** 107 * Create an instance of this type of annotation with the values 108 * given in the members 109 * 110 * @return the created annotation 111 */ 112 private Annotation createValue() { 113 // Find the annotation then find its implementing class 114 final RVMClass annotationInterface = type.resolve().asClass(); 115 annotationInterface.resolve(); 116 Class<?> interfaceClass = annotationInterface.getClassForType(); 117 ClassLoader classLoader = interfaceClass.getClassLoader(); 118 if (classLoader == null) { 119 classLoader = BootstrapClassLoader.getBootstrapClassLoader(); 120 } 121 return (Annotation) Proxy.newProxyInstance(classLoader, new Class[] { interfaceClass }, 122 new AnnotationFactory()); 123 } 124 125 static <T> Object readValue(TypeReference type, int[] constantPool, DataInputStream input, ClassLoader classLoader) 126 throws IOException, ClassNotFoundException { 127 // Read element value's tag 128 byte elementValue_tag = input.readByte(); 129 return readValue(type, constantPool, input, classLoader, elementValue_tag); 130 } 131 private static <T> Object readValue(TypeReference type, int[] constantPool, DataInputStream input, ClassLoader classLoader, byte elementValue_tag) 132 throws IOException, ClassNotFoundException { 133 // decode 134 Object value; 135 switch (elementValue_tag) { 136 case'B': { 137 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Byte); 138 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 139 value = (byte) Statics.getSlotContentsAsInt(offset); 140 break; 141 } 142 case'C': { 143 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Char); 144 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 145 value = (char) Statics.getSlotContentsAsInt(offset); 146 break; 147 } 148 case'D': { 149 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Double); 150 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 151 long longValue = Statics.getSlotContentsAsLong(offset); 152 value = Double.longBitsToDouble(longValue); 153 break; 154 } 155 case'F': { 156 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Float); 157 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 158 int intValue = Statics.getSlotContentsAsInt(offset); 159 value = Float.intBitsToFloat(intValue); 160 break; 161 } 162 case'I': { 163 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Int); 164 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 165 value = Statics.getSlotContentsAsInt(offset); 166 break; 167 } 168 case'J': { 169 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Long); 170 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 171 value = Statics.getSlotContentsAsLong(offset); 172 break; 173 } 174 case'S': { 175 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Short); 176 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 177 value = (short) Statics.getSlotContentsAsInt(offset); 178 break; 179 } 180 case'Z': { 181 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.Boolean); 182 Offset offset = ClassFileReader.getLiteralOffset(constantPool, input.readUnsignedShort()); 183 value = Statics.getSlotContentsAsInt(offset) == 1; 184 break; 185 } 186 case's': { 187 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.JavaLangString); 188 value = ClassFileReader.getUtf(constantPool, input.readUnsignedShort()).toString(); 189 break; 190 } 191 case'e': { 192 int typeNameIndex = input.readUnsignedShort(); 193 @SuppressWarnings("unchecked") Class enumType = 194 TypeReference.findOrCreate(classLoader, 195 ClassFileReader.getUtf(constantPool, typeNameIndex)).resolve().getClassForType(); 196 int constNameIndex = input.readUnsignedShort(); 197 198 //noinspection unchecked 199 value = Enum.valueOf(enumType, ClassFileReader.getUtf(constantPool, constNameIndex).toString()); 200 break; 201 } 202 case'c': { 203 if (VM.VerifyAssertions) VM._assert(type == null || type == TypeReference.JavaLangClass); 204 int classInfoIndex = input.readUnsignedShort(); 205 // Value should be a class but resolving the class at this point could cause infinite recursion in class loading 206 TypeReference unresolvedValue = TypeReference.findOrCreate(classLoader, ClassFileReader.getUtf(constantPool, classInfoIndex)); 207 if (unresolvedValue.peekType() != null) { 208 value = unresolvedValue.peekType().getClassForType(); 209 } else { 210 value = unresolvedValue; 211 } 212 break; 213 } 214 case'@': 215 value = RVMAnnotation.readAnnotation(constantPool, input, classLoader); 216 break; 217 case'[': { 218 int numValues = input.readUnsignedShort(); 219 if (numValues == 0) { 220 if (type != null) { 221 value = Array.newInstance(type.getArrayElementType().resolve().getClassForType(), 0); 222 } else { 223 value = new Object[0]; 224 } 225 } else { 226 byte innerElementValue_tag = input.readByte(); 227 TypeReference innerType = type == null ? null : type.getArrayElementType(); 228 switch(innerElementValue_tag) { 229 case 'B': { 230 byte[] array = new byte[numValues]; 231 array[0] = (Byte)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 232 for (int i = 1; i < numValues; i++) { 233 array[i] = (Byte)readValue(innerType, constantPool, input, classLoader); 234 } 235 value = array; 236 break; 237 } 238 case 'C': { 239 char[] array = new char[numValues]; 240 array[0] = (Character)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 241 for (int i = 1; i < numValues; i++) { 242 array[i] = (Character)readValue(innerType, constantPool, input, classLoader); 243 } 244 value = array; 245 break; 246 } 247 case 'D': { 248 double[] array = new double[numValues]; 249 array[0] = (Double)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 250 for (int i = 1; i < numValues; i++) { 251 array[i] = (Double)readValue(innerType, constantPool, input, classLoader); 252 } 253 value = array; 254 break; 255 } 256 case 'F': { 257 float[] array = new float[numValues]; 258 array[0] = (Float)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 259 for (int i = 1; i < numValues; i++) { 260 array[i] = (Float)readValue(innerType, constantPool, input, classLoader); 261 } 262 value = array; 263 break; 264 } 265 case 'I': { 266 int[] array = new int[numValues]; 267 array[0] = (Integer)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 268 for (int i = 1; i < numValues; i++) { 269 array[i] = (Integer)readValue(innerType, constantPool, input, classLoader); 270 } 271 value = array; 272 break; 273 } 274 case 'J': { 275 long[] array = new long[numValues]; 276 array[0] = (Long)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 277 for (int i = 1; i < numValues; i++) { 278 array[i] = (Long)readValue(innerType, constantPool, input, classLoader); 279 } 280 value = array; 281 break; 282 } 283 case 'S': { 284 short[] array = new short[numValues]; 285 array[0] = (Short)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 286 for (int i = 1; i < numValues; i++) { 287 array[i] = (Short)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 288 } 289 value = array; 290 break; 291 } 292 case 'Z': { 293 boolean[] array = new boolean[numValues]; 294 array[0] = (Boolean)readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 295 for (int i = 1; i < numValues; i++) { 296 array[i] = (Boolean)readValue(innerType, constantPool, input, classLoader); 297 } 298 value = array; 299 break; 300 } 301 case 's': 302 case '@': 303 case 'e': 304 case '[': { 305 Object value1 = readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 306 value = Array.newInstance(value1.getClass(), numValues); 307 Array.set(value, 0, value1); 308 for (int i = 1; i < numValues; i++) { 309 Array.set(value, i, readValue(innerType, constantPool, input, classLoader)); 310 } 311 break; 312 } 313 case 'c': { 314 Object value1 = readValue(innerType, constantPool, input, classLoader, innerElementValue_tag); 315 Object[] values = new Object[numValues]; 316 values[0] = value1; 317 boolean allClasses = value1 instanceof Class; 318 for (int i = 1; i < numValues; i++) { 319 values[i] = readValue(innerType, constantPool, input, classLoader); 320 if (allClasses && !(values[i] instanceof Class)) { 321 allClasses = false; 322 } 323 } 324 if (allClasses == true) { 325 Class<?>[] newValues = new Class[numValues]; 326 for (int i = 0; i < numValues; i++) { 327 newValues[i] = (Class<?>)values[i]; 328 } 329 value = newValues; 330 } else { 331 value = values; 332 } 333 break; 334 } 335 default: 336 throw new ClassFormatError("Unknown element_value tag '" + (char) innerElementValue_tag + "'"); 337 } 338 } 339 break; 340 } 341 default: 342 throw new ClassFormatError("Unknown element_value tag '" + (char) elementValue_tag + "'"); 343 } 344 return value; 345 } 346 347 static Object firstUse(Object value) { 348 if (value instanceof TypeReference) { 349 return ((TypeReference)value).resolve().getClassForType(); 350 } else if (value instanceof Object[]) { 351 Object[] values = (Object[])value; 352 boolean typeChanged = false; 353 for (int i = 0; i < values.length; i++) { 354 Object newVal = firstUse(values[i]); 355 if (newVal.getClass() != values[i].getClass()) { 356 typeChanged = true; 357 } 358 values[i] = newVal; 359 } 360 if (typeChanged) { 361 Object[] newValues = (Object[])Array.newInstance(values[0].getClass(), values.length); 362 for (int i = 0; i < values.length; i++) { 363 newValues[i] = values[i]; 364 } 365 return newValues; 366 } else { 367 return values; 368 } 369 } 370 return value; 371 } 372 373 /** 374 * Return the TypeReference of the declared annotation, ie an 375 * interface and not the class object of this instance 376 * 377 * @return TypeReferernce of interface annotation object implements 378 */ 379 @Uninterruptible 380 TypeReference annotationType() { 381 return type; 382 } 383 384 /* 385 * Hash map support 386 */ 387 @Override 388 public int hashCode() { 389 return type.hashCode(); 390 } 391 392 @Override 393 public boolean equals(Object o) { 394 if (o instanceof RVMAnnotation) { 395 RVMAnnotation that = (RVMAnnotation)o; 396 if (type == that.type) { 397 if (elementValuePairs.length != that.elementValuePairs.length) { 398 return false; 399 } 400 for (int i = 0; i < elementValuePairs.length; i++) { 401 if (!elementValuePairs[i].equals(that.elementValuePairs[i])) { 402 return false; 403 } 404 } 405 return true; 406 } else { 407 return false; 408 } 409 } else { 410 return false; 411 } 412 } 413 414 /** 415 * @return a string representation of the annotation of the form 416 * "@type(name1=val1, ...nameN=valN)" 417 */ 418 @Override 419 public String toString() { 420 RVMClass annotationInterface = type.resolve().asClass(); 421 RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods(); 422 StringBuilder result = new StringBuilder("@"); 423 result.append(type.resolve().getClassForType().getName()); 424 result.append('('); 425 try { 426 for (int i = 0; i < annotationMethods.length; i++) { 427 String name = annotationMethods[i].getName().toUnicodeString(); 428 Object value = getElementValue(name, 429 annotationMethods[i].getReturnType().resolve().getClassForType()); 430 result.append(elementString(name, value)); 431 if (i < (annotationMethods.length - 1)) { 432 result.append(", "); 433 } 434 } 435 } catch (java.io.UTFDataFormatException e) { 436 throw new Error(e); 437 } 438 result.append(')'); 439 return result.toString(); 440 } 441 442 /** 443 * @param name the name 444 * @param value the value 445 * @return string representation of the value pair of the form 446 * "name=value" 447 */ 448 private String elementString(String name, Object value) { 449 return name + "=" + toStringHelper(value); 450 } 451 private static String toStringHelper(Object value) { 452 if (value instanceof Object[]) { 453 StringBuilder result = new StringBuilder("["); 454 Object[] a = (Object[]) value; 455 for (int i = 0; i < a.length; i++) { 456 result.append(toStringHelper(a[i])); 457 if (i < (a.length - 1)) { 458 result.append(", "); 459 } 460 } 461 result.append("]"); 462 return result.toString(); 463 } else { 464 return value.toString(); 465 } 466 } 467 468 private Object getElementValue(String name, Class<?> valueType) { 469 for (AnnotationMember evp : elementValuePairs) { 470 String evpFieldName = evp.getName().toString(); 471 if (name.equals(evpFieldName)) { 472 return evp.getValue(); 473 } 474 } 475 MethodReference methRef = MemberReference.findOrCreate( 476 type, 477 Atom.findOrCreateAsciiAtom(name), 478 Atom.findOrCreateAsciiAtom("()" + TypeReference.findOrCreate(valueType).getName()) 479 ).asMethodReference(); 480 try { 481 return methRef.resolve().getAnnotationDefault(); 482 } catch (Throwable t) { 483 return NO_VALUE; 484 } 485 } 486 487 private int annotationHashCode() { 488 RVMClass annotationInterface = type.resolve().asClass(); 489 RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods(); 490 String typeString = type.toString(); 491 int result = typeString.substring(1, typeString.length() - 1).hashCode(); 492 try { 493 for (RVMMethod method : annotationMethods) { 494 String name = method.getName().toUnicodeString(); 495 Object value = getElementValue(name, method.getReturnType().resolve().getClassForType()); 496 int part_result = name.hashCode() * 127; 497 if (value.getClass().isArray()) { 498 if (value instanceof Object[]) { 499 part_result ^= Arrays.hashCode((Object[]) value); 500 } else if (value instanceof boolean[]) { 501 part_result ^= Arrays.hashCode((boolean[]) value); 502 } else if (value instanceof byte[]) { 503 part_result ^= Arrays.hashCode((byte[]) value); 504 } else if (value instanceof char[]) { 505 part_result ^= Arrays.hashCode((char[]) value); 506 } else if (value instanceof short[]) { 507 part_result ^= Arrays.hashCode((short[]) value); 508 } else if (value instanceof int[]) { 509 part_result ^= Arrays.hashCode((int[]) value); 510 } else if (value instanceof long[]) { 511 part_result ^= Arrays.hashCode((long[]) value); 512 } else if (value instanceof float[]) { 513 part_result ^= Arrays.hashCode((float[]) value); 514 } else if (value instanceof double[]) { 515 part_result ^= Arrays.hashCode((double[]) value); 516 } 517 } else { 518 part_result ^= value.hashCode(); 519 } 520 result += part_result; 521 } 522 } catch (java.io.UTFDataFormatException e) { 523 throw new Error(e); 524 } 525 return result; 526 } 527 528 private boolean annotationEquals(Annotation a, Annotation b) { 529 if (a == b) { 530 return true; 531 } else if (a.getClass() != b.getClass()) { 532 return false; 533 } else { 534 RVMClass annotationInterface = type.resolve().asClass(); 535 RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods(); 536 AnnotationFactory afB = (AnnotationFactory)Proxy.getInvocationHandler(b); 537 try { 538 for (RVMMethod method : annotationMethods) { 539 String name = method.getName().toUnicodeString(); 540 Object objA = getElementValue(name, method.getReturnType().resolve().getClassForType()); 541 Object objB = afB.getValue(name, method.getReturnType().resolve().getClassForType()); 542 if (!objA.getClass().isArray()) { 543 if (!objA.equals(objB)) { 544 return false; 545 } 546 } else { 547 if (!Arrays.equals((Object[]) objA, (Object[]) objB)) { 548 return false; 549 } 550 } 551 } 552 } catch (java.io.UTFDataFormatException e) { 553 throw new Error(e); 554 } 555 return true; 556 } 557 } 558 559 /** 560 * Class used to implement annotations as proxies 561 */ 562 private final class AnnotationFactory implements InvocationHandler { 563 /** Cache of hash code */ 564 private int cachedHashCode; 565 566 AnnotationFactory() { 567 } 568 569 @Override 570 public Object invoke(Object proxy, Method method, Object[] args) { 571 if (method.getName().equals("annotationType")) { 572 return type.resolve().getClassForType(); 573 } 574 if (method.getName().equals("hashCode")) { 575 if (cachedHashCode == 0) { 576 cachedHashCode = annotationHashCode(); 577 } 578 return cachedHashCode; 579 } 580 if (method.getName().equals("equals")) { 581 return annotationEquals((Annotation)proxy, (Annotation)args[0]); 582 } 583 if (method.getName().equals("toString")) { 584 return RVMAnnotation.this.toString(); 585 } 586 Object value = getValue(method.getName(), method.getReturnType()); 587 if (value != NO_VALUE) { 588 return value; 589 } 590 throw new IllegalArgumentException("Invalid method for annotation type: " + method); 591 } 592 593 private Object getValue(String name, Class<?> valueType) { 594 return RVMAnnotation.this.getElementValue(name, valueType); 595 } 596 597 } 598 /** 599 * A class to decode and hold the name and its associated value for 600 * an annotation member 601 */ 602 private static final class AnnotationMember implements Comparable<AnnotationMember> { 603 /** 604 * Name of element 605 */ 606 private final MethodReference meth; 607 /** 608 * Elements value, decoded from its tag 609 */ 610 private Object value; 611 /** 612 * Is this not the first use of the member? 613 */ 614 private boolean notFirstUse = false; 615 616 private AnnotationMember(MethodReference meth, Object value) { 617 this.meth = meth; 618 this.value = value; 619 } 620 621 static AnnotationMember readAnnotationMember(TypeReference type, int[] constantPool, DataInputStream input, ClassLoader classLoader) 622 throws IOException, ClassNotFoundException { 623 // Read name of pair 624 int elemNameIndex = input.readUnsignedShort(); 625 Atom name = ClassFileReader.getUtf(constantPool, elemNameIndex); 626 MethodReference meth; 627 Object value; 628 if (type.isResolved()) { 629 meth = type.resolve().asClass().findDeclaredMethod(name).getMemberRef().asMethodReference(); 630 value = RVMAnnotation.readValue(meth.getReturnType(), constantPool, input, classLoader); 631 } else { 632 value = RVMAnnotation.readValue(null, constantPool, input, classLoader); 633 if (value instanceof Object[] && ((Object[])value).length == 0) { 634 // We blindly guessed Object[] in readValue. 635 // No choice but to force type to be resolved so we actually 636 // create an empty array of the appropriate type. 637 meth = type.resolve().asClass().findDeclaredMethod(name).getMemberRef().asMethodReference(); 638 value = Array.newInstance(meth.getReturnType().getArrayElementType().resolve().getClassForType(), 0); 639 } else { 640 // Reading the value lets us make a MemberReference that is likely to be correct. 641 meth = MemberReference.findOrCreate(type, name, 642 Atom.findOrCreateAsciiAtom("()" + TypeReference.findOrCreate(value.getClass()).getName()) 643 ).asMethodReference(); 644 } 645 } 646 return new AnnotationMember(meth, value); 647 } 648 649 /** @return the name of the of the given pair */ 650 Atom getName() { 651 return meth.getName(); 652 } 653 /** @return the value of the of the given pair */ 654 @Pure 655 Object getValue() { 656 if (!notFirstUse) { 657 synchronized (this) { 658 value = firstUse(value); 659 notFirstUse = true; 660 } 661 } 662 return value; 663 } 664 665 @Override 666 public boolean equals(Object o) { 667 if (o instanceof AnnotationMember) { 668 AnnotationMember that = (AnnotationMember)o; 669 return that.meth == meth && that.value.equals(value); 670 } else { 671 return false; 672 } 673 } 674 675 /** 676 * Compute hashCode from meth 677 */ 678 @Override 679 public int hashCode() { 680 return meth.hashCode(); 681 } 682 683 @Override 684 public int compareTo(AnnotationMember am) { 685 if (am.meth != this.meth) { 686 return am.getName().toString().compareTo(this.getName().toString()); 687 } else { 688 if (value.getClass().isArray()) { 689 return Arrays.hashCode((Object[]) value) - Arrays.hashCode((Object[]) am.value); 690 } else { 691 @SuppressWarnings("unchecked") // True generic programming, we can't type check it in Java 692 Comparable<Object> cValue = (Comparable) value; 693 return cValue.compareTo(am.value); 694 } 695 } 696 } 697 } 698}