ParamLensCore.java

package sprouts.impl;

import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import sprouts.Channel;
import sprouts.Val;
import sprouts.Var;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;

/**
 * A {@link LensCore} backed by a mutable {@link Var} source and a read-only
 * {@link Val} parameter. The parameter shapes the projection but is never
 * written back to; writes go to the source alone.
 * <p>
 * This is the optics-theoretic <i>parameterized lens</i> (also called an
 * <i>indexed lens</i> or <i>dependent lens</i>): the projection function
 * depends on an external read-only value.
 *
 * @param <P> The type of the read-only parameter.
 * @param <A> The item type of the mutable source property.
 * @param <T> The item type of this lens property (the projected value).
 */
final class ParamLensCore<P extends @Nullable Object, A extends @Nullable Object, T extends @Nullable Object>
        implements LensCore<T>
{
    private static final Logger log = org.slf4j.LoggerFactory.getLogger(ParamLensCore.class);

    private final Val<P>              _parameter;
    private final Var<A>              _source;
    private final BiFunction<P, A, T> _getter;
    private final BiFunction<T, P, A> _setter;

    ParamLensCore(
            Val<P>              parameter,
            Var<A>              source,
            BiFunction<P, A, T> getter,
            BiFunction<T, P, A> setter
    ) {
        _parameter = parameter;
        _source    = source;
        _getter    = getter;
        _setter    = setter;
    }

    @Override
    public @Nullable T fetchFromSources(@Nullable T lastKnownItem) {
        T fetchedValue = lastKnownItem;
        try {
            fetchedValue = _getter.apply(
                    Util.fakeNonNull(_parameter.orElseNull()),
                    Util.fakeNonNull(_source.orElseNull())
            );
        } catch ( Exception e ) {
            Util.sneakyThrowExceptionIfFatal(e);
            Util._logError(log,
                    "Failed to fetch item for parameterized property lens from source " +
                    "property {} (with item type '{}') and parameter {} (with item type '{}') " +
                    "using the current getter function.",
                    _source.id().isEmpty()    ? "?" : "'" + _source.id()    + "'",
                    _source.type(),
                    _parameter.id().isEmpty() ? "?" : "'" + _parameter.id() + "'",
                    _parameter.type(),
                    e
            );
        }
        return fetchedValue;
    }

    @Override
    public void writeToSources(Channel channel, @Nullable T newItem) {
        A newSourceItem;
        try {
            newSourceItem = _setter.apply(
                    Util.fakeNonNull(newItem),
                    Util.fakeNonNull(_parameter.orElseNull())
            );
        } catch ( Exception e ) {
            Util.sneakyThrowExceptionIfFatal(e);
            Util._logError(log,
                    "Parameterized property lens failed to invert the new projected value " +
                    "back into a source value using the setter function.", e
            );
            return;
        }
        try {
            _source.set(channel, newSourceItem);
        } catch ( Exception e ) {
            Util.sneakyThrowExceptionIfFatal(e);
            Util._logError(log,
                    "Parameterized property lens failed to write the inverted value into " +
                    "the source property {} (with item type '{}').",
                    _source.id().isEmpty() ? "?" : "'" + _source.id() + "'",
                    _source.type(), e
            );
        }
    }

    @Override
    public Iterable<? extends Val<?>> sources() {
        return Arrays.asList(_source, _parameter);
    }

    @Override
    public boolean shouldSuppressSourceCallback() {
        return false;
    }

    @Override
    public String coreName() {
        return "ParamLens";
    }

    @Override
    public LensCore<T> newInstance() {
        return new ParamLensCore<>(_parameter, _source, _getter, _setter);
    }
}