1
2
3
4
5
6 package mockit.internal.injection;
7
8 import static java.lang.Character.toUpperCase;
9
10 import static mockit.internal.reflection.AnnotationReflection.readAnnotationAttribute;
11 import static mockit.internal.reflection.AnnotationReflection.readAnnotationAttributeIfAvailable;
12 import static mockit.internal.reflection.MethodReflection.invokePublicIfAvailable;
13 import static mockit.internal.reflection.ParameterReflection.NO_PARAMETERS;
14 import static mockit.internal.util.ClassLoad.searchTypeInClasspath;
15
16 import edu.umd.cs.findbugs.annotations.NonNull;
17 import edu.umd.cs.findbugs.annotations.Nullable;
18
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.AccessibleObject;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.GenericArrayType;
23 import java.lang.reflect.ParameterizedType;
24 import java.lang.reflect.Type;
25 import java.lang.reflect.TypeVariable;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29
30 public final class InjectionPoint {
31 public enum KindOfInjectionPoint {
32 NotAnnotated, Required, Optional
33 }
34
35 @Nullable
36 public static final Class<?> JAKARTA_CONVERSATION_CLASS;
37 @Nullable
38 public static final Class<?> JAVAX_CONVERSATION_CLASS;
39
40 @Nullable
41 private static final Class<? extends Annotation> JAKARTA_EJB_CLASS;
42 @Nullable
43 private static final Class<? extends Annotation> JAVAX_EJB_CLASS;
44
45 @Nullable
46 public static final Class<? extends Annotation> JAKARTA_INJECT_CLASS;
47 @Nullable
48 public static final Class<? extends Annotation> JAVAX_INJECT_CLASS;
49
50 @Nullable
51 private static final Class<? extends Annotation> JAKARTA_INSTANCE_CLASS;
52 @Nullable
53 private static final Class<? extends Annotation> JAVAX_INSTANCE_CLASS;
54
55 @Nullable
56 public static final Class<? extends Annotation> JAKARTA_PERSISTENCE_UNIT_CLASS;
57 @Nullable
58 public static final Class<? extends Annotation> JAVAX_PERSISTENCE_UNIT_CLASS;
59
60 @Nullable
61 public static final Class<? extends Annotation> JAKARTA_POST_CONSTRUCT_CLASS;
62 @Nullable
63 public static final Class<? extends Annotation> JAVAX_POST_CONSTRUCT_CLASS;
64
65 @Nullable
66 public static final Class<?> JAKARTA_RESOURCE_CLASS;
67 @Nullable
68 public static final Class<?> JAVAX_RESOURCE_CLASS;
69
70 @Nullable
71 public static final Class<?> JAKARTA_SERVLET_CLASS;
72 @Nullable
73 public static final Class<?> JAVAX_SERVLET_CLASS;
74
75 static {
76 JAKARTA_CONVERSATION_CLASS = searchTypeInClasspath("jakarta.enterprise.context.Conversation");
77 JAVAX_CONVERSATION_CLASS = searchTypeInClasspath("javax.enterprise.context.Conversation");
78
79 JAKARTA_EJB_CLASS = searchTypeInClasspath("jakarta.ejb.EJB");
80 JAVAX_EJB_CLASS = searchTypeInClasspath("javax.ejb.EJB");
81
82 JAKARTA_INJECT_CLASS = searchTypeInClasspath("jakarta.inject.Inject");
83 JAVAX_INJECT_CLASS = searchTypeInClasspath("javax.inject.Inject");
84
85 JAKARTA_INSTANCE_CLASS = searchTypeInClasspath("jakarta.enterprise.inject.Instance");
86 JAVAX_INSTANCE_CLASS = searchTypeInClasspath("javax.enterprise.inject.Instance");
87
88 JAKARTA_POST_CONSTRUCT_CLASS = searchTypeInClasspath("jakarta.annotation.PostConstruct");
89 JAVAX_POST_CONSTRUCT_CLASS = searchTypeInClasspath("javax.annotation.PostConstruct");
90
91 JAKARTA_RESOURCE_CLASS = searchTypeInClasspath("jakarta.annotation.Resource");
92 JAVAX_RESOURCE_CLASS = searchTypeInClasspath("javax.annotation.Resource");
93
94 JAKARTA_SERVLET_CLASS = searchTypeInClasspath("jakarta.servlet.Servlet");
95 JAVAX_SERVLET_CLASS = searchTypeInClasspath("javax.servlet.Servlet");
96
97 Class<? extends Annotation> entity = searchTypeInClasspath("jakarta.persistence.Entity");
98
99 if (entity == null) {
100 JAKARTA_PERSISTENCE_UNIT_CLASS = null;
101 } else {
102 JAKARTA_PERSISTENCE_UNIT_CLASS = searchTypeInClasspath("jakarta.persistence.PersistenceUnit");
103 }
104
105 entity = searchTypeInClasspath("javax.persistence.Entity");
106
107 if (entity == null) {
108 JAVAX_PERSISTENCE_UNIT_CLASS = null;
109 } else {
110 JAVAX_PERSISTENCE_UNIT_CLASS = searchTypeInClasspath("javax.persistence.PersistenceUnit");
111 }
112 }
113
114 @NonNull
115 public final Type type;
116
117 @Nullable
118 public final String name;
119
120 @Nullable
121 private final String normalizedName;
122
123 public final boolean qualified;
124
125 public InjectionPoint(@NonNull Type type) {
126 this(type, null, false);
127 }
128
129 public InjectionPoint(@NonNull Type type, @Nullable String name) {
130 this(type, name, false);
131 }
132
133 public InjectionPoint(@NonNull Type type, @Nullable String name, boolean qualified) {
134 this.type = type;
135 this.name = name;
136 normalizedName = name == null ? null : convertToLegalJavaIdentifierIfNeeded(name);
137 this.qualified = qualified;
138 }
139
140 public InjectionPoint(@NonNull Type type, @NonNull String name, @Nullable String qualifiedName) {
141 this.type = type;
142 this.name = qualifiedName == null ? name : qualifiedName;
143 normalizedName = this.name;
144 qualified = qualifiedName != null;
145 }
146
147 @NonNull
148 public static String convertToLegalJavaIdentifierIfNeeded(@NonNull String name) {
149 if (name.indexOf('-') < 0 && name.indexOf('.') < 0) {
150 return name;
151 }
152
153 StringBuilder identifier = new StringBuilder(name);
154
155 for (int i = name.length() - 1; i >= 0; i--) {
156 char c = identifier.charAt(i);
157
158 if (c == '-' || c == '.') {
159 identifier.deleteCharAt(i);
160 char d = identifier.charAt(i);
161 identifier.setCharAt(i, toUpperCase(d));
162 }
163 }
164
165 return identifier.toString();
166 }
167
168 @Override
169 @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
170 public boolean equals(Object other) {
171 if (this == other) {
172 return true;
173 }
174
175 InjectionPoint otherIP = (InjectionPoint) other;
176
177 if (type instanceof TypeVariable<?> || otherIP.type instanceof TypeVariable<?>) {
178 return false;
179 }
180
181 String thisName = normalizedName;
182
183 return type.equals(otherIP.type) && (thisName == null || thisName.equals(otherIP.normalizedName));
184 }
185
186 @Override
187 public int hashCode() {
188 return 31 * type.hashCode() + (normalizedName == null ? 0 : normalizedName.hashCode());
189 }
190
191 boolean hasSameName(InjectionPoint otherIP) {
192 String thisName = normalizedName;
193 return thisName != null && thisName.equals(otherIP.normalizedName);
194 }
195
196 static boolean isJakartaServlet(@NonNull Class<?> aClass) {
197 return JAKARTA_SERVLET_CLASS != null && jakarta.servlet.Servlet.class.isAssignableFrom(aClass);
198 }
199
200 static boolean isJavaxServlet(@NonNull Class<?> aClass) {
201 return JAVAX_SERVLET_CLASS != null && javax.servlet.Servlet.class.isAssignableFrom(aClass);
202 }
203
204 @NonNull
205 public static Object wrapInProviderIfNeeded(@NonNull Type type, @NonNull final Object value) {
206 if (JAKARTA_INJECT_CLASS != null && type instanceof ParameterizedType
207 && !(value instanceof jakarta.inject.Provider)) {
208 Type parameterizedType = ((ParameterizedType) type).getRawType();
209
210 if (parameterizedType == jakarta.inject.Provider.class) {
211 return (jakarta.inject.Provider<Object>) () -> value;
212 }
213
214 if (JAKARTA_INSTANCE_CLASS != null && parameterizedType == jakarta.enterprise.inject.Instance.class) {
215 @SuppressWarnings("unchecked")
216 List<Object> values = (List<Object>) value;
217 return new ListedJakarta(values);
218 }
219 }
220
221 if (JAVAX_INJECT_CLASS != null && type instanceof ParameterizedType
222 && !(value instanceof javax.inject.Provider)) {
223 Type parameterizedType = ((ParameterizedType) type).getRawType();
224
225 if (parameterizedType == javax.inject.Provider.class) {
226 return (javax.inject.Provider<Object>) () -> value;
227 }
228
229 if (JAVAX_INSTANCE_CLASS != null && parameterizedType == javax.enterprise.inject.Instance.class) {
230 @SuppressWarnings("unchecked")
231 List<Object> values = (List<Object>) value;
232 return new ListedJavax(values);
233 }
234 }
235
236 return value;
237 }
238
239 private static final class ListedJakarta implements jakarta.enterprise.inject.Instance<Object> {
240 @NonNull
241 private final List<Object> instances;
242
243 ListedJakarta(@NonNull List<Object> instances) {
244 this.instances = instances;
245 }
246
247 @Override
248 public jakarta.enterprise.inject.Instance<Object> select(Annotation... annotations) {
249 return null;
250 }
251
252 @Override
253 public <U> jakarta.enterprise.inject.Instance<U> select(Class<U> uClass, Annotation... annotations) {
254 return null;
255 }
256
257 @Override
258 public <U> jakarta.enterprise.inject.Instance<U> select(jakarta.enterprise.util.TypeLiteral<U> tl,
259 Annotation... annotations) {
260 return null;
261 }
262
263 @Override
264 public boolean isUnsatisfied() {
265 return false;
266 }
267
268 @Override
269 public boolean isAmbiguous() {
270 return false;
271 }
272
273 @Override
274 public void destroy(Object instance) {
275 }
276
277 @Override
278 public Iterator<Object> iterator() {
279 return instances.iterator();
280 }
281
282 @Override
283 public Object get() {
284 throw new RuntimeException("Unexpected");
285 }
286
287 @Override
288 public Iterable<? extends jakarta.enterprise.inject.Instance.Handle<Object>> handles() {
289 class SimpleHandle implements jakarta.enterprise.inject.Instance.Handle<Object> {
290 private final Object instance;
291
292 SimpleHandle(Object instance) {
293 this.instance = instance;
294 }
295
296 @Override
297 public Object get() {
298 return instance;
299 }
300
301 @Override
302 public void destroy() {
303
304 }
305
306 @Override
307 public void close() {
308
309 }
310
311 @Override
312 public jakarta.enterprise.inject.spi.Bean<Object> getBean() {
313 return null;
314 }
315 }
316 List<SimpleHandle> handleList = new ArrayList<>();
317 for (Object obj : instances) {
318 handleList.add(new SimpleHandle(obj));
319 }
320 return handleList;
321 }
322
323 @Override
324 public jakarta.enterprise.inject.Instance.Handle<Object> getHandle() {
325 if (instances.isEmpty()) {
326 throw new RuntimeException("No instance available");
327 }
328 return new jakarta.enterprise.inject.Instance.Handle<Object>() {
329 private final Object instance = instances.get(0);
330
331 @Override
332 public Object get() {
333 return instance;
334 }
335
336 @Override
337 public void destroy() {
338
339 }
340
341 @Override
342 public void close() {
343
344 }
345
346 @Override
347 public jakarta.enterprise.inject.spi.Bean<Object> getBean() {
348 return null;
349 }
350 };
351 }
352 }
353
354 private static final class ListedJavax implements javax.enterprise.inject.Instance<Object> {
355 @NonNull
356 private final List<Object> instances;
357
358 ListedJavax(@NonNull List<Object> instances) {
359 this.instances = instances;
360 }
361
362 @Override
363 public javax.enterprise.inject.Instance<Object> select(Annotation... annotations) {
364 return null;
365 }
366
367 @Override
368 public <U> javax.enterprise.inject.Instance<U> select(Class<U> uClass, Annotation... annotations) {
369 return null;
370 }
371
372 @Override
373 public <U> javax.enterprise.inject.Instance<U> select(javax.enterprise.util.TypeLiteral<U> tl,
374 Annotation... annotations) {
375 return null;
376 }
377
378 @Override
379 public boolean isUnsatisfied() {
380 return false;
381 }
382
383 @Override
384 public boolean isAmbiguous() {
385 return false;
386 }
387
388 @Override
389 public void destroy(Object instance) {
390 }
391
392 @Override
393 public Iterator<Object> iterator() {
394 return instances.iterator();
395 }
396
397 @Override
398 public Object get() {
399 throw new RuntimeException("Unexpected");
400 }
401 }
402
403 @NonNull
404 public static KindOfInjectionPoint kindOfInjectionPoint(@NonNull AccessibleObject fieldOrConstructor) {
405 Annotation[] annotations = fieldOrConstructor.getDeclaredAnnotations();
406
407 if (annotations.length == 0) {
408 return KindOfInjectionPoint.NotAnnotated;
409 }
410
411 if (JAKARTA_INJECT_CLASS != null && isAnnotated(annotations, jakarta.inject.Inject.class)) {
412 return KindOfInjectionPoint.Required;
413 }
414
415 if (JAVAX_INJECT_CLASS != null && isAnnotated(annotations, javax.inject.Inject.class)) {
416 return KindOfInjectionPoint.Required;
417 }
418
419 KindOfInjectionPoint kind = isAutowired(annotations);
420
421 if (kind != KindOfInjectionPoint.NotAnnotated || fieldOrConstructor instanceof Constructor) {
422 return kind;
423 }
424
425 if (isRequiredJakarta(annotations) || isRequiredJavax(annotations)) {
426 return KindOfInjectionPoint.Required;
427 }
428
429 return KindOfInjectionPoint.NotAnnotated;
430 }
431
432 private static boolean isAnnotated(@NonNull Annotation[] declaredAnnotations,
433 @NonNull Class<?> annotationOfInterest) {
434 Annotation annotation = getAnnotation(declaredAnnotations, annotationOfInterest);
435 return annotation != null;
436 }
437
438 @Nullable
439 private static Annotation getAnnotation(@NonNull Annotation[] declaredAnnotations,
440 @NonNull Class<?> annotationOfInterest) {
441 for (Annotation declaredAnnotation : declaredAnnotations) {
442 if (declaredAnnotation.annotationType() == annotationOfInterest) {
443 return declaredAnnotation;
444 }
445 }
446
447 return null;
448 }
449
450 @NonNull
451 private static KindOfInjectionPoint isAutowired(@NonNull Annotation[] declaredAnnotations) {
452 for (Annotation declaredAnnotation : declaredAnnotations) {
453 Class<?> annotationType = declaredAnnotation.annotationType();
454
455 if (annotationType.getName().endsWith(".Autowired")) {
456 Boolean required = invokePublicIfAvailable(annotationType, declaredAnnotation, "required",
457 NO_PARAMETERS);
458 return required != null && required ? KindOfInjectionPoint.Required : KindOfInjectionPoint.Optional;
459 }
460 }
461
462 return KindOfInjectionPoint.NotAnnotated;
463 }
464
465 private static boolean isRequiredJakarta(@NonNull Annotation[] annotations) {
466 return JAKARTA_RESOURCE_CLASS != null && isAnnotated(annotations, jakarta.annotation.Resource.class)
467 || JAKARTA_EJB_CLASS != null && isAnnotated(annotations, jakarta.ejb.EJB.class)
468 || JAKARTA_PERSISTENCE_UNIT_CLASS != null
469 && (isAnnotated(annotations, jakarta.persistence.PersistenceContext.class)
470 || isAnnotated(annotations, jakarta.persistence.PersistenceUnit.class));
471 }
472
473 private static boolean isRequiredJavax(@NonNull Annotation[] annotations) {
474 return JAVAX_RESOURCE_CLASS != null && isAnnotated(annotations, javax.annotation.Resource.class)
475 || JAVAX_EJB_CLASS != null && isAnnotated(annotations, javax.ejb.EJB.class)
476 || JAVAX_PERSISTENCE_UNIT_CLASS != null
477 && (isAnnotated(annotations, javax.persistence.PersistenceContext.class)
478 || isAnnotated(annotations, javax.persistence.PersistenceUnit.class));
479 }
480
481 @NonNull
482 public static Type getTypeOfInjectionPointFromVarargsParameter(@NonNull Type parameterType) {
483 if (parameterType instanceof Class<?>) {
484 return ((Class<?>) parameterType).getComponentType();
485 }
486
487 return ((GenericArrayType) parameterType).getGenericComponentType();
488 }
489
490 @Nullable
491 public static String getQualifiedName(@NonNull Annotation[] annotationsOnInjectionPoint) {
492 for (Annotation annotation : annotationsOnInjectionPoint) {
493 Class<?> annotationType = annotation.annotationType();
494 String annotationName = annotationType.getName();
495
496 if ("jakarta.annotation.Resource jakarta.ejb.EJB".contains(annotationName)
497 || "javax.annotation.Resource javax.ejb.EJB".contains(annotationName)) {
498 String name = readAnnotationAttribute(annotation, "name");
499
500 if (name.isEmpty()) {
501 name = readAnnotationAttributeIfAvailable(annotation, "lookup");
502
503
504 if (name == null || name.isEmpty()) {
505 name = readAnnotationAttribute(annotation, "mappedName");
506 }
507
508 name = name.isEmpty() ? null : getNameFromJNDILookup(name);
509 }
510
511 return name;
512 }
513
514 if ("jakarta.inject.Named".equals(annotationName) || "javax.inject.Named".equals(annotationName)
515 || annotationName.endsWith(".Qualifier")) {
516 return readAnnotationAttribute(annotation, "value");
517 }
518 }
519
520 return null;
521 }
522
523 @NonNull
524 public static String getNameFromJNDILookup(@NonNull String jndiLookup) {
525 int p = jndiLookup.lastIndexOf('/');
526
527 if (p >= 0) {
528 jndiLookup = jndiLookup.substring(p + 1);
529 }
530
531 return jndiLookup;
532 }
533 }