001/*
002 * JDrupes Builder
003 * Copyright (C) 2025 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.core;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.List;
024import java.util.stream.Stream;
025
026/// A cached stream allows the user of a [Stream] to access it multiple
027/// times. The stream is evaluated on the first call to [#stream] and
028/// then cached.
029///
030/// As a convenience, the class supports combining the contents of multiple
031/// streams.
032///
033/// @param <T> the generic type
034///
035public class CachedStream<T> {
036
037    private List<Stream<T>> sources = new ArrayList<>();
038    private List<T> cache;
039
040    /// Instantiates a new cached stream.
041    ///
042    @SuppressWarnings("PMD.UnnecessaryConstructor")
043    public CachedStream() {
044        // Make javadoc happy.
045    }
046
047    /// Use all given streams as sources.
048    ///
049    /// @param sources the sources
050    ///
051    @SafeVarargs
052    public final void add(Stream<T>... sources) {
053        if (sources == null) {
054            throw new IllegalStateException(
055                "Cannot add sources after stream() has been called.");
056        }
057        this.sources.addAll(Arrays.asList(sources));
058    }
059
060    /// Provide the contents from the stream(s).
061    ///
062    /// @return the stream<? extends t>
063    ///
064    @SuppressWarnings("PMD.AvoidSynchronizedStatement")
065    public Stream<T> stream() {
066        synchronized (this) {
067            if (cache == null) {
068                cache = sources.stream().flatMap(s -> s).toList();
069                sources = null;
070            }
071            return cache.stream();
072        }
073    }
074
075    /// Create a cached stream from a single source stream.
076    ///
077    /// @param <T> the generic type
078    /// @param source the source
079    /// @return the cached stream
080    ///
081    @SuppressWarnings("PMD.ShortMethodName")
082    public static <T> CachedStream<T> of(Stream<T> source) {
083        var result = new CachedStream<T>();
084        result.add(source);
085        return result;
086    }
087}