View Javadoc
1   package com.atlassian.plugin.spring.scanner.core;
2   
3   import com.google.common.base.Function;
4   import com.google.common.base.Joiner;
5   import com.google.common.collect.Lists;
6   import javassist.bytecode.AnnotationsAttribute;
7   import javassist.bytecode.ClassFile;
8   import javassist.bytecode.MethodInfo;
9   import javassist.bytecode.ParameterAnnotationsAttribute;
10  import javassist.bytecode.annotation.Annotation;
11  import javassist.bytecode.annotation.ArrayMemberValue;
12  import javassist.bytecode.annotation.ClassMemberValue;
13  import javassist.bytecode.annotation.MemberValue;
14  import javassist.bytecode.annotation.StringMemberValue;
15  
16  import javax.annotation.Nullable;
17  import java.util.Collections;
18  import java.util.HashSet;
19  import java.util.List;
20  import java.util.Set;
21  
22  import static com.google.common.collect.ImmutableList.copyOf;
23  import static com.google.common.collect.Iterables.transform;
24  
25  /**
26   * A helper file of Javassist related code.
27   *
28   * The Reflections code gives us some help and an invocation framework however it turns out it doesn't quite give us enough and we
29   * need to get down and dirty with Javassist.
30   */
31  public class JavassistHelper {
32      private static final Joiner ARRAY_MEMBER_VALUE_JOINER = Joiner.on(",");
33  
34      public List<String> getClassAnnotationNames(final ClassFile aClass) {
35          return getAnnotationNames((AnnotationsAttribute) aClass.getAttribute(AnnotationsAttribute.visibleTag));
36      }
37  
38      private List<String> getAnnotationNames(final AnnotationsAttribute... annotationsAttributes) {
39          final List<String> result = Lists.newArrayList();
40  
41          if (annotationsAttributes != null) {
42              for (final AnnotationsAttribute annotationsAttribute : annotationsAttributes) {
43                  if (annotationsAttribute != null) {
44                      for (final Annotation annotation : annotationsAttribute.getAnnotations()) {
45                          result.add(annotation.getTypeName());
46                      }
47                  }
48              }
49          }
50  
51          return result;
52      }
53  
54      /**
55       * Returns a named "member" of the annotation as a string value
56       *
57       * @param classFile      the class to inspect
58       * @param annotationType the name of the annotation type
59       * @param memberName     the member to retrieve
60       * @return a value or null if it has no member
61       */
62      Set<String> getAnnotationMemberSet(final ClassFile classFile, final String annotationType, final String memberName) {
63          final MemberValue value = getMemberValue(classFile, annotationType, memberName);
64          if (value != null && value instanceof ArrayMemberValue) {
65              final Set<String> values = new HashSet<String>();
66              final ArrayMemberValue arrayMemberValue = (ArrayMemberValue) value;
67              final MemberValue[] arrayMemberValueValues = arrayMemberValue.getValue();
68              for (final MemberValue memberValue : arrayMemberValueValues) {
69                  if (memberValue != null) {
70                      values.add(removeQuotes(memberValue.toString()));
71                  }
72              }
73              return values;
74          }
75          return Collections.emptySet();
76      }
77  
78      private MemberValue getMemberValue(final ClassFile classFile, final String annotationType, final String memberName) {
79          final AnnotationsAttribute annotations = (AnnotationsAttribute) classFile.getAttribute(AnnotationsAttribute.visibleTag);
80          final Annotation annotation = annotations.getAnnotation(annotationType);
81          return annotation == null ? null : annotation.getMemberValue(memberName);
82      }
83  
84      /*
85       * Javassist gives us the annotations wrapped in the original quotes "
86       */
87      private String removeQuotes(String s) {
88          if (s.startsWith("\"")) {
89              s = s.substring(1);
90          }
91          if (s.endsWith("\"")) {
92              s = s.substring(0, s.length() - 1);
93          }
94          return s;
95      }
96  
97      /**
98       * Returns a named "member" of the annotation as a string value
99       *
100      * @param classFile      the class to inspect
101      * @param annotationType the name of the annotation type
102      * @param memberName     the member to retrieve
103      * @return a value or null if it has no member
104      */
105     String getAnnotationMember(final ClassFile classFile, final String annotationType, final String memberName) {
106         final MemberValue value = getMemberValue(classFile, annotationType, memberName);
107         return dataForMemberValue(value);
108     }
109 
110     /**
111      * Return the member value of an annotation
112      *
113      * @param annotation the annotation to inspect
114      * @param memberName the name of the annotation member
115      * @return a value or null if it has no member
116      */
117     String getAnnotationMember(final Annotation annotation, final String memberName) {
118         final MemberValue value = annotation == null ? null : annotation.getMemberValue(memberName);
119         return dataForMemberValue(value);
120     }
121 
122     private String dataForMemberValue(final MemberValue memberValue) {
123         if (memberValue instanceof StringMemberValue) {
124             return ((StringMemberValue) memberValue).getValue();
125         } else if (memberValue instanceof ClassMemberValue) {
126             return ((ClassMemberValue) memberValue).getValue();
127         } else if (memberValue instanceof ArrayMemberValue) {
128             final MemberValue[] entryValues = ((ArrayMemberValue) memberValue).getValue();
129             final Iterable<String> entryData = transform(copyOf(entryValues), new Function<MemberValue, String>() {
130                 @Override
131                 public String apply(@Nullable final MemberValue entryValue) {
132                     return dataForMemberValue(entryValue);
133                 }
134             });
135             return ARRAY_MEMBER_VALUE_JOINER.join(entryData);
136 
137         } else {
138             // This branch is entered if memberValue is null, which happens when the annoation value is being defaulted.
139             // The non-null branch is preserving legacy behaviour, as far as i can see no non null values not handled by previous
140             // branches of the if can be generated by current code.
141             return memberValue == null ? null : removeQuotes(memberValue.toString());
142         }
143     }
144 
145     /**
146      * Returns the list of annotations a method parameter might have.
147      *
148      * @param method         the method to inspect
149      * @param parameterIndex the index of the method parameter to look at
150      * @return a list of parameter annotations it may have
151      */
152     List<Annotation> getParameterAnnotations(final MethodInfo method, final int parameterIndex) {
153         final List<Annotation> result = Lists.newArrayList();
154 
155         final ParameterAnnotationsAttribute parameterAnnotationsAttribute =
156                 (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
157         if (parameterAnnotationsAttribute != null) {
158             final Annotation[][] allAnnotations = parameterAnnotationsAttribute.getAnnotations();
159             if (parameterIndex < allAnnotations.length) {
160                 final Annotation[] annotations = allAnnotations[parameterIndex];
161                 if (annotations != null) {
162                     Collections.addAll(result, annotations);
163                 }
164             }
165         }
166         return result;
167     }
168 
169 
170 }