blob: 6141600bc864d99e3f94f462bec6e1f610ea86fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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 org.apache.pivot.collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.pivot.annotations.UnsupportedOperation;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.util.Utils;
/**
* Interface representing an ordered sequence of items.
*
* @param <T> The type of elements stored in this sequence.
*/
public interface Sequence<T> {
/**
* Collection of static utility methods providing path access to nested
* sequence data.
*
* @param <T> note that in Tree the type parameter currently is not used
*/
public static class Tree<T> {
/**
* An object representing a path to a nested node in nested sequence
* data.
*/
public static class Path implements Sequence<Integer>, Iterable<Integer> {
private ArrayList<Integer> elements;
public Path() {
elements = new ArrayList<>();
}
public Path(final Integer... elements) {
this.elements = new ArrayList<>(elements);
}
public Path(final Path path) {
elements = new ArrayList<>(path.elements);
}
public Path(final Path path, final int depth) {
elements = new ArrayList<>(path.elements, 0, depth);
}
private Path(final ArrayList<Integer> elements) {
this.elements = elements;
}
@Override
public int add(final Integer element) {
return elements.add(element);
}
@Override
public void insert(final Integer element, final int index) {
elements.insert(element, index);
}
@Override
public Integer update(final int index, final Integer element) {
return elements.update(index, element);
}
@Override
@UnsupportedOperation
public int remove(final Integer element) {
throw new UnsupportedOperationException();
}
@Override
public Sequence<Integer> remove(final int index, final int count) {
return elements.remove(index, count);
}
@Override
public Integer get(final int index) {
return elements.get(index);
}
@Override
public int indexOf(final Integer element) {
return elements.indexOf(element);
}
@Override
public int getLength() {
return elements.getLength();
}
@Override
public Iterator<Integer> iterator() {
return new ImmutableIterator<>(elements.iterator());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
int i = 0;
for (Integer element : elements) {
if (i > 0) {
sb.append(", ");
}
sb.append(element);
i++;
}
sb.append("]");
return sb.toString();
}
public Integer[] toArray() {
return elements.toArray(Integer[].class);
}
public static Path forDepth(final int depth) {
return new Path(new ArrayList<Integer>(depth));
}
}
/**
* Class representing an immutable path.
*/
public static final class ImmutablePath extends Path {
public ImmutablePath(final Integer... elements) {
super(elements);
}
public ImmutablePath(final Path path) {
super(path);
}
@Override
@UnsupportedOperation
public int add(final Integer element) {
throw new UnsupportedOperationException();
}
@Override
@UnsupportedOperation
public void insert(final Integer element, final int index) {
throw new UnsupportedOperationException();
}
@Override
@UnsupportedOperation
public Integer update(final int index, final Integer element) {
throw new UnsupportedOperationException();
}
@Override
@UnsupportedOperation
public Sequence<Integer> remove(final int index, final int count) {
throw new UnsupportedOperationException();
}
}
/**
* Nested sequence item iterator interface.
*
* @param <T> Type of object contained in the sequence and the iterator.
*/
public interface ItemIterator<T> extends Iterator<T> {
/**
* Gets the path within the nested sequence to the item most
* recently returned by a call to <tt>next()</tt>.
*
* @return The path (from the root sequence) to the current item.
* @throws IllegalStateException If <tt>next()</tt> has not yet been
* called on this iterator.
*/
public Path getPath();
}
private static class DepthFirstItemIterator<T> implements ItemIterator<T> {
private ArrayStack<Sequence<T>> stack = new ArrayStack<>();
private Path previousPath = null;
private Path nextPath = new Path();
public DepthFirstItemIterator(final Sequence<T> sequence) {
stack.push(sequence);
nextPath.add(0);
normalize();
}
@Override
public boolean hasNext() {
return (stack.peek() != null);
}
@Override
@SuppressWarnings("unchecked")
public T next() {
Sequence<T> sequence = stack.peek();
if (sequence == null) {
throw new NoSuchElementException();
}
previousPath = new Path(nextPath);
int n = nextPath.getLength();
int index = nextPath.get(n - 1);
T item = sequence.get(index);
if (item instanceof Sequence<?>) {
stack.push((Sequence<T>) item);
nextPath.add(0);
} else {
nextPath.update(n - 1, index + 1);
}
normalize();
return item;
}
/**
* Normalizes <tt>stack</tt> and <tt>nextPath</tt> such that the
* iterator is pointing to a valid item or the end of the nested
* sequence.
*/
private void normalize() {
Sequence<T> sequence = stack.peek();
int n = nextPath.getLength();
int index = nextPath.get(n - 1);
while (sequence != null && index >= sequence.getLength()) {
stack.pop();
sequence = stack.peek();
nextPath.remove(--n, 1);
if (n > 0) {
index = nextPath.get(n - 1);
nextPath.update(n - 1, ++index);
}
}
}
@Override
@UnsupportedOperation
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Path getPath() {
if (previousPath == null) {
throw new IllegalStateException();
}
return previousPath;
}
}
/**
* Adds an item to a nested sequence.
*
* @param <T> Type of the items in this sequence.
* @param sequence The root sequence.
* @param item The item to be added to the sequence.
* @param path The path of the sequence to which the item should be
* added.
* @return The index at which the item was inserted, relative to the
* parent sequence.
*/
@SuppressWarnings("unchecked")
public static <T> int add(final Sequence<T> sequence, final T item, final Path path) {
return ((Sequence<T>) get(sequence, path)).add(item);
}
/**
* Inserts an item into a nested sequence.
*
* @param <T> Type of items in this sequence.
* @param sequence The root sequence.
* @param item The item to be inserted into the sequence.
* @param path The path of the sequence into which the item should be
* inserted.
* @param index The index at which the item should be inserted within the
* parent sequence.
*/
@SuppressWarnings("unchecked")
public static <T> void insert(final Sequence<T> sequence, final T item, final Path path, final int index) {
((Sequence<T>) get(sequence, path)).insert(item, index);
}
/**
* Updates an item in a nested sequence.
*
* @param <T> The type of items in this sequence.
* @param sequence The root sequence.
* @param path The path of the item to update.
* @param item The item that will replace any existing value at the given
* path.
* @return The item that was previously stored at the given path.
*/
@SuppressWarnings("unchecked")
public static <T> T update(final Sequence<T> sequence, final Path path, final T item) {
Utils.checkNull(sequence, "sequence");
Utils.checkNull(path, "path");
int i = 0, n = path.getLength() - 1;
Sequence<T> sequenceUpdated = sequence;
while (i < n) {
sequenceUpdated = (Sequence<T>) sequenceUpdated.get(path.get(i++));
}
return sequenceUpdated.update(path.get(i), item);
}
/**
* Removes the first occurrence of an item from a nested sequence.
*
* @param <T> The type of items in this sequence.
* @param sequence The root sequence.
* @param item The item to remove.
* @return The path of the item that was removed.
*/
public static <T> Path remove(final Sequence<T> sequence, final T item) {
Path path = pathOf(sequence, item);
if (path == null) {
throw new IllegalArgumentException("item is not a descendant of sequence.");
}
remove(sequence, path, 1);
return path;
}
/**
* Removes items from a nested sequence.
*
* @param <T> The type of items in this sequence.
* @param sequence The root sequence.
* @param path The path of the item(s) to remove.
* @param count The number of items to remove.
* @return The sequence of items that were removed.
*/
@SuppressWarnings("unchecked")
public static <T> Sequence<T> remove(final Sequence<T> sequence, final Path path, final int count) {
Utils.checkNull(sequence, "sequence");
Utils.checkNull(path, "path");
int i = 0, n = path.getLength() - 1;
Sequence<T> sequenceUpdated = sequence;
while (i < n) {
sequenceUpdated = (Sequence<T>) sequenceUpdated.get(path.get(i++));
}
return sequenceUpdated.remove(path.get(i), count);
}
/**
* Retrieves an item from a nested sequence.
*
* @param <T> The type of items in this sequence.
* @param sequence The root sequence.
* @param path The path of the item to retrieve.
* @return The item at the given path, or <tt>null</tt> if the path is
* empty.
*/
@SuppressWarnings("unchecked")
public static <T> T get(final Sequence<T> sequence, final Path path) {
Utils.checkNull(sequence, "sequence");
Utils.checkNull(path, "path");
T item;
if (path.getLength() == 0) {
item = null;
} else {
int i = 0, n = path.getLength() - 1;
Sequence<T> sequenceUpdated = sequence;
while (i < n) {
sequenceUpdated = (Sequence<T>) sequenceUpdated.get(path.get(i++));
}
item = sequenceUpdated.get(path.get(i));
}
return item;
}
/**
* Returns the path to an item in a nested sequence.
*
* @param <T> The type of items in this sequence.
* @param sequence The root sequence.
* @param item The item to locate.
* @return The path of first occurrence of the item if it exists in the
* sequence; <tt>null</tt> otherwise.
*/
@SuppressWarnings("unchecked")
public static <T> Path pathOf(final Sequence<T> sequence, final T item) {
Utils.checkNull(sequence, "sequence");
Utils.checkNull(item, "item");
Path path = null;
for (int i = 0, n = sequence.getLength(); i < n && path == null; i++) {
T t = sequence.get(i);
if (t.equals(item)) {
path = new Path();
path.add(i);
} else {
if (t instanceof Sequence<?>) {
path = pathOf((Sequence<T>) t, item);
if (path != null) {
path.insert(i, 0);
}
}
}
}
return path;
}
/**
* Returns an iterator that will perform a depth-first traversal of the
* nested sequence.
* @param <T> The type of items in this sequence.
* @param sequence The sequence for which we are requesting an iterator.
* @return The new iterator over the sequence (depth-first order).
*/
public static <T> ItemIterator<T> depthFirstIterator(final Sequence<T> sequence) {
return new DepthFirstItemIterator<>(sequence);
}
/**
* Determines whether the path represented by the second argument is a
* descendant of the path represented by the first argument.
*
* @param ancestorPath The ancestor path to test.
* @param descendantPath The descendant path to test.
* @return <tt>true</tt> if the second argument is a descendant of the first
* path argument, <tt>false</tt> otherwise.
*/
public static boolean isDescendant(final Path ancestorPath, final Path descendantPath) {
int ancestorLength = ancestorPath.getLength();
int descendantLength = descendantPath.getLength();
boolean result = (ancestorLength <= descendantLength);
if (result) {
for (int i = 0, n = ancestorLength; i < n; i++) {
int index1 = ancestorPath.get(i);
int index2 = descendantPath.get(i);
if (index1 != index2) {
result = false;
break;
}
}
}
return result;
}
}
/**
* Adds an item to the sequence.
*
* @param item The item to be added to the sequence.
* @return The index at which the item was added, or <tt>-1</tt> if the item
* was not added to the sequence.
*/
int add(T item);
/**
* Inserts an item into the sequence at a specific index.
*
* @param item The item to be added to the sequence.
* @param index The index at which the item should be inserted. Must be a
* value between <tt>0</tt> and <tt>getLength()</tt>.
*/
void insert(T item, int index);
/**
* Updates the item at the given index.
*
* @param index The index of the item to update.
* @param item The item that will replace any existing value at the given
* index.
* @return The item that was previously stored at the given index.
*/
T update(int index, T item);
/**
* Removes the first occurrence of the given item from the sequence.
*
* @param item The item to remove.
* @return The index of the item that was removed, or <tt>-1</tt> if the item
* could not be found.
* @see #remove(int, int)
*/
int remove(T item);
/**
* Removes one or more items from the sequence.
*
* @param index The starting index to remove.
* @param count The number of items to remove, beginning with <tt>index</tt>.
* @return A sequence containing the items that were removed.
*/
Sequence<T> remove(int index, int count);
/**
* Retrieves the item at the given index.
*
* @param index The index of the item to retrieve.
* @return The item at this index in the sequence.
*/
T get(int index);
/**
* Returns the index of an item in the sequence.
*
* @param item The item to locate.
* @return The index of first occurrence of the item if it exists in the
* sequence; <tt>-1</tt>, otherwise.
*/
int indexOf(T item);
/**
* Returns the length of the sequence.
*
* @return The number of items in the sequence.
*/
int getLength();
/**
* Add all the elements of the given collection to this sequence
* using the {@link #add} method of this interface.
*
* @param c The other collection to add to this sequence.
*/
default void addAll(Collection<T> c) {
c.forEach(item -> add(item));
}
/**
* Add all the elements of the given array to this sequence
* using the {@link #add} method of this interface.
*
* @param array The array to add to this sequence.
*/
default void addAll(T[] array) {
for (T item : array) {
add(item);
}
}
}