diff --git a/runner/monitor/CHANGELOG.md b/runner/monitor/CHANGELOG.md index 81697fb4a..97e6e5438 100644 --- a/runner/monitor/CHANGELOG.md +++ b/runner/monitor/CHANGELOG.md @@ -8,6 +8,9 @@ **New Features** +* Adds @Supersedes to ServiceLoaderWrapper so it's possible to choose one +implementation over another when multiple exist. + **Breaking Changes** **API Changes** diff --git a/runner/monitor/java/androidx/test/internal/platform/ServiceLoaderWrapper.java b/runner/monitor/java/androidx/test/internal/platform/ServiceLoaderWrapper.java index 7749b68e7..983b7ecda 100644 --- a/runner/monitor/java/androidx/test/internal/platform/ServiceLoaderWrapper.java +++ b/runner/monitor/java/androidx/test/internal/platform/ServiceLoaderWrapper.java @@ -18,8 +18,10 @@ import android.os.StrictMode; import androidx.annotation.RestrictTo; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.ServiceLoader; +import java.util.Set; /** * Wrapper class for {@link ServiceLoader} that disables StrictMode. @@ -86,7 +88,7 @@ public static T loadSingleService(Class serviceClass, Factory defaultI * @throws IllegalStateException if more than one service implementations are found */ public static T loadSingleServiceOrNull(Class serviceClass) { - List impls = ServiceLoaderWrapper.loadService(serviceClass); + List impls = filter(ServiceLoaderWrapper.loadService(serviceClass)); if (impls.isEmpty()) { return null; } else if (impls.size() == 1) { @@ -102,4 +104,27 @@ public static T loadSingleServiceOrNull(Class serviceClass) { "Found more than one implementation for " + serviceClass.getName() + combinedImpls); } } + + @SuppressWarnings("unchecked") + private static List filter(List services) { + Set> superseded = new HashSet<>(); + for (T service : services) { + Class clazz = (Class) service.getClass(); + Supersedes supersedes = clazz.getAnnotation(Supersedes.class); + if (supersedes != null) { + superseded.add(supersedes.value()); + } + } + if (superseded.isEmpty()) { + return services; + } else { + List filtered = new ArrayList<>(); + for (T service : services) { + if (!superseded.contains(service.getClass())) { + filtered.add(service); + } + } + return filtered; + } + } } diff --git a/runner/monitor/java/androidx/test/internal/platform/Supersedes.java b/runner/monitor/java/androidx/test/internal/platform/Supersedes.java new file mode 100644 index 000000000..5fe98aca9 --- /dev/null +++ b/runner/monitor/java/androidx/test/internal/platform/Supersedes.java @@ -0,0 +1,17 @@ +package androidx.test.internal.platform; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Indicates that the annotated type is intended as a replacement for another type. */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Supersedes { + + /** The type that is superseded by the annotated type. */ + Class value(); +}