/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the Software), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.lanternpowered.server.util.collect;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
import org.lanternpowered.server.util.collect.expirable.ExpirableValue;
import org.lanternpowered.server.util.collect.expirable.ExpirableValueList;
import org.lanternpowered.server.util.collect.expirable.SimpleExpirableValue;
import org.spongepowered.api.util.annotation.NonnullByDefault;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;
@NonnullByDefault
public final class Lists2 {
public static <V, B extends ExpirableValue<V>> ExpirableValueList<V, B> createExpirableValueList(Function<V, B> backValueSupplier) {
return new ExpirableValueListImpl<>(Lists.newArrayList(), backValueSupplier);
}
public static <V, B extends ExpirableValue<V>> ExpirableValueList<V, B> createExpirableValueListWithPredicate(
Predicate<V> expirationChecker) {
// Casting weirdness...
//noinspection unchecked
return new ExpirableValueListImpl<>(Lists.newArrayList(), value -> (B) new PredicateExpirableValue(value, expirationChecker));
}
public static <V, B extends ExpirableValue<V>> ExpirableValueList<V, B> createCopyOnWriteExpirableValueList(Function<V, B> backValueSupplier) {
return new ExpirableValueListImpl<>(Lists.newCopyOnWriteArrayList(), backValueSupplier);
}
public static <V, B extends ExpirableValue<V>> ExpirableValueList<V, B> createCopyOnWriteExpirableValueListWithPredicate(
Predicate<V> expirationChecker) {
// Casting weirdness...
//noinspection unchecked
return new ExpirableValueListImpl<>(Lists.newArrayList(), value -> (B) new PredicateExpirableValue(value, expirationChecker));
}
private static class PredicateExpirableValue<V> extends SimpleExpirableValue<V> {
private final Predicate<V> predicate;
public PredicateExpirableValue(V value, Predicate<V> predicate) {
super(value);
this.predicate = predicate;
}
@Override
public boolean isExpired() {
return this.predicate.test(this.getValue());
}
}
private static class ExpirableValueListImpl<V, B extends ExpirableValue<V>> extends AbstractList<V> implements ExpirableValueList<V, B> {
private final List<B> backing;
private final Function<V, B> backValueSupplier;
public ExpirableValueListImpl(List<B> backing, Function<V, B> backValueSupplier) {
this.backValueSupplier = backValueSupplier;
this.backing = backing;
}
private void clean(int endIndex) {
final Iterator<B> it = this.backing.iterator();
while (it.hasNext() && endIndex >= 0) {
final B value = it.next();
if (value.isExpired()) {
it.remove();
} else {
endIndex--;
}
}
}
@Nullable
@Override
public V set(int index, V element) {
this.clean(index);
final B old = this.backing.set(index, this.backValueSupplier.apply(element));
return old == null ? null : old.getValue();
}
@Override
public void add(int index, V element) {
this.clean(index);
this.backing.add(index, this.backValueSupplier.apply(element));
}
@Nullable
@Override
public V remove(int index) {
this.clean(index);
final B old = this.backing.remove(index);
return old == null ? null : old.getValue();
}
@Override
public V get(int index) {
final Iterator<B> it = this.backing.iterator();
while (it.hasNext()) {
final B value = it.next();
if (value.isExpired()) {
it.remove();
} else if (--index < 0) {
return value.getValue();
}
}
throw new IndexOutOfBoundsException();
}
@Override
public int size() {
final Iterator<B> it = this.backing.iterator();
int size = 0;
while (it.hasNext()) {
final B value = it.next();
if (value.isExpired()) {
it.remove();
} else {
size++;
}
}
return size;
}
@Override
public List<B> getBacking() {
return this.backing;
}
}
/**
* Creates a non null list for the specified list, all the operations
* should be done through the new list.
*
* @param list the list
* @return the non null list
*/
public static <T> List<T> nonNullOf(List<T> list) {
return checkedOf(list, e -> checkNotNull(e, "element"));
}
/**
* Creates a checked list for the specified backing list, all the operations
* should be done through the new list.
*
* @param list the backing list
* @param checker the checker that should be used to validate the added values
* @return the checked list
*/
public static <T> List<T> checkedOf(List<T> list, Consumer<T> checker) {
return new CheckedList<>(checkNotNull(list, "list"), checkNotNull(checker, "checker"));
}
private static class CheckedListIterator<T> implements ListIterator<T> {
private final ListIterator<T> backing;
private final Consumer<T> checker;
public CheckedListIterator(ListIterator<T> backing, Consumer<T> checker) {
this.checker = checker;
this.backing = backing;
}
@Override
public boolean hasNext() {
return this.backing.hasNext();
}
@Override
public T next() {
return this.backing.next();
}
@Override
public boolean hasPrevious() {
return this.backing.hasPrevious();
}
@Override
public T previous() {
return this.backing.previous();
}
@Override
public int nextIndex() {
return this.backing.nextIndex();
}
@Override
public int previousIndex() {
return this.backing.previousIndex();
}
@Override
public void remove() {
this.backing.remove();
}
@Override
public void set(T e) {
this.checker.accept(e);
this.backing.set(e);
}
@Override
public void add(T e) {
this.checker.accept(e);
this.backing.add(e);
}
}
private static class CheckedList<T> implements List<T> {
private final List<T> backing;
private final Consumer<T> checker;
public CheckedList(List<T> backing, Consumer<T> checker) {
this.checker = checker;
this.backing = backing;
}
@Override
public int size() {
return this.backing.size();
}
@Override
public boolean isEmpty() {
return this.backing.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.backing.contains(o);
}
@Override
public Iterator<T> iterator() {
return new CheckedListIterator<>(this.backing.listIterator(), this.checker);
}
@Override
public Object[] toArray() {
return this.backing.toArray();
}
@Override
public <V> V[] toArray(V[] a) {
return this.backing.toArray(a);
}
@Override
public boolean add(T e) {
this.checker.accept(e);
return this.backing.add(e);
}
@Override
public boolean remove(Object o) {
return this.backing.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return this.backing.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends T> c) {
c.forEach(this.checker::accept);
return this.backing.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
c.forEach(this.checker::accept);
return this.backing.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
return this.backing.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return this.backing.retainAll(c);
}
@Override
public void clear() {
this.backing.clear();
}
@Override
public T get(int index) {
return this.backing.get(index);
}
@Override
public T set(int index, T e) {
this.checker.accept(e);
return this.backing.set(index, e);
}
@Override
public void add(int index, T e) {
this.checker.accept(e);
this.backing.add(index, e);
}
@Override
public T remove(int index) {
return this.backing.remove(index);
}
@Override
public int indexOf(Object o) {
return this.backing.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return this.backing.lastIndexOf(o);
}
@Override
public ListIterator<T> listIterator() {
return new CheckedListIterator<>(this.backing.listIterator(), this.checker);
}
@Override
public ListIterator<T> listIterator(int index) {
return new CheckedListIterator<>(this.backing.listIterator(index), this.checker);
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return new CheckedList<>(this.backing.subList(fromIndex, toIndex), this.checker);
}
}
private Lists2() {
}
}