chromium/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingStateCache.java

// Copyright 2019 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.chrome.browser.keyboard_accessory;

import androidx.annotation.Nullable;

import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.WebContents;

import java.util.HashMap;

/**
 * This class stores the state of the ManualFilling-components. It holds {@link ManualFillingState}s
 * associated to {@link WebContents} until explicitly destroyed.
 *
 * Example use:
 * <code>
 *    ManualFillingStateCache cache = new ManualFillingCache();
 *    @NonNull WebContents w1 = [...];
 *    @NonNull WebContents w2 = [...];
 *    assert cache.getStateFor(w1) == cache.getStateFor(w1);
 *    assert cache.getStateFor(w1) != cache.getStateFor(w2);
 *    assert cache.getStateFor(null) != cache.getStateFor(null);
 *    cache.destroyStateFor(w1); // State for w1 cleaned; reference to w2 dropped.
 *    caches.destroy(); // State for w2 cleaned; reference to w2 dropped.
 * </code>
 */
class ManualFillingStateCache {
    private final HashMap<WebContents, ManualFillingState> mStatesForWebContents = new HashMap<>();

    ManualFillingStateCache() {}

    /**
     * @see #getStateFor(WebContents)
     * @param tab A {@link Tab} for whose {@link WebContents} a state is needed.
     * @return A {@link ManualFillingState}. Never null.
     */
    ManualFillingState getStateFor(Tab tab) {
        return getStateFor(tab.getWebContents());
    }

    /**
     * Returns a state for the given WebContents and caches it. If the given WebContents are null,
     * the returned empty state is not cached.
     * @param webContents {@link WebContents} for which a state is needed.
     * @return A {@link ManualFillingState}. Never null.
     */
    ManualFillingState getStateFor(@Nullable WebContents webContents) {
        if (webContents == null || webContents.isDestroyed()) {
            // If state is requested for destroyed or invalid WebContents, it returns a null object.
            return new ManualFillingState(null);
        }
        ManualFillingState state = mStatesForWebContents.get(webContents);
        if (state != null) return state;
        state = new ManualFillingState(webContents);
        mStatesForWebContents.put(webContents, state);
        return state;
    }

    /**
     * Destroys all held states and removes the held references to the WebContents they belong to.
     */
    void destroy() {
        for (ManualFillingState userState : mStatesForWebContents.values()) userState.destroy();
        mStatesForWebContents.clear();
    }

    /**
     * @see #destroyStateFor(WebContents)
     * @param tab The tab whose WebContents are going to be destroyed.
     */
    void destroyStateFor(Tab tab) {
        destroyStateFor(tab.getWebContents());
    }

    /**
     * Ensures a reference to WebContents isn't held longer than necessary so GC can collect it.
     * @param webContents The WebContents about to be destroyed and should not be held any longer.
     */
    void destroyStateFor(WebContents webContents) {
        if (webContents != null) { // No need to check isDestroyed since the object is only a key.
            getStateFor(webContents).destroy();
            mStatesForWebContents.remove(webContents);
        }
    }
}