ModelToViewConverter.java

package swingtree;

import org.jspecify.annotations.Nullable;
import sprouts.HasId;
import sprouts.Var;
import swingtree.api.mvvm.ViewSupplier;
import swingtree.components.JScrollPanels;

import javax.swing.*;
import java.awt.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.function.BiFunction;

final class ModelToViewConverter<M> implements ViewSupplier<M> {

    static <M, C extends JComponent> ModelToViewConverter<M> of(
        C parent,
        ViewSupplier<M> viewSupplier,
        BiFunction<M, Exception, JComponent> errorViewCreator
    ) {
        return new ModelToViewConverter<>(parent, viewSupplier, errorViewCreator);
    }

    private static final Object UNIQUE_VIEW_CACHE_KEY = UUID.randomUUID();

    private final WeakReference<JComponent>            parentRef;
    private final ViewSupplier<M>                      viewCreator;
    private final BiFunction<M, Exception, JComponent> errorViewCreator;

    private final List<JComponent> childComponents = new ArrayList<>();


    ModelToViewConverter(
        JComponent parent,
        ViewSupplier<M> viewCreator,
        BiFunction<M, Exception, JComponent> errorViewCreator
    ) {
        this.parentRef        = new WeakReference<>(Objects.requireNonNull(parent));
        this.viewCreator      = Objects.requireNonNull(viewCreator);
        this.errorViewCreator = Objects.requireNonNull(errorViewCreator);
    }

    void rememberCurrentViewsForReuse() {
        JComponent parent = _subViewParent();
        if ( parent != null ) {
            for ( int vi = 0; vi < InternalUtil._actualComponentCountFrom(parent); vi++ ) {
                Component child = InternalUtil._actualGetComponentAt(vi, parent);
                if ( child instanceof JComponent ) {
                    childComponents.add((JComponent) child);
                }
            }
        }
    }

    void clearCurrentViews() {
        childComponents.clear();
    }

    @Override
    public UIForAnySwing<?, ?> createViewFor(M viewModel) throws Exception {
        return UI.of(_createViewFor(viewModel));
    }

    private JComponent _createViewFor(M model) {
        try {
            JComponent existingView = _findCachedViewIn(model);
            if ( existingView != null )
                return existingView;
            else {
                UIForAnySwing<?,?> newView = viewCreator.createViewFor(model);
                Objects.requireNonNull(newView);
                JComponent viewComponent = newView.get((Class) newView.getType());
                if ( model instanceof UIForAnySwing.ViewHandle ) {
                    UIForAnySwing.ViewHandle handle = (UIForAnySwing.ViewHandle) model;
                    handle.setChild(viewComponent);
                }
                viewComponent.putClientProperty(UNIQUE_VIEW_CACHE_KEY, _idFrom(model));
                return viewComponent;
            }
        } catch (Exception e) {
            return errorViewCreator.apply(model, e);
        }
    }

    private @Nullable JComponent _subViewParent() {
        JComponent parent = this.parentRef.get();
        if ( parent instanceof JScrollPanels ) {
            JScrollPanels panels = (JScrollPanels) parent;
            parent = panels.getContentPanel();
        }
        return parent;
    }

    private @Nullable JComponent _findCachedViewIn(M model) throws Exception {
        JComponent parent = _subViewParent();
        if ( parent != null ) {
            Object id = _idFrom(model);
            for ( JComponent existingSubView : this.childComponents ) {
                if ( existingSubView instanceof JScrollPanels.EntryPanel ) {
                    JScrollPanels.EntryPanel entryPanel = (JScrollPanels.EntryPanel) existingSubView;
                    Component[] components = InternalUtil._actualComponentsFrom(entryPanel);
                    if ( components.length == 0 || !(components[0] instanceof JComponent) )
                        continue;
                    JComponent actualView = (JComponent) components[0];
                    Object foundId = actualView.getClientProperty(UNIQUE_VIEW_CACHE_KEY);
                    if ( Objects.equals(id, foundId) ) {
                        actualView.putClientProperty(UNIQUE_VIEW_CACHE_KEY, id);
                        return (JComponent) InternalUtil._actualGetComponentAt(0, existingSubView);
                    }
                } else {
                    Object foundId = existingSubView.getClientProperty(UNIQUE_VIEW_CACHE_KEY);
                    if ( Objects.equals(id, foundId) ) {
                        existingSubView.putClientProperty(UNIQUE_VIEW_CACHE_KEY, id);
                        return existingSubView;
                    }
                }
            }
        }
        return null;
    }

    private @Nullable Object _idFrom( Object model ) {
        if (model instanceof Var)
            model = ((Var<?>) model).orElseNull();
        if ( model instanceof UIForAnySwing.ViewHandle )
            model = ((UIForAnySwing.ViewHandle<?>) model).property().orElseNull();

        Object id = model;
        if ( model instanceof HasId ) {
            HasId<?> idModel = (HasId<?>) model;
            id = idModel.id();
        }
        return id;
    }

}