All Downloads are FREE. Search and download functionalities are using the official Maven repository.

griffon.javafx.collections.AbstractObservableStream Maven / Gradle / Ivy

/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2008-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package griffon.javafx.collections;

import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableLongValue;
import javafx.beans.value.ObservableValue;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;
import static javafx.beans.binding.Bindings.createBooleanBinding;
import static javafx.beans.binding.Bindings.createObjectBinding;

/**
 * @author Andres Almiray
 * @since 2.13.0
 */
@SuppressWarnings("unchecked")
abstract class AbstractObservableStream implements ObservableStream {
    protected static final String ERROR_PREDICATE_NULL = "Argument 'predicate' must not be null";
    protected static final String ERROR_MAPPER_NULL = "Argument 'mapper' must not be null";
    protected static final String ERROR_COMPARATOR_NULL = "Argument 'comparator' must not be null";
    protected static final String ERROR_OBSERVABLE_NULL = "Argument 'observable' must not be null";
    protected static final String ERROR_ACCUMULATOR = "Argument 'accumulator' must not be null";
    protected static final String ERROR_SUPPLIER_NULL = "Argument 'supplier' must not be null";
    protected static final String ERROR_COMBINER_NULL = "Argument 'combiner' must not be null";
    protected static final String ERROR_IDENTITY_NULL = "Argument 'identity' must not be null";

    protected final Observable observable;
    protected final List operations = new ArrayList<>();

    AbstractObservableStream(@Nonnull Observable observable, @Nonnull List operations) {
        this.observable = requireNonNull(observable, ERROR_OBSERVABLE_NULL);
        this.operations.addAll(operations);
    }

    @Nonnull
    protected abstract  ObservableStream createInstance(@Nonnull List operations);

    @Nonnull
    protected abstract Stream createStream();

    @Nonnull
    @Override
    public ObservableStream limit(final long maxSize) {
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.limit(maxSize);
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream limit(@Nonnull final ObservableLongValue maxSize) {
        requireNonNull(maxSize, ERROR_OBSERVABLE_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.limit(maxSize.get());
            }

            @Nullable
            @Override
            public Observable dependency() {
                return maxSize;
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream skip(final long n) {
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.skip(n);
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream skip(@Nonnull final ObservableLongValue n) {
        requireNonNull(n, ERROR_OBSERVABLE_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.skip(n.get());
            }

            @Nullable
            @Override
            public Observable dependency() {
                return n;
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream distinct() {
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.distinct();
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream sorted() {
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.sorted();
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream sorted(@Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.sorted(comparator);
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream sorted(@Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                Comparator c = comparator.getValue();
                requireNonNull(c, ERROR_COMPARATOR_NULL);
                return stream.sorted(c);
            }

            @Nullable
            @Override
            public Observable dependency() {
                return comparator;
            }
        }));
    }

    @Nonnull
    public ObservableStream filter(@Nonnull final Predicate predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.filter(predicate);
            }
        }));
    }

    @Nonnull
    @Override
    public  ObservableStream map(@Nonnull final Function mapper) {
        requireNonNull(mapper, ERROR_MAPPER_NULL);
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.map(mapper);
            }
        }));
    }

    @Nonnull
    @Override
    public  ObservableStream flatMap(@Nonnull final Function> mapper) {
        requireNonNull(mapper, ERROR_MAPPER_NULL);
        return createInstance(push(operations, new StreamOpAdapter() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                return stream.flatMap(mapper);
            }
        }));
    }

    @Nonnull
    @Override
    public ObservableStream filter(@Nonnull final ObservableValue> predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                Predicate p = predicate.getValue();
                requireNonNull(p, ERROR_PREDICATE_NULL);
                return stream.filter(p);
            }

            @Nullable
            @Override
            public Observable dependency() {
                return predicate;
            }
        }));
    }

    @Nonnull
    @Override
    public  ObservableStream map(@Nonnull final ObservableValue> mapper) {
        requireNonNull(mapper, ERROR_MAPPER_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                Function m = mapper.getValue();
                requireNonNull(m, ERROR_MAPPER_NULL);
                return stream.map(m);
            }

            @Nullable
            @Override
            public Observable dependency() {
                return mapper;
            }
        }));
    }

    @Nonnull
    @Override
    public  ObservableStream flatMap(@Nonnull final ObservableValue>> mapper) {
        requireNonNull(mapper, ERROR_MAPPER_NULL);
        return createInstance(push(operations, new StreamOp() {
            @Nonnull
            @Override
            public Stream apply(@Nonnull Stream stream) {
                Function> m = mapper.getValue();
                requireNonNull(m, ERROR_MAPPER_NULL);
                return stream.flatMap(m);
            }

            @Nullable
            @Override
            public Observable dependency() {
                return mapper;
            }
        }));
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nonnull final BinaryOperator accumulator) {
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> (T) stream().reduce(accumulator).orElse(null), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nullable final T defaultValue, @Nonnull final BinaryOperator accumulator) {
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> (T) stream().reduce(accumulator).orElse(defaultValue), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nonnull final Supplier supplier, @Nonnull final BinaryOperator accumulator) {
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> (T) stream().reduce(accumulator).orElseGet(supplier), dependencies());
    }

    @Nonnull
    @Override
    public  ObjectBinding reduce(@Nullable final U identity, @Nonnull final BiFunction accumulator, @Nonnull final BinaryOperator combiner) {
        requireNonNull(combiner, ERROR_COMBINER_NULL);
        return createObjectBinding(() -> (U) stream().reduce(identity, accumulator, combiner), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nonnull final ObservableValue> accumulator) {
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> {
            BinaryOperator a = accumulator.getValue();
            requireNonNull(a, ERROR_ACCUMULATOR);
            return (T) stream().reduce(a).orElse(null);
        }, dependencies(accumulator));
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nullable final T defaultValue, @Nonnull final ObservableValue> accumulator) {
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> {
            BinaryOperator a = accumulator.getValue();
            requireNonNull(a, ERROR_ACCUMULATOR);
            return (T) stream().reduce(a).orElse(defaultValue);
        }, dependencies(accumulator));
    }

    @Nonnull
    @Override
    public ObjectBinding reduce(@Nonnull final Supplier supplier, @Nonnull final ObservableValue> accumulator) {
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        return createObjectBinding(() -> {
            BinaryOperator a = accumulator.getValue();
            requireNonNull(a, ERROR_ACCUMULATOR);
            return (T) stream().reduce(a).orElseGet(supplier);
        }, dependencies(accumulator));
    }

    @Nonnull
    @Override
    public  ObjectBinding reduce(@Nonnull final ObservableValue identity, @Nonnull final ObservableValue> accumulator, @Nonnull final ObservableValue> combiner) {
        requireNonNull(identity, ERROR_IDENTITY_NULL);
        requireNonNull(accumulator, ERROR_ACCUMULATOR);
        requireNonNull(combiner, ERROR_COMBINER_NULL);
        return createObjectBinding(() -> {
            U i = identity.getValue();
            requireNonNull(i, ERROR_IDENTITY_NULL);
            BiFunction a = accumulator.getValue();
            requireNonNull(a, ERROR_ACCUMULATOR);
            BinaryOperator c = combiner.getValue();
            requireNonNull(c, ERROR_COMBINER_NULL);
            return (U) stream().reduce(i, a, c);
        }, dependencies(identity, accumulator, combiner));
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> (T) stream().min(comparator).orElse(null), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> (T) stream().max(comparator).orElse(null), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nullable final T defaultValue, @Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> (T) stream().min(comparator).orElse(defaultValue), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nullable final T defaultValue, @Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> (T) stream().max(comparator).orElse(defaultValue), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nonnull final Supplier supplier, @Nonnull final Comparator comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        return createObjectBinding(() -> (T) stream().min(comparator).orElseGet(supplier), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nonnull final Supplier supplier, @Nonnull final Comparator comparator) {
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> (T) stream().max(comparator).orElseGet(supplier), dependencies());
    }

    @Nonnull
    @Override
    public BooleanBinding anyMatch(@Nonnull final Predicate predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> stream().anyMatch(predicate), dependencies());
    }

    @Nonnull
    @Override
    public BooleanBinding allMatch(@Nonnull final Predicate predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> stream().allMatch(predicate), dependencies());
    }

    @Nonnull
    @Override
    public BooleanBinding noneMatch(@Nonnull final Predicate predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> stream().noneMatch(predicate), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().min(c).orElse(null);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().max(c).orElse(null);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nullable final T defaultValue, @Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().min(c).orElse(defaultValue);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nullable final T defaultValue, @Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().max(c).orElse(defaultValue);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public ObjectBinding min(@Nonnull final Supplier supplier, @Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().min(c).orElseGet(supplier);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public ObjectBinding max(@Nonnull final Supplier supplier, @Nonnull final ObservableValue> comparator) {
        requireNonNull(comparator, ERROR_COMPARATOR_NULL);
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        return createObjectBinding(() -> {
            Comparator c = comparator.getValue();
            requireNonNull(c, ERROR_COMPARATOR_NULL);
            return (T) stream().max(c).orElseGet(supplier);
        }, dependencies(comparator));
    }

    @Nonnull
    @Override
    public BooleanBinding anyMatch(@Nonnull final ObservableValue> predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> {
            Predicate p = predicate.getValue();
            requireNonNull(p, ERROR_PREDICATE_NULL);
            return stream().anyMatch(p);
        }, dependencies(predicate));
    }

    @Nonnull
    @Override
    public BooleanBinding allMatch(@Nonnull final ObservableValue> predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> {
            Predicate p = predicate.getValue();
            requireNonNull(p, ERROR_PREDICATE_NULL);
            return stream().allMatch(p);
        }, dependencies(predicate));
    }

    @Nonnull
    @Override
    public BooleanBinding noneMatch(@Nonnull final ObservableValue> predicate) {
        requireNonNull(predicate, ERROR_PREDICATE_NULL);
        return createBooleanBinding(() -> {
            Predicate p = predicate.getValue();
            requireNonNull(p, ERROR_PREDICATE_NULL);
            return stream().noneMatch(p);
        }, dependencies(predicate));
    }

    @Nonnull
    @Override
    public ObjectBinding findFirst() {
        return createObjectBinding(() -> (T) stream().findFirst().orElse(null), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding findFirst(@Nullable final T defaultValue) {
        return createObjectBinding(() -> (T) stream().findFirst().orElse(defaultValue), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding findFirst(@Nonnull final Supplier supplier) {
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        return createObjectBinding(() -> (T) stream().findFirst().orElseGet(supplier), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding findAny() {
        return createObjectBinding(() -> (T) stream().findAny().orElse(null), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding findAny(@Nullable final T defaultValue) {
        return createObjectBinding(() -> (T) stream().findAny().orElse(defaultValue), dependencies());
    }

    @Nonnull
    @Override
    public ObjectBinding findAny(@Nonnull final Supplier supplier) {
        requireNonNull(supplier, ERROR_SUPPLIER_NULL);
        return createObjectBinding(() -> (T) stream().findAny().orElseGet(supplier), dependencies());
    }

    @Nonnull
    private Stream stream() {
        Stream stream = createStream();

        for (StreamOp op : operations) {
            stream = op.apply(stream);
        }

        return stream;
    }

    @Nonnull
    private Observable[] dependencies(Observable... deps) {
        List dependencies = new ArrayList<>();
        dependencies.add(observable);
        if (deps != null) {
            Collections.addAll(dependencies, deps);
        }

        for (StreamOp op : operations) {
            Observable dependency = op.dependency();
            if (dependency != null) {
                dependencies.add(dependency);
            }
        }

        return dependencies.toArray(new Observable[dependencies.size()]);
    }

    private static List push(List operations, StreamOp op) {
        List ops = new ArrayList<>(operations);
        ops.add(op);
        return ops;
    }

    interface StreamOp {
        @Nonnull
        Stream apply(@Nonnull Stream stream);

        @Nullable
        Observable dependency();
    }

    static class StreamOpAdapter implements StreamOp {
        @Nonnull
        @Override
        public Stream apply(@Nonnull Stream stream) {
            return stream;
        }

        @Nullable
        @Override
        public Observable dependency() {
            return null;
        }
    }
}