ValsBasedComboModel.java

package swingtree;

import org.jspecify.annotations.Nullable;
import sprouts.Var;
import sprouts.Vals;
import sprouts.Viewables;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 *  A combo box model whose available options are modelled by a read only
 *  {@link Vals} property list. Note that the UI thread never accesses the
 *  property list itself, because it belongs to the application thread!
 *  Instead, this model maintains a UI thread owned snapshot of the items,
 *  which is refreshed through the change events of the property list.
 *  This is the same threading convention that the selection state
 *  of the {@link AbstractComboModel} follows.
 */
final class ValsBasedComboModel<E extends @Nullable Object> extends AbstractComboModel<E>
{
    private final Vals<E> _items;
    private final Viewables<E> _itemsView; // A strong reference keeps the (weakly parented) view and its change listeners alive.
    private volatile List<E> _itemsSnapshot; // The UI thread owned copy of the state of `_items`.

    ValsBasedComboModel( Vals<E> items ) {
        this(Var.ofNullable(_findCommonType(items), null), items);
    }

    ValsBasedComboModel( Var<E> var, Vals<E> items ) {
        super(var);
        _items         = Objects.requireNonNull(items);
        _itemsSnapshot = _snapshotOf(items.toList());
        _selectedIndex = _indexOf(_getSelectedItemSafely());
        _itemsView = _items.view();
        _itemsView.onChange( it -> {
            List<E> newItems = _snapshotOf(it.currentValues().toList());
            _publishToUIThread(() -> {
                _itemsSnapshot = newItems;
                updateSelectedIndex();
                fireListeners();
            });
        });
    }

    private static <E> List<E> _snapshotOf( List<E> items ) {
        return Collections.unmodifiableList(new ArrayList<>(items));
    }

    @Override public int getSize() { return _itemsSnapshot.size(); }

    @Override public @Nullable E getElementAt( int index ) {
        List<E> snapshot = _itemsSnapshot;
        if ( index < 0 || index >= snapshot.size() )
            return null;
        return snapshot.get(index);
    }

    @Override public AbstractComboModel<E> withVar( Var<E> newVar ) {
        return new ValsBasedComboModel<>(newVar, _items);
    }

    @Override
    protected void setAt( int index, @Nullable E element ) {
        /*
            Vals are immutable, so we can't modify them.
            So we'll just ignore it.
        */
    }
}