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    }