de.team33.patterns.serial.charon.Series Maven / Gradle / Ivy
Show all versions of serial-charon Show documentation
package de.team33.patterns.serial.charon;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Abstracts a series of elements of a certain type and as such represents an alternative view of a {@link Collection}.
*
* In this view, a series essentially consists of two components:
*
* - The first element ("head", an element that may be missing)
* - The rest of the sequence ("tail", again a sequence)
*
*
* If the first component ("head") is missing, the sequence is empty.
* Then the rest of the sequence ("tail") is also empty.
*
* A sequence according to this definition is immutable and cannot contain null elements.
* In addition to the components mentioned above, it has a certain size (number of items contained),
* can be streamed and presented as a "normal" {@link List}.
*
* @param The element type.
*/
public abstract class Series {
/**
* Returns an empty {@link Series}.
*/
@SuppressWarnings("unchecked")
public static Series empty() {
return Empty.INSTANCE;
}
/**
* Returns a {@link Series} composed of the given elements in the given order.
*/
@SuppressWarnings("OverloadedVarargsMethod")
@SafeVarargs
public static Series of(final E... elements) {
return of(Arrays.asList(elements));
}
/**
* Returns a {@link Series} composed of the given {@link Streamable}'s elements in the given order.
*/
public static Series of(final Streamable extends E> origin) {
return of(origin.stream().collect(Collectors.toList()));
}
/**
* Returns a {@link Series} composed of the given {@link Iterable}'s elements in the given order.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Series of(final Iterable extends E> origin) {
if (origin instanceof Collection) {
return of((Collection)origin);
} else {
final Streamable streamable = () -> StreamSupport.stream(origin.spliterator(), false);
return of(streamable);
}
}
/**
* Returns a {@link Series} composed of the given {@link Collection}'s elements in the given order.
*/
public static Series of(final Collection extends E> origin) {
return seriesOf(new ArrayList<>(origin), 0);
}
private static Series seriesOf(final List backing, final int headIndex) {
assert 0 <= headIndex;
return (headIndex < backing.size()) ? new Charged<>(backing, headIndex) : empty();
}
/**
* Returns {@code true} if this does not contain any element, {@code false} otherwise.
*
* @see #isCharged()
*/
public final boolean isEmpty() {
return 0 == size();
}
/**
* Returns {@code true} if this contains at least one element, {@code false} otherwise.
*
* @see #isEmpty()
*/
public final boolean isCharged() {
return 0 < size();
}
/**
* Executes a given {@linkplain BiFunction function} with this series' {@linkplain #head() head} and
* {@linkplain #tail() tail} as arguments if this {@linkplain #isCharged() is charged}.
*
* @param The result type.
* @return
* - An {@link Optional} describing the result of the {@linkplain BiFunction#apply(Object, Object) function call}
* if this {@linkplain #isCharged() is charged}.
* - {@link Optional#empty()} if the result of the {@linkplain BiFunction#apply(Object, Object) function call}
* is {@code null}.
* - {@link Optional#empty()} if this {@linkplain #isEmpty() is empty}.
*
*
* @see #isCharged()
*/
public final Optional ifCharged(final BiFunction, R> function) {
return Optional.of(this)
.filter(Series::isCharged)
.map(series -> function.apply(series.head(), series.tail()));
}
/**
* Returns this series' head element if this {@linkplain #isCharged() is charged}.
*
* @throws NoSuchElementException if this {@linkplain #isEmpty() is empty}.
*/
public abstract E head();
/**
* Returns the remaining subseries that follows this series' {@linkplain #head() head element}.
*/
public abstract Series tail();
/**
* Returns the number of elements within this series.
*/
public abstract int size();
/**
* Returns an immutable {@link List} representation of this series.
*/
public abstract List asList();
/**
* Returns a sequential {@link Stream} with this {@link Series} as its source.
*/
public final Stream stream() {
return asList().stream();
}
@Override
public final boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof Series) && asList().equals(((Series>) obj).asList()));
}
@Override
public final int hashCode() {
return asList().hashCode();
}
@Override
public final String toString() {
return asList().toString();
}
private static final class Empty extends Series {
@SuppressWarnings("rawtypes")
private static final Empty INSTANCE = new Empty();
@Override
public final E head() {
throw new NoSuchElementException("this Series is empty");
}
@Override
public final Series tail() {
return empty();
}
@Override
public final int size() {
return 0;
}
@Override
public final List asList() {
return Collections.emptyList();
}
}
private static final class Charged extends Series {
private final List backing;
private final int headIndex;
private Charged(final List backing, final int headIndex) {
this.headIndex = headIndex;
this.backing = backing;
}
@Override
public final E head() {
return backing.get(headIndex);
}
@Override
public final Series tail() {
return seriesOf(backing, headIndex + 1);
}
@Override
public final int size() {
return backing.size() - headIndex;
}
@Override
public final List asList() {
return backing.subList(headIndex, backing.size());
}
}
}