Some collection interfaces and implementations
Overview
This is a collection of some collection interfaces and implementations, mainly intended for clear-cut API design:
This library is hosted in the Maven Central Repository. You can use it with the following coordinates:
<dependency>
<groupId>net.markenwerk</groupId>
<artifactId>commons-collections</artifactId>
<version>1.0.0</version>
</dependency>
Consult the usage description and Javadoc for further information.
Motivation
This library provides collections for clear-cut API design. Most libraries just use JAVA's collection API. Although this has the important advantage, that the API is well known and understood, it is mostly intended as a general purpose API and not for clear-cut API. A clear-cut API should convey as much usage description as possible through the type system and names of public methods, interfaces and classes. if, for example, an API allows a read-only access to one of its properties, there should only be a getter-method for that property and no setter-method. If the API would have a setter-method in such a scenario, it would need to throw an UnsupportedOperationException
, to protect its internal state, should the method be invoked by the API user and inform the API user about this fact in the API documentation. Not to include an unwanted setter in an APi is easy, but if the API designer wants to allow a read-only access to a collection, he has several options (All of which we observed in the public APIs of several libraries):
- Include a getter-method for an unmodifiable JAVA collections to the API interface, i.e. by returning a
Collections.unmodifiableCollection()
and convey this fact in the API documentation. This is undesirable, because the API user receives aCollection
object that is going to throw exception at runtime, if the API user doesn't read the API documentation carefully. - Add methods that mimic the desired subset of the behavior of the JAVA collection class to the API interface, i.e. by adding
add()
,addAll()
, ... instead of a getter-method for an unmodifiable JAVA collection. This is undesirable because it pollutes the API interface with a lot of methods, especially if the API interface has more than one such collection (and the method names need to be suffixed accordingly), it will easily become inconsistent, complicate testing and generally defeats the purpose of OOP. - Add just the minimum subset of methods that mimic the behavior of a collection class, i.e. by adding
add()
, ..., but no convenience methods likeaddAll()
. This has all the disadvantages of the previous option, but with added inconvenience.
This is where this library comes in. It provides interfaces for collections with read-only-access, write-only-access and for common data-structures. If, for example, an API wants to publish a stack, it can't use JAVA collections, because ther is no pure stack. A List
and even a Stack
offer a lot of methods, that allow "un-stack-like" access.
Interfaces
Sink
The Sink
interface should be used by components that allow a write-only-access.
A Sink
must implement the following methods:
public Sink<Payload> add(Payload payload);
public Sink<Payload> addAll(Payload... payloads);
public Sink<Payload> addAll(Iterable<? extends Payload> payloads);
A Sink
is forbidden to reveal information about the content of an underlying component through its hashCode
-, equals()
- or toString()
-methods.
This library provides AbstractSink
as a base implementation and CollectionSink
, HandlerSink
, SequenceSink
and StackSink
as ready-to-use implementations that are backed by the corresponding components.
Source
The Source
interface should be used by components that allow a read-only-access.
A Source
must, in essence, implement the following methods:
public boolean isEmpty();
public int size();
public Payload getFirst() throws NoSuchElementException;
public boolean contains(Object reference);
There are several other methods that can be used to query or filter the Source
using a reference object or a Predicate
.
The IndexedSource
interface should be used by components that allow a read-only-access to a linear data-structure.
An IndexedSource
is a Source
that must, in essence, implement the following additional methods:
public Payload get(int index) throws IndexOutOfBoundsException;
public boolean isFirst(Payload payload) throws NoSuchElementException;
public boolean isLast(Payload payload) throws NoSuchElementException;
public Payload getLast() throws NoSuchElementException;
public Optional<Integer> firstIndexOf(Payload payload);
public Optional<Integer> lastIndexOf(Payload payload);
There are several other methods that can be used to query or filter the IndexedSource
using a reference object or a Predicate
.
This library provides AbstractSource
and AbstractIndexedSource
as a base implementation and ArraySource
, CollectionSource
, EmptySource
, ListSource
, MapKeySource
, MapValueSource
, ObjectSource
and OptionalSource
as ready-to-use implementations that are backed by the corresponding components.
Stack
The Stack
interface should be used by components that need a pure stack.
A Stack
is a Source
that must, in essence, implement the following additional methods:
public Stack<Payload> push(Payload payload);
public Stack<Payload> pushAll(Payload... payloads);
public Payload pop() throws NoSuchElementException;
public Source<Payload> popAll(int number) throws IllegalArgumentException;
public Payload get(int index) throws IndexOutOfBoundsException;
public Payload getFirst() throws NoSuchElementException;
public replace(Payload payload) NoSuchElementException;
public Source<Payload> clear();
public ProtectedIterator<Payload> iterator();
There are several other methods that can be used to query or filter the Stack
using a reference object or a Predicate
.
This library provides AbstractStack
as a base implementation and LinkedStack
as a ready-to-use implementation.
Sequence
The Sequence
interface should be used by components that need a pure linear data-structure.
A Sequence
is an IndexedSource
that must, in essence, implement the following additional methods:
public Sequence<Payload> insert(int index, Payload payload) throws IndexOutOfBoundsException;
public Sequence<Payload> insertAll(int index, Payload... payloads) throws IndexOutOfBoundsException;
public Sequence<Payload> prepend(Payload payload);
public Sequence<Payload> prependAll(Payload... payloads);
public Sequence<Payload> append(Payload payload);
public Sequence<Payload> appendAll(Payload... payloads);
public Payload remove(int index) throws IndexOutOfBoundsException;
public Payload removeFirst() throws NoSuchElementException;
public Payload removeLast() throws NoSuchElementException;
public Source<Payload> removeAll(Payload reference);
public Source<Payload> retainAll(Payload reference);
public Source<Payload> clear();
public Payload replace(int index, Payload replacement) IndexOutOfBoundsException;
There are several other methods that can be used to query or filter the Sequence
using a reference object or a Predicate
.
This library provides AbstractSequence
as a base implementation and Listsequence
as a ready-to-use implementation.