blob: 01447aa8241ee486ef548c82ef86e0c324845952 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.setupwizardlib.util;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.concurrent.ConcurrentHashMap;
/** A registry of singleton-like helpers, which can be injected by the application for testing. */
public class CarHelperRegistry {
/**
* Interface that creates the helper. Typically a constructor that takes a context.
*
* @param <H> The helper class.
*/
public interface HelperCreator<H> {
/** Returns an instance of the helper class. */
@NonNull
H createHelper(@NonNull Context appContext);
}
private static final CarHelperRegistry GLOBAL_REGISTRY = new CarHelperRegistry();
private final ConcurrentHashMap<Class<?>, Object> mMap = new ConcurrentHashMap<>();
/**
* Query the application context for an injected registry, or return the global registry if one
* doesn't exist. Normal runs would not have an injected registry and therefore use the global
* one, while test runs will typically inject a registry so it can inject individual helpers for
* testing.
*
* @param context The context to get the registry from.
* @return The registry injected by the application context if one exists, or the global
* registry.
*/
@NonNull
@VisibleForTesting
static synchronized CarHelperRegistry getRegistry(@NonNull Context context) {
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof CarHelperInjectionContext) {
return ((CarHelperInjectionContext) applicationContext).getCarHelperRegistry();
}
return GLOBAL_REGISTRY;
}
/**
* Get the helper from the registry if it exists, or create and put one into the registry if it
* does not exist.
*
* <p>Since helpers are singleton-like, the context passed to the creator is always the
* application context so it doesn't leak local contexts pass their lifecycles.
*
* @param context The context to create the helper with. This can be any context as this method
* will call {@link Context#getApplicationContext()} to ensure the application context is
* used.
* @param cls The helper class.
* @param creator The method to create the helper. Typically if the helper has a constructor
* that takes a context, this creator will be {@code MyHelper::new}.
* @param <H> The helper class.
* @return The helper in the registry, either existing or newly registered.
*/
@NonNull
public static <H> H getOrCreateWithAppContext(
@NonNull Context context, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) {
return CarHelperRegistry.getRegistry(context)
.getOrCreateHelper(context.getApplicationContext(), cls, creator);
}
/**
* Retrieve a helper from the registry.
*
* @param cls The helper class.
* @param <H> The helper class.
* @return The helper of the given {@code cls}, or null if no helper of {@code cls} is
* registered.
*/
@Nullable
@SuppressWarnings("unchecked")
@VisibleForTesting
<H> H getHelper(@NonNull Class<H> cls) {
return (H) mMap.get(cls);
}
/** @see #getOrCreateWithAppContext(Context, Class, HelperCreator) */
@NonNull
@VisibleForTesting
<H> H getOrCreateHelper(
@NonNull Context appContext, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) {
// Synchronize on the class to ensure only creator is only called once (per classloader)
synchronized (cls) {
H helper = getHelper(cls);
if (helper == null) {
helper = creator.createHelper(appContext);
putHelper(cls, helper);
}
return helper;
}
}
@VisibleForTesting
int size() {
return mMap.size();
}
/**
* Put a helper for the given {@code cls} into the registry. Outside of this class, this should
* only be used to inject helper for testing purposes. Singletons should use {@link
* #getOrCreateWithAppContext(Context, Class, HelperCreator)} to create the instance which will
*
* @param cls The helper class.
* @param helper The helper instance to add to the registry.
* @param <H> The helper class.
*/
public <H> void putHelper(@NonNull Class<H> cls, @NonNull H helper) {
mMap.put(cls, helper);
}
}