All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.collections.PairList Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * 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 org.conqat.lib.commons.collections;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.function.BiFunctionWithException;
import org.conqat.lib.commons.function.FunctionWithException;
import org.jetbrains.annotations.Contract;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;

/**
 * A list for storing pairs in a specific order.
 */
public class PairList implements Serializable, Iterable> {

	private static final long serialVersionUID = 1;

	/** The JSON property name of {@link #firstElements}. */
	private static final String FIRST_ELEMENTS_PROPERTY_NAME = "firstElements";

	/** The JSON property name of {@link #secondElements}. */
	private static final String SECOND_ELEMENTS_PROPERTY_NAME = "secondElements";

	/** The current size. */
	@JsonProperty("size")
	private int size = 0;

	/** The array used for storing the S. */
	// Please note that the actual JSON serialization
	// uses the result of #getFirstList
	@JsonProperty(FIRST_ELEMENTS_PROPERTY_NAME)
	// Same as e.g. ArrayList this instance is only Serializable if the actual
	// elements are all Serializable
	@SuppressWarnings("NonSerializableFieldInSerializableClass")
	private Object[] firstElements;

	/** The array used for storing the T. */
	// Please note that the actual JSON serialization
	// uses the result of #getSecondList
	@JsonProperty(SECOND_ELEMENTS_PROPERTY_NAME)
	// Same as e.g. ArrayList this instance is only Serializable if the actual
	// elements are all Serializable
	@SuppressWarnings("NonSerializableFieldInSerializableClass")
	private Object[] secondElements;

	public PairList() {
		this(16);
	}

	/** Constructor. */
	public PairList(int initialCapacity) {
		if (initialCapacity < 1) {
			initialCapacity = 1;
		}
		firstElements = new Object[initialCapacity];
		secondElements = new Object[initialCapacity];
	}

	/** Copy constructor. */
	public PairList(PairList other) {
		this(other.size);
		addAll(other);
	}

	/**
	 * Constructor to convert a map into a pair list.
	 */
	public PairList(Map map) {
		this(map.size());
		for (Entry entry : map.entrySet()) {
			add(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * Creates a new pair list initialized with a single key/value pair. This is especially helpful for
	 * construction of small pair lists, as type inference reduces writing overhead.
	 */
	public static  PairList from(S key, T value) {
		PairList result = new PairList<>(1);
		result.add(key, value);
		return result;
	}

	/**
	 * Creates a new {@link PairList} from given {@link Pair}s.
	 */
	@SafeVarargs
	public static  PairList fromPairs(Pair pair, Pair... additionalPairs) {
		PairList result = new PairList<>(1 + additionalPairs.length);
		result.add(pair.getFirst(), pair.getSecond());
		for (Pair additionalPair : additionalPairs) {
			result.add(additionalPair.getFirst(), additionalPair.getSecond());
		}

		return result;
	}

	/** Creates a new {@link PairList} from the given {@link Map}. */
	public static  PairList fromMap(Map map) {
		PairList pairList = new PairList<>(map.size());
		map.forEach(pairList::add);
		return pairList;
	}

	/** Returns whether the list is empty. */
	public boolean isEmpty() {
		return size == 0;
	}

	/** Returns the size of the list. */
	public int size() {
		return size;
	}

	/** Add the given pair to the list. */
	public void add(S first, T second) {
		ensureSpace(size + 1);
		firstElements[size] = first;
		secondElements[size] = second;
		++size;
	}

	/** Add the given pair to the list. */
	public void add(Pair pair) {
		add(pair.getFirst(), pair.getSecond());
	}

	/**
	 * Inserts the given element at the given index, shifting all other elements. Note that this
	 * operation is liner in the length of this list.
	 */
	public void insert(int index, S first, T second) {
		ensureSpace(size + 1);
		for (int i = size; i > index; --i) {
			firstElements[i] = firstElements[i - 1];
			secondElements[i] = secondElements[i - 1];
		}
		++size;
		firstElements[index] = first;
		secondElements[index] = second;
	}

	/** Adds all pairs from another list. */
	public void addAll(PairList other) {
		// we have to store this in a local var, as other.size may change if
		// other == this
		int otherSize = other.size;

		ensureSpace(size + otherSize);
		for (int i = 0; i < otherSize; ++i) {
			firstElements[size] = other.firstElements[i];
			secondElements[size] = other.secondElements[i];
			++size;
		}
	}

	/**
	 * Add the product of {@code firstElement} and with all the elements from {@code secondElements}.
	 */
	public void addAll(S firstElement, Iterable secondElements) {
		for (T secondElement : secondElements) {
			add(firstElement, secondElement);
		}
	}

	/** Make sure there is space for at least the given amount of elements. */
	private void ensureSpace(int space) {
		if (space <= firstElements.length) {
			return;
		}

		Object[] oldFirst = firstElements;
		Object[] oldSecond = secondElements;
		int newSize = firstElements.length * 2;
		while (newSize < space) {
			newSize *= 2;
		}

		firstElements = new Object[newSize];
		secondElements = new Object[newSize];
		System.arraycopy(oldFirst, 0, firstElements, 0, size);
		System.arraycopy(oldSecond, 0, secondElements, 0, size);
	}

	/** Returns the first element at given index. */
	@SuppressWarnings("unchecked")
	public S getFirst(int i) {
		checkWithinBounds(i);
		return (S) firstElements[i];
	}

	/**
	 * Checks whether the given i is within the bounds. Throws an exception otherwise.
	 */
	private void checkWithinBounds(int i) {
		if (i < 0 || i >= size) {
			throw new IndexOutOfBoundsException("Out of bounds: " + i);
		}
	}

	/** Sets the first element at given index. */
	public void setFirst(int i, S value) {
		checkWithinBounds(i);
		firstElements[i] = value;
	}

	/** Returns the second element at given index. */
	@SuppressWarnings("unchecked")
	public T getSecond(int i) {
		checkWithinBounds(i);
		return (T) secondElements[i];
	}

	/** Sets the first element at given index. */
	public void setSecond(int i, T value) {
		checkWithinBounds(i);
		secondElements[i] = value;
	}

	/** Creates a new list containing all first elements. */
	@SuppressWarnings("unchecked")
	public List extractFirstList() {
		List result = new ArrayList<>(size + 1);
		for (int i = 0; i < size; ++i) {
			result.add((S) firstElements[i]);
		}
		return result;
	}

	/** Returns a list view of the first elements backed directly by the array. */
	// Use this getter for actual JSON serialization, so that no trailing null
	// elements of the array are serialized (when firstElements.length > size)
	@JsonGetter(FIRST_ELEMENTS_PROPERTY_NAME)
	@SuppressWarnings("unchecked")
	public UnmodifiableList getFirstList() {
		return (UnmodifiableList) CollectionUtils.asUnmodifiable(Arrays.asList(firstElements).subList(0, size));
	}

	/** Creates a new list containing all second elements. */
	public List extractSecondList() {
		return extractSecondElementsFilteredByFirst(x -> true);
	}

	/** Returns a list view of the second elements backed directly by the array. */
	// Use this getter for actual JSON serialization, so that no trailing null
	// elements of the array are serialized (when secondElements.length > size)
	@JsonGetter(SECOND_ELEMENTS_PROPERTY_NAME)
	@SuppressWarnings("unchecked")
	public UnmodifiableList getSecondList() {
		return (UnmodifiableList) CollectionUtils.asUnmodifiable(Arrays.asList(secondElements).subList(0, size));
	}

	/**
	 * Creates a new list containing all second elements where the corresponding first element is
	 * matched by the predicate.
	 */
	@SuppressWarnings("unchecked")
	public List extractSecondElementsFilteredByFirst(Predicate predicate) {
		List result = new ArrayList<>();
		for (int i = 0; i < size; ++i) {
			if (predicate.test((S) firstElements[i])) {
				result.add((T) secondElements[i]);
			}
		}
		return result;
	}

	/** Swaps the entries located at indexes i and j. */
	public void swapEntries(int i, int j) {
		S tmp1 = getFirst(i);
		T tmp2 = getSecond(i);
		setFirst(i, getFirst(j));
		setSecond(i, getSecond(j));
		setFirst(j, tmp1);
		setSecond(j, tmp2);
	}

	/** Clears this list. */
	public void clear() {
		size = 0;
	}

	/** Removes the last element of the list. */
	public void removeLast() {
		CCSMAssert.isTrue(size > 0, "Size must be positive!");
		size -= 1;

		// clear elements to allow GC
		firstElements[size] = null;
		secondElements[size] = null;
	}

	/**
	 * Removes the element at given index. Note that this operation is liner in the length of this list.
	 */
	public void remove(int index) {
		for (int i = index + 1; i < size; ++i) {
			firstElements[i - 1] = firstElements[i];
			secondElements[i - 1] = secondElements[i];
		}
		removeLast();
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		result.append('[');
		for (int i = 0; i < size; i++) {
			if (i != 0) {
				result.append(',');
			}
			result.append('(');
			result.append(firstElements[i]);
			result.append(',');
			result.append(secondElements[i]);
			result.append(')');
		}
		result.append(']');
		return result.toString();
	}

	/** {@inheritDoc} */
	@Override
	public int hashCode() {
		int prime = 31;
		int hash = size;
		hash = prime * hash + hashArrayUntil(firstElements, size);
		return prime * hash + hashArrayUntil(secondElements, size);
	}

	/**
	 * Generates a hash code for part of an array. The hash code is calculated from the hash codes of
	 * the given objects. Only objects that lie within index 0 and end index in the array are considered
	 * (so we ignore the {@link #capacity()}).
	 * 

* This method offers a better performance than creating a copy of the array that only contains the * desired elements and then calling {@link Objects#hashCode(Object)}. */ private static int hashArrayUntil(Object[] objects, int endExclusive) { int hashCode = 1; for (int i = 0; i < endExclusive; i++) { hashCode = hashCode * 31 + Objects.hashCode(objects[i]); } return hashCode; } /** * Checks if the two given arrays match until the given {@code endExclusive} (which can be used to * ignore the {@link #capacity()}). */ private static boolean arrayEqualsUntil(Object[] elements, Object[] otherElements, int endExclusive) { for (int i = 0; i < endExclusive; i++) { if (!Objects.equals(elements[i], otherElements[i])) { return false; } } return true; } /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PairList)) { return false; } PairList other = (PairList) obj; if (size != other.size) { return false; } return arrayEqualsUntil(firstElements, other.firstElements, size) && arrayEqualsUntil(secondElements, other.secondElements, size); } /** {@inheritDoc} */ @Override public Iterator> iterator() { return new Iterator>() { private int index = 0; /** {@inheritDoc} */ @Override public boolean hasNext() { return index < size; } /** {@inheritDoc} */ @Override public Pair next() { checkWithinBounds(index); int oldIndex = index; index++; return createPairForIndex(oldIndex); } }; } /** * Returns a non-parallel stream of {@link Pair}s from this list. *

* NOTE: the underlying {@link Spliterator} implementation does currently not support splitting. * Thus, trying to use the returned stream in parallel execution will fail. * * @see PairListSpliterator#trySplit() */ public Stream> stream() { return StreamSupport.stream(new PairListSpliterator(), false); } /** * Converts this {@link PairList} into a {@link Map}. *

* If the first elements contain duplicates (according to {@link Object#equals(Object)}), an * {@link IllegalStateException} is thrown. If the first elements may have duplicates, use * {@link #toMap(BinaryOperator)} instead. */ public Map toMap() { Map result = new HashMap<>(); // must use explicit put, as stream collector does not support null // values forEach(result::put); return result; } /** * Converts this {@link PairList} into a {@link Map}. *

* If the first elements contain duplicates (according to {@link Object#equals(Object)}), the * respective second elements are merged using the provided merging function. */ public Map toMap(BinaryOperator mergeFunction) { return stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond, mergeFunction)); } /** * Converts this {@link PairList} into a {@link Map} with the {@link #getFirst(int) first} elements * as keys and the {@link #getSecond(int) second} elements combined into a {@link List}. */ public Map> groupedByFirst() { return groupedByFirst(Collectors.toList()); } /** * Converts this {@link PairList} into a {@link Map} with the {@link #getFirst(int) first} elements * as keys and the {@link #getSecond(int) second} combined by the provided {@code downstream}. */ public Map groupedByFirst(Collector downstream) { return stream().collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, downstream))); } /** * Creates a pair from the values at the given index in this list. *

* We suppress unchecked cast warnings since the PairList stores all elements as plain Objects. */ @SuppressWarnings("unchecked") private Pair createPairForIndex(int index) { return new Pair<>((S) firstElements[index], (T) secondElements[index]); } /** * Spliterator for the {@link PairList}. */ private final class PairListSpliterator implements Spliterator> { /** * The next element to process when {@link #tryAdvance(Consumer)} is called. */ @JsonProperty("nextElementToProcess") private int nextElementToProcess; @Override public int characteristics() { // the Spliterator is not sub-sized since we don't support splitting // at the moment return Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.SIZED; } @Override public long estimateSize() { return size - nextElementToProcess; } @Override public boolean tryAdvance(Consumer> action) { if (nextElementToProcess == size) { return false; } action.accept(createPairForIndex(nextElementToProcess)); nextElementToProcess++; return true; } /** * {@inheritDoc} *

* We return null to indicate that this {@link Spliterator} cannot be split. If * parallel execution is required, this method must be implemented and the sub-sized characteristic * should be adhered to. */ @Override public Spliterator> trySplit() { return null; } } /** * Collects a stream of elements into a {@link PairList}. * * @param * stream element type * @param * {@link PairList#getFirst(int) first} {@link PairList} element type * @param * {@link PairList#getSecond(int) second} {@link PairList} element type */ private static class PairListCollector implements Collector, PairList> { /** * Maps the element to the {@link PairList#getFirst(int) first} entry. */ private final Function firstMapper; /** * Maps the element to the {@link PairList#getSecond(int) second} entry. */ private final Function secondMapper; private PairListCollector(Function firstMapper, Function secondMapper) { CCSMAssert.isNotNull(firstMapper, () -> String.format("Expected \"%s\" to be not null", "firstMapper")); CCSMAssert.isNotNull(secondMapper, () -> String.format("Expected \"%s\" to be not null", "secondMapper")); this.firstMapper = firstMapper; this.secondMapper = secondMapper; } @Override public Supplier> supplier() { return PairList::new; } @Override public BiConsumer, I> accumulator() { return (list, value) -> list.add(firstMapper.apply(value), secondMapper.apply(value)); } @Override public BinaryOperator> combiner() { return (list1, list2) -> { list1.addAll(list2); return list1; }; } @Override public Function, PairList> finisher() { return list -> list; } @Override public Set characteristics() { return EnumSet.of(Characteristics.IDENTITY_FINISH); } } /** * Returns a collector for collecting a stream of {@link Pair}s into a {@link PairList}. */ public static Collector, ?, PairList> toPairList() { return new PairListCollector<>(Pair::getFirst, Pair::getSecond); } /** * Returns a collector for collecting a stream of {@link PairList}s into a single {@link PairList}. */ public static Collector, ?, PairList> combine() { return Collector.of(PairList::new, PairList::addAll, (p1, p2) -> { p1.addAll(p2); return p1; }); } /** * Returns a collector for collecting a stream of elements into a {@link PairList}. * * @param firstMapper * Maps an element of the stream to the {@link PairList }s * {@link PairList#setFirst(int, Object) first} element type * @param secondMapper * Maps an element of the stream to the {@link PairList }s * {@link PairList#setSecond(int, Object) second} element type */ public static Collector> toPairList(Function firstMapper, Function secondMapper) { return new PairListCollector<>(firstMapper, secondMapper); } /** * Creates a {@link PairList} that consists of pairs of values taken from the two {@link List}s. * I.e. entry i in the resulting {@link PairList} will consists of the pair (a, b) where a is the * entry at index i in the first collection and b is the entry at index i of the second collection. *

* Both collections must be of the same size. The order of insertion into the new {@link PairList} * is determined by the order imposed by the collections' iterators. */ public static PairList zip(List firstValues, List secondValues) { CCSMAssert.isTrue(firstValues.size() == secondValues.size(), "Can only zip together collections of the same size."); PairList result = new PairList<>(firstValues.size()); Iterator firstIterator = firstValues.iterator(); Iterator secondIterator = secondValues.iterator(); while (firstIterator.hasNext()) { result.add(firstIterator.next(), secondIterator.next()); } return result; } /** * For each element pair in this list, calls the given consumer with the first and second value from * the pair as the only arguments. */ public void forEach(BiConsumer consumer) { for (int i = 0; i < size; i++) { consumer.accept(getFirst(i), getSecond(i)); } } /** * Returns a new {@link PairList}, where both mappers are applied to the elements of the current * {@link PairList} (input for each mapper is the pair of elements at each position). */ public PairList map(BiFunction firstMapper, BiFunction secondMapper) { PairList result = new PairList<>(size); forEach((key, value) -> result.add(firstMapper.apply(key, value), secondMapper.apply(key, value))); return result; } /** * Produce a {@link List} of the given {@link PairList} using the given mapper function. */ public List map(BiFunctionWithException mapper) throws E { List result = new ArrayList<>(size()); for (int i = 0; i < size; i++) { result.add(mapper.apply(getFirst(i), getSecond(i))); } return result; } /** * Returns a new {@link PairList}, where the first elements are obtained by applying the given * mapper function to the first elements of this list. */ public PairList mapFirst(FunctionWithException mapper) throws E { PairList mappedList = new PairList<>(size); for (int i = 0; i < size; i++) { mappedList.add(mapper.apply(getFirst(i)), getSecond(i)); } return mappedList; } /** * Returns a new {@link PairList}, where the second elements are obtained by applying the given * mapper function to the second elements of this list. */ public PairList mapSecond(FunctionWithException mapper) throws E { PairList mappedList = new PairList<>(size); for (int i = 0; i < size; i++) { mappedList.add(getFirst(i), mapper.apply(getSecond(i))); } return mappedList; } /** * Filters the pair list by testing all items against the predicate and returning a pair list of * those for which it returns true. This method does not modify the original pair list. */ public PairList filter(BiFunction filterPredicate) { PairList filteredList = new PairList<>(size); for (int i = 0; i < size; i++) { if (filterPredicate.apply(getFirst(i), getSecond(i))) { filteredList.add(getFirst(i), getSecond(i)); } } return filteredList; } /** * Tests all the items against the predicate and returns true, if any of the items matched the * predicate. Otherwise, returns false. */ public boolean anyMatch(BiFunction predicate) { for (int i = 0; i < size; i++) { if (predicate.apply(getFirst(i), getSecond(i))) { return true; } } return false; } /** Returns a reversed copy of this list. */ @Contract(value = "-> new", pure = true) public PairList reversed() { PairList reversed = new PairList<>(size()); for (int i = size() - 1; i >= 0; i--) { reversed.add(getFirst(i), getSecond(i)); } return reversed; } /** * Returns an inversed copy of this list, i.e. a list where keys and values are switched. */ @Contract(value = "-> new", pure = true) public PairList inversed() { PairList inverse = new PairList<>(size()); for (int i = size() - 1; i >= 0; i--) { inverse.add(getSecond(i), getFirst(i)); } return inverse; } /** * Returns a newly allocated array containing all the elements in this PairList as an array of * Pairs. */ @SuppressWarnings("unchecked") public Pair[] toArray() { Pair[] result = new Pair[size]; for (int i = 0; i < size; i++) { result[i] = new Pair<>((S) firstElements[i], (T) secondElements[i]); } return result; } /** * Returns a newly allocated list containing all the elements in this PairList as a list of * Pairs. */ public List> toList() { return toList(Pair::new); } /** * Returns a newly allocated list containing all the elements in this PairList as a list of entries * that are created by the given entryFactory. */ public List toList(BiFunction entryFactory) { List result = new ArrayList<>(size); for (int i = 0; i < size; i++) { result.add(entryFactory.apply((S) firstElements[i], (T) secondElements[i])); } return result; } /** * Sorts the PairList according to the given comparator. Returns a reference to itself, for * convenience. */ public void sort(Comparator> comparator) { Pair[] sorted = toArray(); Arrays.sort(sorted, comparator); for (int i = 0; i < sorted.length; i++) { setFirst(i, sorted[i].getFirst()); setSecond(i, sorted[i].getSecond()); } } /** * Returns a new {@link PairList} with all elements from both lists in order. */ public static PairList concatenate(PairList list1, PairList list2) { PairList result = new PairList<>(list1.size() + list2.size()); result.addAll(list1); result.addAll(list2); return result; } /** * The empty list (immutable). This list is serializable. */ @SuppressWarnings("rawtypes") private static final PairList EMPTY_PAIR_LIST = new PairList(Collections.emptyMap()) { private static final long serialVersionUID = 1; @Override public Iterator> iterator() { return Collections.emptyIterator(); } @Override public Spliterator> spliterator() { return Spliterators.emptySpliterator(); } @Override public void add(Object first, Object second) { throw new UnsupportedOperationException(); } @Override public void addAll(PairList other) { throw new UnsupportedOperationException(); } @Override public List extractFirstList() { return Collections.emptyList(); } @Override public List extractSecondList() { return Collections.emptyList(); } }; /** * Returns an empty list (immutable). This list is serializable. This is inspired by the Java * Collections.emptyList() method. * *

* This example illustrates the type-safe way to obtain an empty Pairlist: * *

	 * 
	 * PairList<String, String> s = PairList.emptyPairList();
	 * 
* * @param * key type of elements, if there were any, in the list * @param * value type of elements, if there were any, in the list * @return an empty immutable list */ @SuppressWarnings("unchecked") public static PairList emptyPairList() { return EMPTY_PAIR_LIST; } /** Add the given pair to the list, if it is present. */ public void addIfPresent(Optional> pair) { if (pair.isPresent()) { add(pair.get().getFirst(), pair.get().getSecond()); } } /** * Maps a pair list to another one using separate mappers for keys and values. */ public PairList map(Function keyMapper, Function valueMapper) { PairList result = new PairList<>(); forEach((key, value) -> result.add(keyMapper.apply(key), valueMapper.apply(value))); return result; } /** * Maps a pair list to another one using separate mappers for keys and values. */ public PairList mapWithException( FunctionWithException keyMapper, FunctionWithException valueMapper) throws E { PairList result = new PairList<>(); for (Pair pair : this) { result.add(keyMapper.apply(pair.getFirst()), valueMapper.apply(pair.getSecond())); } return result; } /** * Returns the index of the given object in the "first" list of this {@link PairList} or * {@link Optional#empty} if none of the first objects is equal to the given object. */ public Optional indexOfFirst(S firstSearchObject) { for (int i = 0; i < size; i++) { if (Objects.equals(firstElements[i], firstSearchObject)) { return Optional.of(i); } } return Optional.empty(); } @VisibleForTesting /* package */ int capacity() { return firstElements.length; } }