blob: 064119ec39adc33662992c5328c819fcdc65838a [file] [log] [blame]
// Copyright 2011 Google Inc.
//
// Licensed 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 com.google.enterprise.secmgr.common;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* An immutable accumulator that adds elements to the end of a "chain" and can
* later convert that to a {@link List}.
*
* @param <E> The type of the elements stored in this chain.
*/
@Immutable
@ParametersAreNonnullByDefault
public abstract class Chain<E> implements Iterable<E> {
@Nonnull private static final Chain<?> EMPTY_CHAIN = new EmptyChain<Object>();
/**
* Gets a new chain with no elements.
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
@Nonnull
public static <F> Chain<F> empty() {
return (Chain<F>) EMPTY_CHAIN;
}
/**
* Gets a new chain with the given elements.
*
* @param elements The elements to make a chain from.
* @return A chain of those elements.
*/
@CheckReturnValue
@Nonnull
public static <F> Chain<F> copyOf(Iterable<F> elements) {
Chain<F> chain = empty();
for (F element : elements) {
chain = chain.add(element);
}
return chain;
}
/**
* Gets the number of elements in this chain.
*
* @return The number of elements.
*/
@CheckReturnValue
@Nonnegative
public abstract int size();
/**
* Is this an empty chain?
*
* @return True if this chain has no elements.
*/
@CheckReturnValue
public abstract boolean isEmpty();
/**
* Gets the last element in this chain. That is, for any chain {@code C} and
* element {@code E}, {@code C.add(E).getLast() == E}.
*
* @return The last element in the chain.
* @throws UnsupportedOperationException if this is an empty chain.
*/
@CheckReturnValue
@Nonnull
public abstract E getLast();
/**
* Gets the rest of this chain. That is, for any chain {@code C} and element
* {@code E}, {@code C.add(E).getRest() == C}.
*
* @return The rest of this chain.
* @throws UnsupportedOperationException if this is an empty chain.
*/
@CheckReturnValue
@Nonnull
public abstract Chain<E> getRest();
@Immutable
private static final class EmptyChain<F> extends Chain<F> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public F getLast() {
throw new UnsupportedOperationException();
}
@Override
public Chain<F> getRest() {
throw new UnsupportedOperationException();
}
}
@Immutable
@ParametersAreNonnullByDefault
private static final class NonEmptyChain<F> extends Chain<F> {
@Nonnegative final int numberOfElements;
@Nullable final F element;
@Nonnull final Chain<F> rest;
NonEmptyChain(@Nullable F element, Chain<F> rest) {
Preconditions.checkNotNull(rest);
this.numberOfElements = rest.size() + 1;
this.element = element;
this.rest = rest;
}
@Override
public int size() {
return numberOfElements;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
@Nullable
public F getLast() {
return element;
}
@Override
@Nonnull
public Chain<F> getRest() {
return rest;
}
}
/**
* Adds a new element to the chain. Since this is an immutable structure, a
* new chain is returned that contains the new element as well as all the
* elements of this chain. The new chain has a "last" of {@code element} and
* a "rest" of {@code this}.
*
* @param element The element to be added.
* @return The new chain with the added element.
* @throws NullPointerException if element is null.
*/
@CheckReturnValue
@Nonnull
public Chain<E> add(@Nullable E element) {
return new NonEmptyChain<E>(element, this);
}
/**
* Adds some new elements to the chain.
*
* @param elements The elements to be added.
* @return The new chain with the added elements.
*/
@CheckReturnValue
@Nonnull
public Chain<E> addAll(Iterable<E> elements) {
Chain<E> chain = this;
for (E element : elements) {
chain = chain.add(element);
}
return chain;
}
/**
* Gets an element from the chain. A given index specifies the
* element, where zero refers to the first element in the chain and
* {@code size() - 1} refers to the last.
*
* @param index The index of the element to get.
* @return The specified element.
* @throws IllegalArgumentException if index is negative or too large.
*/
@CheckReturnValue
@Nonnull
public E get(@Nonnegative int index) {
Preconditions.checkArgument(index >= 0 && index < size());
Chain<E> chain = this;
for (int i = 0; i < size() - (index + 1); i += 1) {
chain = chain.getRest();
}
return chain.getLast();
}
/**
* Converts this chain to a set.
*
* @return The elements of this chain as a set.
*/
@CheckReturnValue
@Nonnull
public Set<E> toSet() {
return Sets.newHashSet(this);
}
/**
* Converts this chain to a list, preserving the chain's order.
*
* @return The elements of this chain as a list.
*/
@CheckReturnValue
@Nonnull
public List<E> toList() {
LinkedList<E> list = Lists.newLinkedList();
Chain<E> chain = this;
for (int i = 0; i < size(); i += 1) {
list.addFirst(chain.getLast());
chain = chain.getRest();
}
return list;
}
/**
* Given a chain that is an ancestor of this chain, returns the elements that
* have been added to the ancestor to make this chain.
*
* <p>For any ancestor chain {@code C} it is always true that
* {@code this.equals(C.addAll(this.toList(C)))}.
*
* @param ancestor The ancestor chain.
* @return A list of the elements elements added .
* @throws IllegalArgumentException if {@code chain} is not an ancestor.
*/
@CheckReturnValue
@Nonnull
public List<E> toList(Chain<E> ancestor) {
Preconditions.checkNotNull(ancestor);
LinkedList<E> list = Lists.newLinkedList();
Chain<E> chain = this;
while (chain != ancestor) {
if (chain.isEmpty()) {
throw new IllegalArgumentException("Not an ancestor chain: " + ancestor);
}
list.addFirst(chain.getLast());
chain = chain.getRest();
}
return list;
}
@Override
public boolean equals(Object object) {
if (object == this) { return true; }
if (!(object instanceof Chain<?>)) { return false; }
Chain<?> other = (Chain<?>) object;
if (size() != other.size()) { return false; }
Chain<E> c1 = this;
Chain<?> c2 = other;
for (int i = 0; i < size(); i += 1) {
if (!c1.getLast().equals(c2.getLast())) { return false; }
c1 = c1.getRest();
c2 = c2.getRest();
}
return true;
}
@Override
public int hashCode() {
return Objects.hashCode(toList());
}
@Override
public String toString() {
return toList().toString();
}
/**
* Gets an iterator for this chain. The iterator starts with the last element
* and moves towards the first. If you want to iterate from first element to
* last, use {@link #toList()}.
*/
@Override
public Iterator<E> iterator() {
return new LocalIterator<E>(this);
}
@NotThreadSafe
private static final class LocalIterator<F> implements Iterator<F> {
Chain<F> chain;
LocalIterator(Chain<F> chain) {
this.chain = chain;
}
@Override
public boolean hasNext() {
return !chain.isEmpty();
}
@Override
public F next() {
if (chain.isEmpty()) { throw new NoSuchElementException(); }
F value = chain.getLast();
chain = chain.getRest();
return value;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Converts an iterable of chains to an iterable of lists. The resulting
* lists have the same ordering as the input chains.
*
* @param chains The chains to convert.
* @return The resulting lists.
*/
@CheckReturnValue
@Nonnull
public static <T> Iterable<List<T>> toLists(Iterable<Chain<T>> chains) {
return Iterables.transform(chains, Chain.<T>toListFunction());
}
/**
* Gets a function that invokes the {@link #toList} method on its argument.
*/
@CheckReturnValue
@Nonnull
public static <T> Function<Chain<T>, List<T>> toListFunction() {
return new Function<Chain<T>, List<T>>() {
@Override
public List<T> apply(Chain<T> chain) {
return chain.toList();
}
};
}
/**
* Converts an iterable of chains to an iterable of sets.
*
* @param chains The chains to convert.
* @return The resulting sets.
*/
@CheckReturnValue
@Nonnull
public static <T> Iterable<Set<T>> toSets(Iterable<Chain<T>> chains) {
return Iterables.transform(chains, Chain.<T>toSetFunction());
}
/**
* Gets a function that invokes the {@link #toSet} method on its argument.
*/
@CheckReturnValue
@Nonnull
public static <T> Function<Chain<T>, Set<T>> toSetFunction() {
return new Function<Chain<T>, Set<T>>() {
@Override
public Set<T> apply(Chain<T> chain) {
return chain.toSet();
}
};
}
/**
* Extends an iterable of chains with a given element. For each member of the
* iterable, it creates a new member by adding the element to that member.
*
* @param chains The chains to be extended.
* @param element The element to extend them with.
* @return An iterable of the extended chains.
*/
@CheckReturnValue
@Nonnull
public static <T> Iterable<Chain<T>> addToChains(Iterable<Chain<T>> chains, T element) {
return Iterables.transform(chains, addFunction(element));
}
/**
* Gets a function that adds a given element to a given chain.
*
* @param element The element to be added.
* @return A function that extends its argument with {@code element}.
*/
@CheckReturnValue
@Nonnull
public static <T> Function<Chain<T>, Chain<T>> addFunction(@Nullable final T element) {
return new Function<Chain<T>, Chain<T>>() {
@Override
public Chain<T> apply(Chain<T> chain) {
return chain.add(element);
}
};
}
}