1
2
3
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");
306
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 }