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