Skip to content

Commit

Permalink
Support customized implementation method name
Browse files Browse the repository at this point in the history
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 <utzcoz@outlook.com>
  • Loading branch information
utzcoz committed Feb 16, 2024
1 parent be9dada commit 9317483
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 15 deletions.
Expand Up @@ -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.
*
* <p>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 "";
}
Expand Up @@ -384,7 +384,14 @@ private static String cleanMethodName(ExecutableElement methodElement) {
} else if (STATIC_INITIALIZER_METHOD_NAME.equals(name)) {
return "<clinit>";
} 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;
}
}
}

Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down
Expand Up @@ -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)
Expand Down

0 comments on commit 9317483

Please sign in to comment.