chromium/ui/android/java/src/org/chromium/ui/modelutil/PropertyModelChangeProcessor.java

// Copyright 2018 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.ui.modelutil;

import org.chromium.ui.modelutil.PropertyObservable.PropertyObserver;

/**
 * A model change processor for use with a {@link PropertyObservable} model. The
 * {@link PropertyModelChangeProcessor} should be registered as a property observer of the model.
 * Internally uses a view binder to bind model properties to the toolbar view.
 * @param <M> The {@link PropertyObservable} model.
 * @param <V> The view object that is changing.
 * @param <P> The property of the view that changed.
 */
public class PropertyModelChangeProcessor<M extends PropertyObservable<P>, V, P> {
    /**
     * A generic view binder that associates a view with a model.
     *
     * @param <M> The {@link PropertyObservable} model.
     * @param <V> The view object that is changing.
     * @param <P> The property of the view that changed.
     */
    public interface ViewBinder<M, V, P> {
        void bind(M model, V view, P propertyKey);
    }

    private final V mView;
    private final M mModel;
    private final ViewBinder<M, V, P> mViewBinder;

    private final PropertyObserver<P> mPropertyObserver = this::onPropertyChanged;

    /**
     * Construct a new PropertyModelChangeProcessor.
     * @param model The model containing the data to be bound.
     * @param view The view to which data will be bound.
     * @param viewBinder A class that binds the model to the view.
     * @param performInitialBind Whether all set model properties should be immediately bound.
     */
    private PropertyModelChangeProcessor(
            M model, V view, ViewBinder<M, V, P> viewBinder, boolean performInitialBind) {
        mModel = model;
        mView = view;
        mViewBinder = viewBinder;

        if (performInitialBind) {
            for (P property : model.getAllSetProperties()) {
                onPropertyChanged(model, property);
            }
        }

        model.addObserver(mPropertyObserver);
    }

    /**
     * Creates a new PropertyModelChangeProcessor observing the given {@code model}. All set model
     * properties will be bound.
     *
     * @param model The model containing the data to be bound.
     * @param view The view to which data will be bound.
     * @param viewBinder A class that binds the model to the view.
     */
    public static <M extends PropertyObservable<P>, V, P>
            PropertyModelChangeProcessor<M, V, P> create(
                    M model, V view, ViewBinder<M, V, P> viewBinder) {
        return create(model, view, viewBinder, true);
    }

    /**
     * Creates a new PropertyModelChangeProcessor observing the given {@code model}.
     *
     * @param model The model containing the data to be bound.
     * @param view The view to which data will be bound.
     * @param viewBinder A class that binds the model to the view.
     * @param performInitialBind Whether all set model properties should be immediately bound.
     */
    public static <M extends PropertyObservable<P>, V, P>
            PropertyModelChangeProcessor<M, V, P> create(
                    M model, V view, ViewBinder<M, V, P> viewBinder, boolean performInitialBind) {
        return new PropertyModelChangeProcessor<>(model, view, viewBinder, performInitialBind);
    }

    /** To be called when the model should no longer be observed. */
    public void destroy() {
        mModel.removeObserver(mPropertyObserver);
    }

    private void onPropertyChanged(PropertyObservable<P> source, P propertyKey) {
        assert source == mModel;

        // TODO(bauerb): Add support for batching updates.
        mViewBinder.bind(mModel, mView, propertyKey);
    }
}