001/*
002 * MIT License
003 *
004 * Copyright (c) 2022 Michael Cowan
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024
025package io.blt.util.stream;
026
027import java.util.Optional;
028import java.util.Set;
029import java.util.function.BiConsumer;
030import java.util.function.BinaryOperator;
031import java.util.function.Function;
032import java.util.function.Supplier;
033import java.util.stream.Collector;
034
035import static java.util.Collections.emptySet;
036import static java.util.Objects.nonNull;
037
038/**
039 * Implementations of {@link Collector} that reduce to exactly one or zero elements.
040 * e.g.,
041 * <pre>{@code
042 * var maybeActiveStep = sequentialSteps.stream()
043 *         .filter(SequentialStep::isActive)
044 *         .collect(SingletonCollectors.toOptional());
045 * }</pre>
046 * <p>
047 * If more than one element is present, then {@code IllegalArgumentException} is thrown.
048 */
049public final class SingletonCollectors {
050
051    private SingletonCollectors() {
052        throw new IllegalAccessError("Utility class should be accessed statically and never constructed");
053    }
054
055    /**
056     * Returns a {@code Collector} that accumulates the only element, if any, into an {@code Optional}.
057     *
058     * @param <T> the type of the input elements
059     * @return a {@code Collector} that accumulates the only element, if any, into an {@code Optional}.
060     * @throws IllegalArgumentException if more than one element is present
061     */
062    public static <T> SingletonCollector<T, Optional<T>> toOptional() {
063        return new SingletonCollector<>(Container::getOptional);
064    }
065
066    /**
067     * Returns a {@code Collector} that accumulates the only element, if any, into a nullable {@code Object}.
068     *
069     * @param <T> the type of the input elements
070     * @return a {@code Collector} that accumulates the only element, if any, into a nullable {@code Object}.
071     * @throws IllegalArgumentException if more than one element is present
072     */
073    public static <T> SingletonCollector<T, T> toNullable() {
074        return new SingletonCollector<>(Container::getValue);
075    }
076
077    public static final class SingletonCollector<T, R> implements Collector<T, Container<T>, R> {
078
079        private final Function<Container<T>, R> finisher;
080
081        private SingletonCollector(Function<Container<T>, R> finisher) {
082            this.finisher = finisher;
083        }
084
085        @Override
086        public Supplier<Container<T>> supplier() {
087            return Container::new;
088        }
089
090        @Override
091        public BiConsumer<Container<T>, T> accumulator() {
092            return Container::setValue;
093        }
094
095        @Override
096        public BinaryOperator<Container<T>> combiner() {
097            return null; // Only used in a parallel stream, which this collector doesn't support
098        }
099
100        @Override
101        public Function<Container<T>, R> finisher() {
102            return finisher;
103        }
104
105        @Override
106        public Set<Characteristics> characteristics() {
107            return emptySet();
108        }
109    }
110
111    public static final class Container<T> {
112
113        private T value;
114
115        private Container() {}
116
117        public T getValue() {
118            return value;
119        }
120
121        public void setValue(T value) {
122            if (nonNull(this.value)) {
123                throw new IllegalStateException("Expected stream to contain exactly 0 or 1 elements");
124            }
125            this.value = value;
126        }
127
128        public Optional<T> getOptional() {
129            return Optional.ofNullable(getValue());
130        }
131    }
132
133}