• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockito.internal.util;
6 
7 import org.mockito.internal.exceptions.Reporter;
8 import org.mockito.internal.stubbing.answers.InvocationInfo;
9 import org.mockito.invocation.InvocationOnMock;
10 
11 import java.lang.annotation.Annotation;
12 import java.lang.reflect.InvocationTargetException;
13 import java.lang.reflect.Method;
14 
15 public class KotlinInlineClassUtil {
16     private static Class<Annotation> jvmInlineAnnotation;
17 
18     static {
19         try {
20             jvmInlineAnnotation = (Class<Annotation>) Class.forName("kotlin.jvm.JvmInline");
21         } catch (ClassNotFoundException e) {
22             // Do nothing: kotlin is pre 1.5.0
23         }
24     }
25 
26     // When mocking function, returning inline class, its return type is
27     // underlying type.
28     // So, `thenReturn` calls fails, because of non-compatible types.
isInlineClassWithAssignableUnderlyingType( Class<?> inlineClass, Class<?> underlyingType)29     public static boolean isInlineClassWithAssignableUnderlyingType(
30             Class<?> inlineClass, Class<?> underlyingType) {
31         try {
32             // Since 1.5.0 inline classes have @JvmInline annotation
33             if (jvmInlineAnnotation == null
34                     || !inlineClass.isAnnotationPresent(jvmInlineAnnotation)) return false;
35 
36             // All inline classes have 'box-impl' method, which accepts
37             // underlying type and returns inline class.
38             // Make sure that the current inline class is also the class that is compatible with the
39             // underlying type.
40             // If we don't make this check, then we would potentially pass a mock of inline type A
41             // into a method
42             // that accepts inline type B.
43             Object ignored = inlineClass.getDeclaredMethod("box-impl", underlyingType);
44             return true;
45         } catch (NoSuchMethodException e) {
46             return false;
47         }
48     }
49 
unboxInlineClassIfPossible(Object boxedValue)50     private static Object unboxInlineClassIfPossible(Object boxedValue) {
51         Class<?> inlineClass = boxedValue.getClass();
52         try {
53             Method unboxImpl = inlineClass.getDeclaredMethod("unbox-impl");
54             return unboxImpl.invoke(boxedValue);
55         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
56             throw Reporter.inlineClassWithoutUnboxImpl(inlineClass, e);
57         }
58     }
59 
unboxUnderlyingValueIfNeeded(InvocationOnMock invocation, Object value)60     public static Object unboxUnderlyingValueIfNeeded(InvocationOnMock invocation, Object value) {
61         // Short path - Kotlin 1.5+ is not present.
62         if (value == null || jvmInlineAnnotation == null) {
63             return value;
64         }
65 
66         Class<?> valueType = value.getClass();
67         InvocationInfo invocationInfo = new InvocationInfo(invocation);
68         Class<?> returnType = invocationInfo.getMethod().getReturnType();
69         if (valueType.isAssignableFrom(returnType)) return value;
70 
71         if (isInlineClassWithAssignableUnderlyingType(valueType, returnType)) {
72             return unboxInlineClassIfPossible(value);
73         } else {
74             return value;
75         }
76     }
77 }
78