View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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                     // No-op
304                 }
305 
306                 @Override
307                 public void close() {
308                     // No-op
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                     // No-op
339                 }
340 
341                 @Override
342                 public void close() {
343                     // No-op
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"); // EJB 3.0 has no "lookup"
502                     // attribute
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 }