chromium/base/android/java/src/org/chromium/base/ServiceLoaderUtil.java

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base;

import android.util.ArrayMap;

import androidx.annotation.Nullable;

import org.chromium.build.annotations.AlwaysInline;

import java.util.Map;
import java.util.ServiceLoader;

/**
 * A wrapper around java.util.ServiceLoader, which ensures (with the help of R8) that ServiceLoader
 * calls are optimized away in release builds.
 *
 * <pre>
 * Example usage:
 *   // In release builds, this will be replace by either: "null" or "new MyImpl()".
 *   MyInterface impl = ServiceLoaderUtil.maybeCreate(MyInterface.class);
 *   if (impl != null) {
 *     impl.doThing();
 *   }
 *
 * To inject a singleton:
 *   public static @Nullable MyInterface getInstance() {
 *       MyProvider provider = ServiceLoaderUtil.maybeCreate(MyProvider.class);
 *       // So long as MyProviderImpl is stateless, provider.getInstance() will be inlined.
 *       return provider != null ? provider.getInstance() : null;
 *   }
 *
 * To set up MyInterfaceImpl:
 *   1) Enable Annotation Processor:
 *     android_library("my_impl_java") {
 *       annotation_processor_deps =
 *           [ "//third_party/android_deps:auto_service_processor" ]
 *     }
 *   2) Add annotation to impl class:
 *     @AutoService(MyInterface.class)
 *     public class MyInterfaceImpl implements MyInterface {
 *         // Must have a public no-arg constructor.
 *         ...
 *     }
 *
 * </pre>
 */
public final class ServiceLoaderUtil {
    private static Map<Class<?>, Object> sOverridesForTesting;

    private ServiceLoaderUtil() {}

    @AlwaysInline
    public static <T> @Nullable T maybeCreate(Class<T> clazz) {
        if (sOverridesForTesting != null) {
            Object ret = sOverridesForTesting.get(clazz);
            if (ret != null) {
                return clazz.cast(ret);
            }
        }

        var it = ServiceLoader.load(clazz, clazz.getClassLoader()).iterator();
        if (it.hasNext()) {
            T ret = it.next();
            assert !it.hasNext()
                    : String.format(
                            "Multiple implementations found for %s: %s and %s",
                            clazz, ret.getClass(), it.next().getClass());
            return ret;
        }
        return null;
    }

    @SuppressWarnings("NoSynchronizedMethodCheck")
    public static synchronized <T> void setInstanceForTesting(Class<T> clazz, T instance) {
        if (sOverridesForTesting == null) {
            sOverridesForTesting = new ArrayMap<>();
        }
        sOverridesForTesting.put(clazz, instance);
        ResettersForTesting.register(() -> sOverridesForTesting.remove(clazz));
    }
}