/*
* 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 groovy.lang;
import java.util.
ArrayList;
import java.util.
Collection;
import java.util.
Iterator;
import java.util.
List;
import java.util.
ListIterator;
/**
* A wrapper for {@link List} which automatically grows the list when either {@link #get(int)} or
* {@link #getAt(int)} is called with an index greater than or equal to {@code size()}.
*
* @author Andre Steingress
* @since 1.8.7
*/
public final class
ListWithDefault<T> implements
List<T> {
private final
List<T>
delegate;
private final boolean
lazyDefaultValues;
private final
Closure initClosure;
private
ListWithDefault(
List<T>
items, boolean
lazyDefaultValues,
Closure initClosure) {
this.
delegate =
items;
this.
lazyDefaultValues =
lazyDefaultValues;
this.
initClosure =
initClosure;
}
public
List<T>
getDelegate() {
return
delegate != null ? new
ArrayList<T>(
delegate) : null;
}
public boolean
isLazyDefaultValues() {
return
lazyDefaultValues;
}
public
Closure getInitClosure() {
return
initClosure != null ? (
Closure)
initClosure.
clone() : null;
}
public static <T>
ListWithDefault<T>
newInstance(
List<T>
items, boolean
lazyDefaultValues,
Closure initClosure) {
if (
items == null)
throw new
IllegalArgumentException("Parameter \"items\" must not be null");
if (
initClosure == null)
throw new
IllegalArgumentException("Parameter \"initClosure\" must not be null");
return new
ListWithDefault<T>(new
ArrayList<T>(
items),
lazyDefaultValues, (
Closure)
initClosure.
clone());
}
public int
size() {
return
delegate.
size();
}
public boolean
isEmpty() {
return
delegate.
isEmpty();
}
public boolean
contains(
Object o) {
return
delegate.
contains(
o);
}
public
Iterator<T>
iterator() {
return
delegate.
iterator();
}
public
Object[]
toArray() {
return
delegate.
toArray();
}
public <T> T[]
toArray(T[]
ts) {
return
delegate.
toArray(
ts);
}
public boolean
add(T
t) {
return
delegate.
add(
t);
}
public boolean
remove(
Object o) {
return
delegate.
remove(
o);
}
public boolean
containsAll(
Collection<?>
objects) {
return
delegate.
containsAll(
objects);
}
public boolean
addAll(
Collection<? extends T>
ts) {
return
delegate.
addAll(
ts);
}
public boolean
addAll(int
i,
Collection<? extends T>
ts) {
return
delegate.
addAll(
i,
ts);
}
public boolean
removeAll(
Collection<?>
objects) {
return
delegate.
removeAll(
objects);
}
public boolean
retainAll(
Collection<?>
objects) {
return
delegate.
retainAll(
objects);
}
public void
clear() {
delegate.
clear();
}
/**
* Overwrites subscript operator handling by redirecting to {@link #get(int)}.
*
* @param index an index (might be greater or equal to {@code size()}, or smaller than 0)
* @return the value at the given {@code index} or the default value
*/
public T
getAt(int
index) {
return
get(
index);
}
/**
* Returns the element at the given index but grows the list if needed. If the requested {@code index} is
* greater than or equal to {@code size()}, the list will grow to the new size and a default value calculated
* using the <code>initClosure</code> will be used to populate the missing value and returned.
* <p>
* If <code>lazyDefaultValues</code> is <code>true</code> any gaps when growing the list are filled
* with nulls. Subsequent attempts to retrieve items from the list from those gap index values
* will, upon finding null, call the <code>initClosure</code> to populate the list for the
* given list value. Hence, when in this mode, nulls cannot be stored in this list.
* If <code>lazyDefaultValues</code> is <code>false</code> any gaps when growing the list are filled
* eagerly by calling the <code>initClosure</code> for all gap indexes during list growth.
* No calls to <code>initClosure</code> are made except during list growth and it is ok to
* store null values in the list when in this mode.
* <p>
* This implementation breaks
* the contract of {@link java.util.List#get(int)} as it a) possibly modifies the underlying list and b) does
* NOT throw an {@link IndexOutOfBoundsException} when {@code index < 0 || index >= size()}.
*
* @param index an index (might be greater or equal to {@code size()}, or smaller than 0)
* @return the value at the given {@code index} or the default value
*/
public T
get(int
index) {
final int
size =
size();
int
normalisedIndex =
normaliseIndex(
index,
size);
if (
normalisedIndex < 0) {
throw new
IndexOutOfBoundsException("Negative index [" +
normalisedIndex + "] too large for list size " +
size);
}
// either index >= size or the normalised index is negative
if (
normalisedIndex >=
size) {
// find out the number of gaps to fill with null/the default value
final int
gapCount =
normalisedIndex -
size;
// fill all gaps
for (int
i = 0;
i <
gapCount;
i++) {
final int
idx =
size();
// if we lazily create default values, use 'null' as placeholder
if (
lazyDefaultValues)
delegate.
add(
idx, null);
else
delegate.
add(
idx,
getDefaultValue(
idx));
}
// add the first/last element being always the default value
final int
idx =
normalisedIndex;
delegate.
add(
idx,
getDefaultValue(
idx));
// normalise index again to get positive index
normalisedIndex =
normaliseIndex(
index,
size());
}
T
item =
delegate.
get(
normalisedIndex);
if (
item == null &&
lazyDefaultValues) {
item =
getDefaultValue(
normalisedIndex);
delegate.
set(
normalisedIndex,
item);
}
return
item;
}
@
SuppressWarnings("unchecked")
private T
getDefaultValue(int
idx) {
return (T)
initClosure.
call(new
Object[]{
idx});
}
private static int
normaliseIndex(int
index, int
size) {
if (
index < 0) {
index +=
size;
}
return
index;
}
public T
set(int
i, T
t) {
return
delegate.
set(
i,
t);
}
public void
add(int
i, T
t) {
delegate.
add(
i,
t);
}
public T
remove(int
i) {
return
delegate.
remove(
i);
}
public int
indexOf(
Object o) {
return
delegate.
indexOf(
o);
}
public int
lastIndexOf(
Object o) {
return
delegate.
lastIndexOf(
o);
}
public
ListIterator<T>
listIterator() {
return
delegate.
listIterator();
}
public
ListIterator<T>
listIterator(int
i) {
return
delegate.
listIterator(
i);
}
@
Override
public boolean
equals(
Object obj) {
return
delegate.
equals(
obj);
}
@
Override
public int
hashCode() {
return
delegate.
hashCode();
}
/**
* Returns a view of a portion of this list. This method returns a list with the same
* lazy list settings as the original list.
*
* @param fromIndex low endpoint of the subList (inclusive)
* @param toIndex upper endpoint of the subList (exclusive)
* @return a view of a specified range within this list, keeping all lazy list settings
*/
public
ListWithDefault<T>
subList(int
fromIndex, int
toIndex) {
return new
ListWithDefault<T>(
delegate.
subList(
fromIndex,
toIndex),
lazyDefaultValues, (
Closure)
initClosure.
clone());
}
}