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.getArrayElementType().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      @Override
401      public int hashCode() {
402        return type.hashCode();
403      }
404    
405      @Override
406      public boolean equals(Object o) {
407        if (o instanceof RVMAnnotation) {
408          RVMAnnotation that = (RVMAnnotation)o;
409          if (type == that.type) {
410            if (elementValuePairs.length != that.elementValuePairs.length) {
411              return false;
412            }
413            for (int i=0; i<elementValuePairs.length; i++) {
414              if (!elementValuePairs[i].equals(that.elementValuePairs[i])) {
415                return false;
416              }
417            }
418            return true;
419          } else {
420            return false;
421          }
422        } else {
423          return false;
424        }
425      }
426    
427      /**
428       * Return a string representation of the annotation of the form
429       * "@type(name1=val1, ...nameN=valN)"
430       */
431      @Override
432      public String toString() {
433        RVMClass annotationInterface = type.resolve().asClass();
434        RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods();
435        String result = "@" + type.resolve().getClassForType().getName() + "(";
436        try {
437          for (int i=0; i < annotationMethods.length; i++) {
438            String name=annotationMethods[i].getName().toUnicodeString();
439            Object value=getElementValue(name, annotationMethods[i].getReturnType().resolve().getClassForType());
440            result += elementString(name, value);
441            if (i < (annotationMethods.length - 1)) {
442              result += ", ";
443            }
444          }
445        } catch (java.io.UTFDataFormatException e) {
446          throw new Error(e);
447        }
448        result += ")";
449        return result;
450      }
451    
452      /**
453       * String representation of the value pair of the form
454       * "name=value"
455       */
456      private String elementString(String name, Object value) {
457        return name + "=" + toStringHelper(value);
458      }
459      private static String toStringHelper(Object value) {
460        if (value instanceof Object[]) {
461          StringBuilder result = new StringBuilder("[");
462          Object[] a = (Object[]) value;
463          for (int i = 0; i < a.length; i++) {
464            result.append(toStringHelper(a[i]));
465            if (i < (a.length - 1)) {
466              result.append(", ");
467            }
468          }
469          result.append("]");
470          return result.toString();
471        } else {
472          return value.toString();
473        }
474      }
475    
476      /** Find the value for an annotation */
477      private Object getElementValue(String name, Class<?> valueType) {
478        for (AnnotationMember evp : elementValuePairs) {
479          String evpFieldName = evp.getName().toString();
480          if (name.equals(evpFieldName)) {
481            return evp.getValue();
482          }
483        }
484        MethodReference methRef = MemberReference.findOrCreate(
485            type,
486            Atom.findOrCreateAsciiAtom(name),
487            Atom.findOrCreateAsciiAtom("()" + TypeReference.findOrCreate(valueType).getName())
488        ).asMethodReference();
489        try {
490          return methRef.resolve().getAnnotationDefault();
491        } catch (Throwable t) {
492          return NO_VALUE;
493        }
494      }
495    
496      /** Hash code for annotation value */
497      private int annotationHashCode() {
498        RVMClass annotationInterface = type.resolve().asClass();
499        RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods();
500        String typeString = type.toString();
501        int result = typeString.substring(1, typeString.length() - 1).hashCode();
502        try {
503          for (RVMMethod method : annotationMethods) {
504            String name = method.getName().toUnicodeString();
505            Object value = getElementValue(name, method.getReturnType().resolve().getClassForType());
506            int part_result = name.hashCode() * 127;
507            if (value.getClass().isArray()) {
508              if (value instanceof Object[]) {
509                part_result ^= Arrays.hashCode((Object[]) value);
510              } else if (value instanceof boolean[]) {
511                part_result ^= Arrays.hashCode((boolean[]) value);
512              } else if (value instanceof byte[]) {
513                part_result ^= Arrays.hashCode((byte[]) value);
514              } else if (value instanceof char[]) {
515                  part_result ^= Arrays.hashCode((char[]) value);
516              } else if (value instanceof short[]) {
517                part_result ^= Arrays.hashCode((short[]) value);
518              } else if (value instanceof int[]) {
519                part_result ^= Arrays.hashCode((int[]) value);
520              } else if (value instanceof long[]) {
521                part_result ^= Arrays.hashCode((long[]) value);
522              } else if (value instanceof float[]) {
523                part_result ^= Arrays.hashCode((float[]) value);
524              } else if (value instanceof double[]) {
525                part_result ^= Arrays.hashCode((double[]) value);
526              }
527            } else {
528              part_result ^= value.hashCode();
529            }
530            result += part_result;
531          }
532        } catch (java.io.UTFDataFormatException e) {
533          throw new Error(e);
534        }
535        return result;
536      }
537    
538      /** Are two annotations equal? */
539      private boolean annotationEquals(Annotation a, Annotation b) {
540        if (a == b) {
541          return true;
542        } else if (a.getClass() != b.getClass()) {
543          return false;
544        } else {
545          RVMClass annotationInterface = type.resolve().asClass();
546          RVMMethod[] annotationMethods = annotationInterface.getDeclaredMethods();
547          AnnotationFactory afB = (AnnotationFactory)Proxy.getInvocationHandler(b);
548          try {
549            for (RVMMethod method : annotationMethods) {
550              String name = method.getName().toUnicodeString();
551              Object objA = getElementValue(name, method.getReturnType().resolve().getClassForType());
552              Object objB = afB.getValue(name, method.getReturnType().resolve().getClassForType());
553              if (!objA.getClass().isArray()) {
554                if (!objA.equals(objB)) {
555                  return false;
556                }
557              } else {
558                if(!Arrays.equals((Object[]) objA, (Object[]) objB)) {
559                  return false;
560                }
561              }
562            }
563          } catch (java.io.UTFDataFormatException e) {
564            throw new Error(e);
565          }
566          return true;
567        }
568      }
569    
570      /**
571       * Class used to implement annotations as proxies
572       */
573      private final class AnnotationFactory implements InvocationHandler {
574        /** Cache of hash code */
575        private int cachedHashCode;
576    
577        AnnotationFactory() {
578        }
579    
580        /** Entry point to factory */
581        @Override
582        public Object invoke(Object proxy, Method method, Object[] args) {
583          if (method.getName().equals("annotationType")) {
584            return type.resolve().getClassForType();
585          }
586          if (method.getName().equals("hashCode")) {
587            if (cachedHashCode == 0) {
588              cachedHashCode = annotationHashCode();
589            }
590            return cachedHashCode;
591          }
592          if (method.getName().equals("equals")) {
593            return annotationEquals((Annotation)proxy, (Annotation)args[0]);
594          }
595          if (method.getName().equals("toString")) {
596            return RVMAnnotation.this.toString();
597          }
598          Object value = getValue(method.getName(), method.getReturnType());
599          if (value != NO_VALUE) {
600            return value;
601          }
602          throw new IllegalArgumentException("Invalid method for annotation type: " + method);
603        }
604    
605        private Object getValue(String name, Class<?> valueType) {
606          return RVMAnnotation.this.getElementValue(name, valueType);
607        }
608    
609      }
610      /**
611       * A class to decode and hold the name and its associated value for
612       * an annotation member
613       */
614      private static final class AnnotationMember implements Comparable<AnnotationMember> {
615        /**
616         * Name of element
617         */
618        private final MethodReference meth;
619        /**
620         * Elements value, decoded from its tag
621         */
622        private Object value;
623        /**
624         * Is this not the first use of the member?
625         */
626        private boolean notFirstUse = false;
627        /**
628         * Construct a read value pair
629         */
630        private AnnotationMember(MethodReference meth, Object value) {
631          this.meth = meth;
632          this.value = value;
633        }
634    
635        /**
636         * Read the pair from the input stream and create object
637         * @param constantPool the constant pool for the class being read
638         * @param input stream to read from
639         * @param classLoader the class loader being used to load this annotation
640         * @return a newly created annotation member
641         */
642        static AnnotationMember readAnnotationMember(TypeReference type, int[] constantPool, DataInputStream input, ClassLoader classLoader)
643            throws IOException, ClassNotFoundException {
644          // Read name of pair
645          int elemNameIndex = input.readUnsignedShort();
646          Atom name = ClassFileReader.getUtf(constantPool, elemNameIndex);
647          MethodReference meth;
648          Object value;
649          if (type.isResolved()) {
650            meth = type.resolve().asClass().findDeclaredMethod(name).getMemberRef().asMethodReference();
651            value = RVMAnnotation.readValue(meth.getReturnType(), constantPool, input, classLoader);
652          } else {
653            value = RVMAnnotation.readValue(null, constantPool, input, classLoader);
654            if (value instanceof Object[] && ((Object[])value).length == 0) {
655                // We blindly guessed Object[] in readValue.
656                // No choice but to force type to be resolved so we actually
657                // create an empty array of the appropriate type.
658                meth = type.resolve().asClass().findDeclaredMethod(name).getMemberRef().asMethodReference();
659                value = Array.newInstance(meth.getReturnType().getArrayElementType().resolve().getClassForType(), 0);
660            } else {
661                // Reading the value lets us make a MemberReference that is likely to be correct.
662                meth = MemberReference.findOrCreate(type, name,
663                        Atom.findOrCreateAsciiAtom("()"+TypeReference.findOrCreate(value.getClass()).getName())
664                ).asMethodReference();
665            }
666          }
667          return new AnnotationMember(meth, value);
668        }
669    
670        /** @return the name of the of the given pair */
671        Atom getName() {
672          return meth.getName();
673        }
674        /** @return the value of the of the given pair */
675        @Pure
676        Object getValue() {
677          if (!notFirstUse) {
678            synchronized(this) {
679              value = firstUse(value);
680              notFirstUse = true;
681            }
682          }
683          return value;
684        }
685    
686        /**
687         * Are two members equivalent?
688         */
689        @Override
690        public boolean equals(Object o) {
691          if (o instanceof AnnotationMember) {
692            AnnotationMember that = (AnnotationMember)o;
693            return that.meth == meth && that.value.equals(value);
694          } else {
695            return false;
696          }
697        }
698    
699        /**
700         * Compute hashCode from meth
701         */
702        @Override
703        public int hashCode() {
704          return meth.hashCode();
705        }
706    
707        /**
708         * Ordering for sorted annotation members
709         */
710        @Override
711        public int compareTo(AnnotationMember am) {
712          if (am.meth != this.meth) {
713            return am.getName().toString().compareTo(this.getName().toString());
714          } else {
715            if (value.getClass().isArray()) {
716              return Arrays.hashCode((Object[]) value) - Arrays.hashCode((Object[]) am.value);
717            } else {
718              @SuppressWarnings("unchecked") // True generic programming, we can't type check it in Java
719              Comparable<Object> cValue = (Comparable) value;
720              return cValue.compareTo(am.value);
721            }
722          }
723        }
724      }
725    }