BuiltCells.java

package swingtree;

import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sprouts.Association;
import sprouts.Pair;

import javax.swing.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

final class BuiltCells<C extends JComponent, E> {

    private static final Logger log = LoggerFactory.getLogger(BuiltCells.class);

    private final Class<C> _componentType;
    private final Class<E> _elementType;
    private final Association<Class<?>, CellBuilder.CellView<C>> _rendererLookup;

    BuiltCells(
            Class<C> componentType,
            Class<E> elementType
    ) {
        this(
            componentType,
            elementType,
            new LinkedHashMap<>()
        );
    }

    BuiltCells(
        Class<C> componentType,
        Class<E> elementType,
        Map<Class<?>, CellBuilder.CellView<C>> rendererLookup
    ) {
        this(
            componentType,
            elementType,
            ((Association) Association.betweenLinked(Class.class, CellBuilder.CellView.class)).putAll(rendererLookup)
        );
    }

    BuiltCells(
        Class<C> componentType,
        Class<E> elementType,
        Association<Class<?>, CellBuilder.CellView<C>> rendererLookup
    ) {
        _componentType = componentType;
        _elementType = elementType;
        _rendererLookup = rendererLookup;
    }

    public BuiltCells<C,E> addRenderLookups(Association<Class<?>, CellBuilder.CellView<C>> additionalLookups) {
        Association<Class<?>, CellBuilder.CellView<C>> rendererLookup = _rendererLookup;
        for ( Pair<Class<?>, CellBuilder.CellView<C>> entry : additionalLookups.entrySet() ) {
            if ( !rendererLookup.containsKey(entry.first()) )
                rendererLookup = rendererLookup.put(entry.first(), entry.second());
            else {
                CellBuilder.CellView<C> existing = rendererLookup.get(entry.first()).get();
                existing._configurators.addAll(0, entry.second()._configurators);
            }
        }
        Optional<CellBuilder.CellView<C>> forObject = rendererLookup.get(Object.class);
        if ( forObject.isPresent() ) {
            rendererLookup = rendererLookup.remove(Object.class);
            rendererLookup = rendererLookup.put(Object.class, forObject.get());
            // The 'Object' type always needs to be last when rendering!
        }
        return new BuiltCells<>(_componentType, _elementType, rendererLookup);
    }

    public Class<C> componentType() {
        return _componentType;
    }

    public Class<E> elementType() {
        return _elementType;
    }

    public Association<Class<?>, CellBuilder.CellView<C>> rendererLookup() {
        return _rendererLookup;
    }

    BuiltCells<C,E> computeIfAbsent(Class<?> type, Supplier<CellBuilder.CellView<C>> cellViewSupplier) {
        if ( _rendererLookup.containsKey(type) ) {
            return this;
        }
        return new BuiltCells<>(
                _componentType,
                _elementType,
                _rendererLookup.put(type, cellViewSupplier.get())
            );
    }

    void checkTypeValidity( @Nullable Object encounteredValue ) {
        if ( encounteredValue != null ) {
            if ( !_elementType.isAssignableFrom(encounteredValue.getClass()) )
                log.debug(
                    "Encountered an unusual cell entry in component '{}'. " +
                    "Expected type '{}', but got '{}'.",
                    _componentType.getSimpleName(),
                    _elementType.getSimpleName(),
                    encounteredValue.getClass().getSimpleName()
                );
        }
    }

}