// Copyright 2020 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.supplier;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;

import org.chromium.base.UnownedUserData;
import org.chromium.base.UnownedUserDataHost;
import org.chromium.base.UnownedUserDataKey;
import org.chromium.base.lifetime.DestroyChecker;
import org.chromium.base.lifetime.Destroyable;

/**
 * UnownedUserDataSupplier handles the combined lifecycle management for {@link UnownedUserData}
 * and {@link DestroyableObservableSupplier}. It can be constructed anywhere
 * but needs to be attached before it's accessible via {@link UnownedUserDataHost}. When destroyed,
 * UnownedUserDataSupplier is detached from all hosts.
 *
 * <p>
 * A functional implementation with best practices is defined in
 * {@link UnownedUserDataSupplierTest}.
 * <p>
 * Classes that hold a reference to to the concrete implementation of this class are also in charge
 * of its lifecycle. {@link #destroy} should be called when the applciation is shutting down. This
 * will detach the {@link UnownedUserDataSupplier}, but it won't destroy the supplied object.
 * <p>
 * In practice, UnownedUserDataSupplier owners should declare and assign the supplier inline. This
 * allows interop between other supplier implementations as well as use in activity constructors
 * before {@link WindowAndroid} is created. See the example below:
 *
 * <pre>
 * {@code
 * UnownedUserDataSupplier<Foo> mFooSupplier = new FooSupplier();
 * ...
 * // Sometime after WindowAndroid has been created.
 * mFooSupplier.attach(mWindowAndroid.getUnownedUserDataHost());
 * }
 * </pre>
 *
 * @param <E> The type of the data to be Supplied and stored in UnownedUserData.
 * @see UnownedUserDataHost for more details on ownership and typical usage.
 * @see UnownedUserDataKey for information about the type of key that is required.
 * @see UnownedUserData for the marker interface used for this type of data.
 */
public abstract class UnownedUserDataSupplier<E>
        extends ObservableSupplierImpl<E> implements Destroyable, UnownedUserData {
    private final UnownedUserDataKey<UnownedUserDataSupplier<E>> mUudKey;
    private final DestroyChecker mDestroyChecker = new DestroyChecker();

    /**
     * Constructs an UnownedUserDataSupplier.
     * @param uudKey The {@link UnownedUserDataKey}, which is defined in subclasses.
     */
    protected UnownedUserDataSupplier(
            @NonNull UnownedUserDataKey<? extends UnownedUserDataSupplier<E>> uudKey) {
        mUudKey = (UnownedUserDataKey<UnownedUserDataSupplier<E>>) uudKey;
    }

    /**
     * Attach to the specified host.
     * @param host The host to attach the supplier to.
     */
    public void attach(@NonNull UnownedUserDataHost host) {
        mDestroyChecker.checkNotDestroyed();
        mUudKey.attachToHost(host, this);
    }

    @Override
    @CallSuper
    public void destroy() {
        mDestroyChecker.destroy();
        mUudKey.detachFromAllHosts(this);
    }
}
