org.mockito.internal.util.KotlinInlineClassUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mockito-core Show documentation
Show all versions of mockito-core Show documentation
Mockito mock objects library core API and implementation
/*
* Copyright (c) 2021 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.util;
import org.mockito.internal.exceptions.Reporter;
import org.mockito.internal.stubbing.answers.InvocationInfo;
import org.mockito.invocation.InvocationOnMock;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class KotlinInlineClassUtil {
private static Class jvmInlineAnnotation;
static {
try {
jvmInlineAnnotation = (Class) Class.forName("kotlin.jvm.JvmInline");
} catch (ClassNotFoundException e) {
// Do nothing: kotlin is pre 1.5.0
}
}
// When mocking function, returning inline class, its return type is
// underlying type.
// So, `thenReturn` calls fails, because of non-compatible types.
public static boolean isInlineClassWithAssignableUnderlyingType(
Class inlineClass, Class underlyingType) {
try {
// Since 1.5.0 inline classes have @JvmInline annotation
if (jvmInlineAnnotation == null
|| !inlineClass.isAnnotationPresent(jvmInlineAnnotation)) return false;
// All inline classes have 'box-impl' method, which accepts
// underlying type and returns inline class.
// Make sure that the current inline class is also the class that is compatible with the
// underlying type.
// If we don't make this check, then we would potentially pass a mock of inline type A
// into a method
// that accepts inline type B.
Object ignored = inlineClass.getDeclaredMethod("box-impl", underlyingType);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
private static Object unboxInlineClassIfPossible(Object boxedValue) {
Class inlineClass = boxedValue.getClass();
try {
Method unboxImpl = inlineClass.getDeclaredMethod("unbox-impl");
return unboxImpl.invoke(boxedValue);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw Reporter.inlineClassWithoutUnboxImpl(inlineClass, e);
}
}
public static Object unboxUnderlyingValueIfNeeded(InvocationOnMock invocation, Object value) {
// Short path - Kotlin 1.5+ is not present.
if (value == null || jvmInlineAnnotation == null) {
return value;
}
Class valueType = value.getClass();
InvocationInfo invocationInfo = new InvocationInfo(invocation);
Class returnType = invocationInfo.getMethod().getReturnType();
if (valueType.isAssignableFrom(returnType)) return value;
if (isInlineClassWithAssignableUnderlyingType(valueType, returnType)) {
return unboxInlineClassIfPossible(value);
} else {
return value;
}
}
}