Sprouts.java
package sprouts.impl;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import sprouts.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;
/**
* Exposes an API for configuring the {@link SproutsFactory},
* which serves implementations of the various property types in the Sprouts library,
* like {@link Event}, {@link Val}, {@link Var}, {@link Vals} and {@link Vars}.
* The methods implemented here are used by the various factory methods of the sprouts API like
* {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...<br>
* <b>So technically speaking, this is a configurable singleton, so be careful when using it
* as it effectively maintains global + mutable state!</b>
*/
public final class Sprouts implements SproutsFactory
{
private static final Logger log = org.slf4j.LoggerFactory.getLogger(Sprouts.class);
private static final Pattern DEFAULT_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]*");
private static SproutsFactory FACTORY = new Sprouts();
/**
* A {@link SproutsFactory} is used by the various factory methods of this API like
* {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...
* to create instances of these properties. <br>
* You can plug in your own factory implementation through the {@link #setFactory(SproutsFactory)} method,
* where you can then serve your own implementations of the various property types in the Sprouts library.
*
* @return The default factory for creating instances of the various property types in the Sprouts library.
*/
public static SproutsFactory factory() { return FACTORY; }
/**
* Sets the factory to be used by the various factory methods of this API like
* {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...
* to create instances of these properties. <br>
* You can use a custom {@link SproutsFactory} to instantiate and serve your own
* implementations of the various property types in the Sprouts library. <br>
* <p><b>
* WARNING: This is a global + mutable state, so be careful when using it <br>
* as it will have global side effects on the various factory methods of this API.
* </b>
*
* @param factory The factory to be used by the various factory methods of this API.
* @throws NullPointerException if the factory is null.
*/
public static void setFactory( SproutsFactory factory ) {
Objects.requireNonNull(factory);
FACTORY = factory;
}
private Sprouts() {}
@Override
public Event event() {
return new Event() {
private final List<Observer> observers = new ArrayList<>();
@Override public void fire() {
observers.forEach( observer -> {
try {
observer.invoke();
} catch (Exception e) {
log.error("Error invoking observer!", e);
}
});
}
@Override
public Event subscribe( Observer observer) {
observers.add(observer);
return this;
}
@Override
public Observable unsubscribe( Subscriber subscriber) {
if ( subscriber instanceof Observer )
observers.remove( (Observer) subscriber );
return this;
}
@Override public void unsubscribeAll() { observers.clear(); }
};
}
@Override
public Event eventOf( Event.Executor executor ) {
return new Event() {
private final List<Observer> observers = new ArrayList<>();
@Override
public void fire() {
executor.execute( () -> {
observers.forEach( observer -> {
try {
observer.invoke();
} catch (Exception e) {
log.error("Error invoking observer!", e);
}
});
});
}
@Override
public Event subscribe(Observer observer) {
observers.add(observer);
return this;
}
@Override
public Observable unsubscribe( Subscriber subscriber ) {
if ( subscriber instanceof Observer )
observers.remove( (Observer) subscriber );
return this;
}
@Override public void unsubscribeAll() { observers.clear(); }
};
}
@Override public <T> Val<@Nullable T> valOfNullable( Class<T> type, @Nullable T item ) {
return Property.ofNullable( true, type, item );
}
@Override public <T> Val<@Nullable T> valOfNull( Class<T> type ) {
return Property.ofNullable( true, type, null );
}
@Override public <T> Val<T> valOf( T item ) {
return Property.of( true, item );
}
@Override public <T> Val<T> valOf( Val<T> toBeCopied ) {
Objects.requireNonNull(toBeCopied);
return Val.of( toBeCopied.get() ).withId( toBeCopied.id() );
}
@Override public <T> Val<@Nullable T> valOfNullable( Val<@Nullable T> toBeCopied ) {
Objects.requireNonNull(toBeCopied);
return Val.ofNullable( toBeCopied.type(), toBeCopied.orElseNull() ).withId( toBeCopied.id() );
}
@Override
public <T> Viewable<T> viewOf(Val<T> source) {
Objects.requireNonNull(source);
return PropertyView.of( source );
}
@Override
public <T extends @Nullable Object, U extends @Nullable Object> Viewable<@NonNull T> viewOf(Val<T> first, Val<U> second, BiFunction<T, U, @NonNull T> combiner ) {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
Objects.requireNonNull(combiner);
return PropertyView.viewOf( first, second, combiner );
}
@Override
public <T extends @Nullable Object, U extends @Nullable Object> Viewable<@Nullable T> viewOfNullable(Val<T> first, Val<U> second, BiFunction<T, U, @Nullable T> combiner ) {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
Objects.requireNonNull(combiner);
return PropertyView.viewOfNullable( first, second, combiner );
}
@Override
public <T extends @Nullable Object, U extends @Nullable Object, R> Viewable<R> viewOf(Class<R> type, Val<T> first, Val<U> second, BiFunction<T, U, R> combiner) {
Objects.requireNonNull(type);
Objects.requireNonNull(first);
Objects.requireNonNull(second);
Objects.requireNonNull(combiner);
return PropertyView.viewOf( type, first, second, combiner );
}
@Override
public <T extends @Nullable Object, U extends @Nullable Object, R> Viewable<@Nullable R> viewOfNullable(Class<R> type, Val<T> first, Val<U> second, BiFunction<T, U, @Nullable R> combiner) {
Objects.requireNonNull(type);
Objects.requireNonNull(first);
Objects.requireNonNull(second);
Objects.requireNonNull(combiner);
return PropertyView.viewOfNullable( type, first, second, combiner );
}
@Override
public <T> Viewables<T> viewOf(Vals<T> source) {
Objects.requireNonNull(source);
return Viewables.cast(source); // TODO: Implement
}
@Override
public <T, U> Viewable<T> viewOf(Class<T> type, Val<U> source, Function<U, T> mapper) {
Objects.requireNonNull(type);
Objects.requireNonNull(source);
Objects.requireNonNull(mapper);
return PropertyView.of(type, source, mapper);
}
@Override
public <T, U> Viewables<U> viewOf(U nullObject, U errorObject, Vals<T> source, Function<T, @Nullable U> mapper) {
Objects.requireNonNull(nullObject);
Objects.requireNonNull(errorObject);
Objects.requireNonNull(source);
Objects.requireNonNull(mapper);
return PropertyListView.of(nullObject, errorObject, source, mapper);
}
@Override
public <T, U> Viewable<U> viewOf(U nullObject, U errorObject, Val<T> source, Function<T, @Nullable U> mapper) {
Objects.requireNonNull(nullObject);
Objects.requireNonNull(errorObject);
Objects.requireNonNull(source);
Objects.requireNonNull(mapper);
return PropertyView.of(nullObject, errorObject, source, mapper);
}
@Override
public <T, U> Viewable<@Nullable U> viewOfNullable(Class<U> type, Val<T> source, Function<T, @Nullable U> mapper) {
Objects.requireNonNull(type);
Objects.requireNonNull(source);
Objects.requireNonNull(mapper);
return PropertyView.ofNullable(type, source, mapper);
}
@Override
public <T, B> Var<B> lensOf(Var<T> source, Function<T, B> getter, BiFunction<T, B, T> wither) {
B initialValue = getter.apply(source.orElseNull());
Class<B> type = Util.expectedClassFromItem(initialValue);
return new PropertyLens<>(
type,
Sprouts.factory().defaultId(),
false,//does not allow null
initialValue, //may NOT be null
source,
getter,
wither,
null
);
}
@Override
public <T, B> Var<B> lensOf(Var<T> source, B nullObject, Function<T, B> getter, BiFunction<T, B, T> wither) {
Objects.requireNonNull(nullObject, "Null object must not be null");
Objects.requireNonNull(getter, "Getter must not be null");
Objects.requireNonNull(wither, "Wither must not be null");
return PropertyLens.of(source, nullObject, getter, wither);
}
@Override
public <T, B> Var<B> lensOfNullable(Class<B> type, Var<T> source, Function<T, B> getter, BiFunction<T, B, T> wither) {
Objects.requireNonNull(type, "Type must not be null");
Objects.requireNonNull(getter, "Getter must not be null");
Objects.requireNonNull(wither, "Wither must not be null");
return PropertyLens.ofNullable(type, source, getter, wither);
}
@Override public <T> Var<T> varOfNullable(Class<T> type, @Nullable T item ) {
return Property.ofNullable( false, type, item );
}
@Override public <T> Var<T> varOfNull(Class<T> type ) {
return Property.ofNullable( false, type, null );
}
@Override public <T> Var<T> varOf(T item ) {
return Property.of( false, item );
}
@Override public <T, V extends T> Var<T> varOf(Class<T> type, V item ) {
return Property.of( false, type, item );
}
@Override public <T> Vals<T> valsOf(Class<T> type ) {
return PropertyList.of( true, type );
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<T> valsOf(Class<T> type, Val<T>... vars ) {
return PropertyList.of( true, type, (Var<T>[]) vars );
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<T> valsOf(Val<T> first, Val<T>... rest ) {
Var<T>[] vars = new Var[rest.length];
System.arraycopy(rest, 0, vars, 0, rest.length);
return PropertyList.of( true, (Var<T>) first, vars );
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<T> valsOf( T first, T... rest ) { return PropertyList.of( true, first, rest); }
@SuppressWarnings("unchecked")
@Override public <T> Vals<T> valsOf( Class<T> type, T... items ) { return PropertyList.of( true, type, items ); }
@Override public <T> Vals<T> valsOf( Class<T> type, Iterable<Val<T>> properties ) {
return PropertyList.of( true, type, (Iterable) properties );
}
@Override public <T> Vals<T> valsOf( Class<T> type, Vals<T> vals ) {
T[] values = (T[]) vals.stream().toArray(Object[]::new);
return PropertyList.of(true, type, values);
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type, Val<@Nullable T>... vals ) {
Var<T>[] vars = new Var[vals.length];
System.arraycopy(vals, 0, vars, 0, vals.length);
return PropertyList.ofNullable( true, type, vars );
}
@Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type ) {
return PropertyList.ofNullable( true, type );
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type, @Nullable T... items ) {
return PropertyList.ofNullable( true, type, items );
}
@SuppressWarnings("unchecked")
@Override public <T> Vals<@Nullable T> valsOfNullable( Val<@Nullable T> first, Val<@Nullable T>... rest ) {
Var<T>[] vars = new Var[rest.length];
System.arraycopy(rest, 0, vars, 0, rest.length);
return PropertyList.ofNullable( true, (Var<T>) first, vars );
}
@Override
public <T> Vals<@Nullable T> valsOfNullable(Class<T> type, Vals<@Nullable T> vals) {
T[] values = (T[]) vals.stream().toArray(Object[]::new);
return valsOfNullable(type, values);
}
@Override
public <T> Vars<T> varsOfNullable(Class<T> type, Iterable<Var<T>> vars) {
Var<@Nullable T>[] varsArray = (Var<@Nullable T>[]) StreamSupport.stream(vars.spliterator(), false).toArray(Var[]::new);
return varsOfNullable(type, varsArray);
}
@SuppressWarnings("unchecked")
@Override public <T> Vars<T> varsOf( Class<T> type, Var<T>... vars ) { return PropertyList.of( false, type, vars ); }
@Override public <T> Vars<T> varsOf( Class<T> type ) { return PropertyList.of( false, type ); }
@SuppressWarnings("unchecked")
@Override public <T> Vars<T> varsOf( Var<T> first, Var<T>... rest ) { return PropertyList.of( false, first, rest ); }
@SuppressWarnings("unchecked")
@Override public <T> Vars<T> varsOf( T first, T... rest ) { return PropertyList.of( false, first, rest ); }
@SuppressWarnings("unchecked")
@Override public <T> Vars<T> varsOf( Class<T> type, T... items ) { return PropertyList.of( false, type, items ); }
@Override public <T> Vars<T> varsOf( Class<T> type, Iterable<Var<T>> vars ) { return PropertyList.of( false, type, vars ); }
@SuppressWarnings("unchecked")
@Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type, Var<@Nullable T>... vars ) {
return PropertyList.ofNullable( false, type, vars );
}
@Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type ) { return PropertyList.ofNullable( false, type ); }
@SuppressWarnings("unchecked")
@Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type, @Nullable T... values ) {
return PropertyList.ofNullable( false, type, values );
}
@SuppressWarnings("unchecked")
@Override public <T> Vars<@Nullable T> varsOfNullable( Var<@Nullable T> first, Var<@Nullable T>... rest ) {
return PropertyList.ofNullable( false, first, rest );
}
@Override public <V> Result<V> resultOf( Class<V> type ) {
Objects.requireNonNull(type);
return new ResultImpl<>(type, Collections.emptyList(), null);
}
@Override public <V> Result<V> resultOf( V value ) {
Objects.requireNonNull(value);
return resultOf(value, Collections.emptyList());
}
@Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value ) {
Objects.requireNonNull(type);
return resultOf(type, value, Collections.emptyList());
}
@Override public <V> Result<V> resultOf( V value, List<Problem> problems ) {
Objects.requireNonNull(value);
problems = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(problems)));
Class<V> itemType = Util.expectedClassFromItem(value);
return new ResultImpl<>(itemType, problems, value);
}
@Override public <V> Result<V> resultOf( Class<V> type, List<Problem> problems ) {
Objects.requireNonNull(type);
problems = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(problems)));
return new ResultImpl<>(type, problems, null);
}
@Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value, List<Problem> problems ) {
Objects.requireNonNull(type);
problems = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(problems)));
return new ResultImpl<>(type, problems, value);
}
@Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value, Problem problem ) {
Objects.requireNonNull(type);
Objects.requireNonNull(problem);
return new ResultImpl<>(type, Collections.singletonList(problem), value);
}
@Override public <V> Result<V> resultOf( Class<V> type, Problem problem ) {
Objects.requireNonNull(type);
Objects.requireNonNull(problem);
return new ResultImpl<>(type, Collections.singletonList(problem), null);
}
@Override public <V> Result<List<V>> resultOfList( Class<V> type, Problem problem ) {
Objects.requireNonNull(type);
Objects.requireNonNull(problem);
return (Result<List<V>>) (Result) new ResultImpl<>(List.class, Collections.singletonList(problem), null);
}
@Override public <V> Result<List<V>> resultOfList( Class<V> type, List<V> list ) {
Objects.requireNonNull(type);
Objects.requireNonNull(list);
// We check the types of the list elements are of the correct type.
boolean matches = list.stream().filter(Objects::nonNull).allMatch(e -> type.isAssignableFrom(e.getClass()));
if ( !matches )
throw new IllegalArgumentException("List elements must be of type " + type.getName());
return (Result<List<V>>) (Result) new ResultImpl<>(List.class, Collections.emptyList(), list);
}
@Override public <V> Result<List<V>> resultOfList( Class<V> type, List<V> list, List<Problem> problems ) {
Objects.requireNonNull(type);
Objects.requireNonNull(list);
boolean matches = list.stream().filter(Objects::nonNull).allMatch(e -> type.isAssignableFrom(e.getClass()));
if ( !matches )
throw new IllegalArgumentException("List elements must be of type " + type.getName());
problems = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(problems)));
return (Result<List<V>>) (Result) new ResultImpl<>(List.class, problems, list);
}
@Override
public <V> Result<V> resultOfTry( Class<V> type, Supplier<V> supplier ) {
Objects.requireNonNull(type);
Objects.requireNonNull(supplier);
try {
return resultOf(type, supplier.get());
} catch (Exception e) {
return resultOf(type, Problem.of(e));
}
}
@Override
public <O, D> WeakAction<O, D> actionOfWeak( O owner, BiConsumer<O, D> action ) {
return new WeakActionImpl<>(owner, action);
}
@Override
public String defaultId() {
return "";
}
@Override
public Pattern idPattern() {
return DEFAULT_ID_PATTERN;
}
@Override
public Channel defaultChannel() {
return From.VIEW_MODEL;
}
@Override
public Channel defaultObservableChannel() {
return From.ALL;
}
}