Skip to content

Commit d9a1307

Browse files
Add support for @Supersedes annotation in ServiceLoaderWrapper
This allows us to explicitly mark an implementation as being a replacement for another implementation. This is useful for cases where two or more implementations of a service exist, but only one should be used and the other can't be removed. PiperOrigin-RevId: 766669871
1 parent d59c2ea commit d9a1307

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

runner/monitor/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
**New Features**
1010

11+
* Adds @Supersedes to ServiceLoaderWrapper so it's possible to choose one
12+
implementation over another when multiple exist.
13+
1114
**Breaking Changes**
1215

1316
**API Changes**

runner/monitor/java/androidx/test/api/current_internal.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ package androidx.test.internal.platform {
1111
method public T! create();
1212
}
1313

14+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Supersedes {
15+
method public abstract Class<? extends java.lang.Object!> value();
16+
}
17+
1418
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface ThreadChecker {
1519
method public void checkMainThread();
1620
method public void checkNotMainThread();

runner/monitor/java/androidx/test/internal/platform/ServiceLoaderWrapper.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import android.os.StrictMode;
1919
import androidx.annotation.RestrictTo;
2020
import java.util.ArrayList;
21+
import java.util.HashSet;
2122
import java.util.List;
2223
import java.util.ServiceLoader;
24+
import java.util.Set;
2325

2426
/**
2527
* Wrapper class for {@link ServiceLoader} that disables StrictMode.
@@ -79,14 +81,16 @@ public static <T> T loadSingleService(Class<T> serviceClass, Factory<T> defaultI
7981

8082
/**
8183
* A wrapper method around {@link #loadService(Class)} that returns one implementation of the
82-
* service or {@code null} if no implementation is found.
84+
* service or {@code null} if no implementation is found. If an implementation is marked with
85+
* {@link Supersedes}, the implementation in Supersedes will be filtered out.
8386
*
8487
* @param serviceClass the service type class to load implementation for
85-
* @return the implementing service or null if none is found.
88+
* @return the implementing service or null if none is found. If an implementation is marked with
89+
* {@link Supersedes}, the implementation in Supersedes will be filtered out.
8690
* @throws IllegalStateException if more than one service implementations are found
8791
*/
8892
public static <T> T loadSingleServiceOrNull(Class<T> serviceClass) {
89-
List<T> impls = ServiceLoaderWrapper.loadService(serviceClass);
93+
List<T> impls = filter(ServiceLoaderWrapper.loadService(serviceClass));
9094
if (impls.isEmpty()) {
9195
return null;
9296
} else if (impls.size() == 1) {
@@ -102,4 +106,27 @@ public static <T> T loadSingleServiceOrNull(Class<T> serviceClass) {
102106
"Found more than one implementation for " + serviceClass.getName() + combinedImpls);
103107
}
104108
}
109+
110+
@SuppressWarnings("unchecked")
111+
private static <T> List<T> filter(List<T> services) {
112+
Set<Class<?>> superseded = new HashSet<>();
113+
for (T service : services) {
114+
Class<? extends T> clazz = (Class<? extends T>) service.getClass();
115+
Supersedes supersedes = clazz.getAnnotation(Supersedes.class);
116+
if (supersedes != null) {
117+
superseded.add(supersedes.value());
118+
}
119+
}
120+
if (superseded.isEmpty()) {
121+
return services;
122+
} else {
123+
List<T> filtered = new ArrayList<>();
124+
for (T service : services) {
125+
if (!superseded.contains(service.getClass())) {
126+
filtered.add(service);
127+
}
128+
}
129+
return filtered;
130+
}
131+
}
105132
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package androidx.test.internal.platform;
2+
3+
import androidx.annotation.RestrictTo;
4+
import java.lang.annotation.Documented;
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Indicates that the annotated type loaded by ServiceLoadWrapper is intended as a replacement for
12+
* another type.
13+
*
14+
* @hide
15+
*/
16+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
17+
@Documented
18+
@Retention(RetentionPolicy.RUNTIME)
19+
@Target(ElementType.TYPE)
20+
public @interface Supersedes {
21+
22+
/** The type that is superseded by the annotated type. */
23+
Class<?> value();
24+
}

0 commit comments

Comments
 (0)