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}