View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.injection;
6   
7   import static java.lang.Character.toUpperCase;
8   
9   import static mockit.internal.reflection.AnnotationReflection.readAnnotationAttribute;
10  import static mockit.internal.reflection.AnnotationReflection.readAnnotationAttributeIfAvailable;
11  import static mockit.internal.reflection.MethodReflection.invokePublicIfAvailable;
12  import static mockit.internal.reflection.ParameterReflection.NO_PARAMETERS;
13  import static mockit.internal.util.ClassLoad.searchTypeInClasspath;
14  
15  import edu.umd.cs.findbugs.annotations.NonNull;
16  import edu.umd.cs.findbugs.annotations.Nullable;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.AccessibleObject;
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.GenericArrayType;
22  import java.lang.reflect.ParameterizedType;
23  import java.lang.reflect.Type;
24  import java.lang.reflect.TypeVariable;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import javax.annotation.Resource;
29  import javax.ejb.EJB;
30  import javax.enterprise.inject.Instance;
31  import javax.enterprise.util.TypeLiteral;
32  import javax.inject.Inject;
33  import javax.inject.Provider;
34  import javax.persistence.PersistenceContext;
35  import javax.persistence.PersistenceUnit;
36  import javax.servlet.Servlet;
37  
38  public final class InjectionPoint {
39      public enum KindOfInjectionPoint {
40          NotAnnotated, Required, Optional
41      }
42  
43      @Nullable
44      public static final Class<? extends Annotation> INJECT_CLASS;
45      @Nullable
46      private static final Class<? extends Annotation> INSTANCE_CLASS;
47      @Nullable
48      private static final Class<? extends Annotation> EJB_CLASS;
49      @Nullable
50      public static final Class<? extends Annotation> PERSISTENCE_UNIT_CLASS;
51      @Nullable
52      public static final Class<?> SERVLET_CLASS;
53      @Nullable
54      public static final Class<?> CONVERSATION_CLASS;
55  
56      static {
57          INJECT_CLASS = searchTypeInClasspath("javax.inject.Inject");
58          INSTANCE_CLASS = searchTypeInClasspath("javax.enterprise.inject.Instance");
59          EJB_CLASS = searchTypeInClasspath("javax.ejb.EJB");
60          SERVLET_CLASS = searchTypeInClasspath("javax.servlet.Servlet");
61          CONVERSATION_CLASS = searchTypeInClasspath("javax.enterprise.context.Conversation");
62  
63          Class<? extends Annotation> entity = searchTypeInClasspath("javax.persistence.Entity");
64  
65          if (entity == null) {
66              PERSISTENCE_UNIT_CLASS = null;
67          } else {
68              PERSISTENCE_UNIT_CLASS = searchTypeInClasspath("javax.persistence.PersistenceUnit");
69          }
70      }
71  
72      @NonNull
73      public final Type type;
74      @Nullable
75      public final String name;
76      @Nullable
77      private final String normalizedName;
78      public final boolean qualified;
79  
80      public InjectionPoint(@NonNull Type type) {
81          this(type, null, false);
82      }
83  
84      public InjectionPoint(@NonNull Type type, @Nullable String name) {
85          this(type, name, false);
86      }
87  
88      public InjectionPoint(@NonNull Type type, @Nullable String name, boolean qualified) {
89          this.type = type;
90          this.name = name;
91          normalizedName = name == null ? null : convertToLegalJavaIdentifierIfNeeded(name);
92          this.qualified = qualified;
93      }
94  
95      public InjectionPoint(@NonNull Type type, @NonNull String name, @Nullable String qualifiedName) {
96          this.type = type;
97          this.name = qualifiedName == null ? name : qualifiedName;
98          normalizedName = this.name;
99          qualified = qualifiedName != null;
100     }
101 
102     @NonNull
103     public static String convertToLegalJavaIdentifierIfNeeded(@NonNull String name) {
104         if (name.indexOf('-') < 0 && name.indexOf('.') < 0) {
105             return name;
106         }
107 
108         StringBuilder identifier = new StringBuilder(name);
109 
110         for (int i = name.length() - 1; i >= 0; i--) {
111             char c = identifier.charAt(i);
112 
113             if (c == '-' || c == '.') {
114                 identifier.deleteCharAt(i);
115                 char d = identifier.charAt(i);
116                 identifier.setCharAt(i, toUpperCase(d));
117             }
118         }
119 
120         return identifier.toString();
121     }
122 
123     @Override
124     @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
125     public boolean equals(Object other) {
126         if (this == other) {
127             return true;
128         }
129 
130         InjectionPoint otherIP = (InjectionPoint) other;
131 
132         if (type instanceof TypeVariable<?> || otherIP.type instanceof TypeVariable<?>) {
133             return false;
134         }
135 
136         String thisName = normalizedName;
137 
138         return type.equals(otherIP.type) && (thisName == null || thisName.equals(otherIP.normalizedName));
139     }
140 
141     @Override
142     public int hashCode() {
143         return 31 * type.hashCode() + (normalizedName == null ? 0 : normalizedName.hashCode());
144     }
145 
146     boolean hasSameName(InjectionPoint otherIP) {
147         String thisName = normalizedName;
148         return thisName != null && thisName.equals(otherIP.normalizedName);
149     }
150 
151     static boolean isServlet(@NonNull Class<?> aClass) {
152         return SERVLET_CLASS != null && Servlet.class.isAssignableFrom(aClass);
153     }
154 
155     @NonNull
156     public static Object wrapInProviderIfNeeded(@NonNull Type type, @NonNull final Object value) {
157         if (INJECT_CLASS != null && type instanceof ParameterizedType && !(value instanceof Provider)) {
158             Type parameterizedType = ((ParameterizedType) type).getRawType();
159 
160             if (parameterizedType == Provider.class) {
161                 return (Provider<Object>) () -> value;
162             }
163 
164             if (INSTANCE_CLASS != null && parameterizedType == Instance.class) {
165                 @SuppressWarnings("unchecked")
166                 List<Object> values = (List<Object>) value;
167                 return new Listed(values);
168             }
169         }
170 
171         return value;
172     }
173 
174     private static final class Listed implements Instance<Object> {
175         @NonNull
176         private final List<Object> instances;
177 
178         Listed(@NonNull List<Object> instances) {
179             this.instances = instances;
180         }
181 
182         @Override
183         public Instance<Object> select(Annotation... annotations) {
184             return null;
185         }
186 
187         @Override
188         public <U> Instance<U> select(Class<U> uClass, Annotation... annotations) {
189             return null;
190         }
191 
192         @Override
193         public <U> Instance<U> select(TypeLiteral<U> tl, Annotation... annotations) {
194             return null;
195         }
196 
197         @Override
198         public boolean isUnsatisfied() {
199             return false;
200         }
201 
202         @Override
203         public boolean isAmbiguous() {
204             return false;
205         }
206 
207         @Override
208         public void destroy(Object instance) {
209         }
210 
211         @Override
212         public Iterator<Object> iterator() {
213             return instances.iterator();
214         }
215 
216         @Override
217         public Object get() {
218             throw new RuntimeException("Unexpected");
219         }
220     }
221 
222     @NonNull
223     public static KindOfInjectionPoint kindOfInjectionPoint(@NonNull AccessibleObject fieldOrConstructor) {
224         Annotation[] annotations = fieldOrConstructor.getDeclaredAnnotations();
225 
226         if (annotations.length == 0) {
227             return KindOfInjectionPoint.NotAnnotated;
228         }
229 
230         if (INJECT_CLASS != null && isAnnotated(annotations, Inject.class)) {
231             return KindOfInjectionPoint.Required;
232         }
233 
234         KindOfInjectionPoint kind = isAutowired(annotations);
235 
236         if (kind != KindOfInjectionPoint.NotAnnotated || fieldOrConstructor instanceof Constructor) {
237             return kind;
238         }
239 
240         if (isRequired(annotations)) {
241             return KindOfInjectionPoint.Required;
242         }
243 
244         return KindOfInjectionPoint.NotAnnotated;
245     }
246 
247     private static boolean isAnnotated(@NonNull Annotation[] declaredAnnotations,
248             @NonNull Class<?> annotationOfInterest) {
249         Annotation annotation = getAnnotation(declaredAnnotations, annotationOfInterest);
250         return annotation != null;
251     }
252 
253     @Nullable
254     private static Annotation getAnnotation(@NonNull Annotation[] declaredAnnotations,
255             @NonNull Class<?> annotationOfInterest) {
256         for (Annotation declaredAnnotation : declaredAnnotations) {
257             if (declaredAnnotation.annotationType() == annotationOfInterest) {
258                 return declaredAnnotation;
259             }
260         }
261 
262         return null;
263     }
264 
265     @NonNull
266     private static KindOfInjectionPoint isAutowired(@NonNull Annotation[] declaredAnnotations) {
267         for (Annotation declaredAnnotation : declaredAnnotations) {
268             Class<?> annotationType = declaredAnnotation.annotationType();
269 
270             if (annotationType.getName().endsWith(".Autowired")) {
271                 Boolean required = invokePublicIfAvailable(annotationType, declaredAnnotation, "required",
272                         NO_PARAMETERS);
273                 return required != null && required ? KindOfInjectionPoint.Required : KindOfInjectionPoint.Optional;
274             }
275         }
276 
277         return KindOfInjectionPoint.NotAnnotated;
278     }
279 
280     private static boolean isRequired(@NonNull Annotation[] annotations) {
281         return isAnnotated(annotations, Resource.class) || EJB_CLASS != null && isAnnotated(annotations, EJB.class)
282                 || PERSISTENCE_UNIT_CLASS != null && (isAnnotated(annotations, PersistenceContext.class)
283                         || isAnnotated(annotations, PersistenceUnit.class));
284     }
285 
286     @NonNull
287     public static Type getTypeOfInjectionPointFromVarargsParameter(@NonNull Type parameterType) {
288         if (parameterType instanceof Class<?>) {
289             return ((Class<?>) parameterType).getComponentType();
290         }
291 
292         return ((GenericArrayType) parameterType).getGenericComponentType();
293     }
294 
295     @Nullable
296     public static String getQualifiedName(@NonNull Annotation[] annotationsOnInjectionPoint) {
297         for (Annotation annotation : annotationsOnInjectionPoint) {
298             Class<?> annotationType = annotation.annotationType();
299             String annotationName = annotationType.getName();
300 
301             if ("javax.annotation.Resource javax.ejb.EJB".contains(annotationName)) {
302                 String name = readAnnotationAttribute(annotation, "name");
303 
304                 if (name.isEmpty()) {
305                     name = readAnnotationAttributeIfAvailable(annotation, "lookup"); // EJB 3.0 has no "lookup"
306                     // attribute
307 
308                     if (name == null || name.isEmpty()) {
309                         name = readAnnotationAttribute(annotation, "mappedName");
310                     }
311 
312                     name = name.isEmpty() ? null : getNameFromJNDILookup(name);
313                 }
314 
315                 return name;
316             }
317 
318             if ("javax.inject.Named".equals(annotationName) || annotationName.endsWith(".Qualifier")) {
319                 return readAnnotationAttribute(annotation, "value");
320             }
321         }
322 
323         return null;
324     }
325 
326     @NonNull
327     public static String getNameFromJNDILookup(@NonNull String jndiLookup) {
328         int p = jndiLookup.lastIndexOf('/');
329 
330         if (p >= 0) {
331             jndiLookup = jndiLookup.substring(p + 1);
332         }
333 
334         return jndiLookup;
335     }
336 }