From 9317483c086220254f5d824f0c2e1894b0371333 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Fri, 16 Feb 2024 12:06:06 +0800 Subject: [PATCH] Support customized implementation method name 1. Expand Implementation with a new method called methodName for mapped method name. 2. Expand SdkStore to consider mapped shadow method for validation. 3. Expand shadow method finding logic when running to add mapped shadow method finding logic as the secondest priority than looseSignature. 4. Split `ShadowBluetoothAdapter#setScanMode` into two methods for different SDKs to test mapped shadow method. Signed-off-by: utzcoz --- .../annotation/Implementation.java | 14 ++++++++ .../processing/validator/SdkStore.java | 9 ++++- .../internal/bytecode/ShadowWrangler.java | 33 +++++++++++++++++++ .../shadows/ShadowBluetoothAdapter.java | 28 ++++++++-------- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/annotations/src/main/java/org/robolectric/annotation/Implementation.java b/annotations/src/main/java/org/robolectric/annotation/Implementation.java index a041c273cbc..181ac960cef 100644 --- a/annotations/src/main/java/org/robolectric/annotation/Implementation.java +++ b/annotations/src/main/java/org/robolectric/annotation/Implementation.java @@ -21,4 +21,18 @@ /** The annotated shadow method will be invoked only for the specified SDK or lesser. */ int maxSdk() default DEFAULT_SDK; + + /** + * The implemented method name. + * + *

Sometimes internal methods return different types for different SDKs. It's safe because + * these methods are internal/private methods, not public methods. To support different return + * types of a method for different SDKs, we often use looseSignature method, although all return + * types are common types like bool and int. This field/property can be used to fix this issue by + * using different real methods for different SDKs/ + * + * @return The expected implemented method name. If it is empty/null, the Robolectric will uses + * the method's name that marked by @Implementation as the implemented method name. + */ + String methodName() default ""; } diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java index e08b3acd1b7..c7a845c9e97 100644 --- a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java +++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java @@ -384,7 +384,14 @@ private static String cleanMethodName(ExecutableElement methodElement) { } else if (STATIC_INITIALIZER_METHOD_NAME.equals(name)) { return ""; } else { - return name; + Implementation implementation = methodElement.getAnnotation(Implementation.class); + String methodName = implementation == null ? "" : implementation.methodName(); + methodName = methodName == null ? "" : methodName.trim(); + if (methodName.isEmpty()) { + return name; + } else { + return methodName; + } } } diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java index 1c6d8c19c5d..fa9e42739c9 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java @@ -21,6 +21,7 @@ import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Priority; +import org.robolectric.annotation.Implementation; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.ReflectorObject; import org.robolectric.sandbox.ShadowMatcher; @@ -305,9 +306,18 @@ private Method findShadowMethod( Class[] types, ShadowInfo shadowInfo, Class shadowClass) { + // First, try to find shadow method with the exact method signature. Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types); + if (method == null) { + // Second, try to find mapped shadow method with the exact method signature except method + // name. + method = findMappedShadowMethodDeclaredOnClass(shadowClass, name, types); + } + if (method == null && shadowInfo.looseSignatures) { + // Third, try to find special all Object method signature if this class is marked by + // looseSignature. Class[] genericTypes = MethodType.genericMethodType(types.length).parameterArray(); method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes); } @@ -333,6 +343,29 @@ private Method findShadowMethod( return method; } + private Method findMappedShadowMethodDeclaredOnClass( + Class shadowClass, String methodName, Class[] paramCLasses) { + String mappedMethodName = methodName; + if (methodName != null) { + Method[] methods = shadowClass.getDeclaredMethods(); + for (Method method : methods) { + if (method == null) { + continue; + } + Implementation implementation = method.getAnnotation(Implementation.class); + if (implementation == null) { + continue; + } + if (methodName.equals(implementation.methodName())) { + mappedMethodName = method.getName(); + break; + } + } + } + // Leverage findShadowMethodDeclaredOnClass to validate mapped method's parameter list. + return findShadowMethodDeclaredOnClass(shadowClass, mappedMethodName, paramCLasses); + } + private Method findShadowMethodDeclaredOnClass( Class shadowClass, String methodName, Class[] paramClasses) { try { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java index bff4fe2b772..95846c41483 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java @@ -381,21 +381,21 @@ protected boolean setName(String name) { * Needs looseSignatures because in Android T the return value of this method was changed from * bool to int. */ - @Implementation - protected Object setScanMode(int scanMode) { - boolean result = true; - if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE - && scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE - && scanMode != BluetoothAdapter.SCAN_MODE_NONE) { - result = false; - } - + @Implementation(maxSdk = S_V2) + protected boolean setScanMode(int scanMode) { + boolean result = + scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE + || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE + || scanMode == BluetoothAdapter.SCAN_MODE_NONE; this.scanMode = scanMode; - if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) { - return result ? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN; - } else { - return result; - } + return result; + } + + @Implementation(minSdk = TIRAMISU, methodName = "setScanMode") + protected int setScanModeFromT(int scanMode) { + return setScanMode(scanMode) + ? BluetoothStatusCodes.SUCCESS + : BluetoothStatusCodes.ERROR_UNKNOWN; } @Implementation(maxSdk = Q)