/*
* Copyright 2015 Victor Albertos
*
* 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 io.rx_cache2;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Provides a set of actions in order to perform write operations on lists with providers in a more
* easy and safely way.
*/
public class ActionsList<T> {
protected Observable<List<T>> cache;
protected final Evict<T> evict;
public ActionsList(Evict<T> evict, Observable<List<T>> cache) {
this.evict = evict;
this.cache = cache;
}
/**
* Static accessor to create an instance of Actions in order follow the "builder" pattern.
*
* @param evict The implementation of Evict interface which allow to persists the changes.
* @param cache An observable which is the result of calling the provider without evicting its
* data.
* @param <T> the type of the element of the list to be processed.
* @return an instance of Actions.
*/
public static <T> ActionsList<T> with(Evict<T> evict, Observable<List<T>> cache) {
return new ActionsList<T>(evict, cache);
}
/**
* Func2 will be called for every iteration until its condition returns true. When true, the
* element is added to the cache at the position of the current iteration.
*
* @param func2 exposes the position of the current iteration and the count of elements in the
* cache.
* @param element the object to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> add(Func2 func2, T element) {
return addAll(func2, Arrays.asList(element));
}
/**
* Add the object at the first position of the cache.
*
* @param element the object to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> addFirst(T element) {
Func2 first = new Func2() {
@Override public boolean call(int position, int count) {
return position == 0;
}
};
return addAll(first, Arrays.asList(element));
}
/**
* Add the object at the last position of the cache.
*
* @param element the object to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> addLast(T element) {
Func2 last = new Func2() {
@Override public boolean call(int position, int count) {
return position == count;
}
};
return addAll(last, Arrays.asList(element));
}
/**
* Add the objects at the first position of the cache.
*
* @param elements the objects to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> addAllFirst(List<T> elements) {
Func2 first = new Func2() {
@Override public boolean call(int position, int count) {
return position == 0;
}
};
return addAll(first, elements);
}
/**
* Add the objects at the last position of the cache.
*
* @param elements the objects to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> addAllLast(List<T> elements) {
Func2 last = new Func2() {
@Override public boolean call(int position, int count) {
return position == count;
}
};
return addAll(last, elements);
}
/**
* Func2 will be called for every iteration until its condition returns true. When true, the
* elements are added to the cache at the position of the current iteration.
*
* @param func2 exposes the position of the current iteration and the count of elements in the
* cache.
* @param elements the objects to addOrUpdate to the cache.
* @return itself
*/
public ActionsList<T> addAll(final Func2 func2, final List<T> elements) {
cache = cache.map(new Function<List<T>, List<T>>() {
@Override public List<T> apply(List<T> items) throws Exception {
int count = items.size();
for (int position = 0; position <= count; position++) {
if (func2.call(position, count)) {
items.addAll(position, elements);
break;
}
}
return items;
}
});
return this;
}
/**
* Evict object at the first position of the cache
*
* @return itself
*/
public ActionsList<T> evictFirst() {
Func3<T> first = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return position == 0;
}
};
return evict(first);
}
/**
* Evict as much objects as requested by n param starting from the first position.
*
* @param n the amount of elements to evict.
* @return itself
*/
public ActionsList<T> evictFirstN(final int n) {
return evictFirstN(new Func1Count() {
@Override public boolean call(int count) {
return true;
}
}, n);
}
/**
* Evict object at the last position of the cache.
*
* @return itself
*/
public ActionsList<T> evictLast() {
Func3<T> last = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return position == count - 1;
}
};
return evict(last);
}
/**
* Evict as much objects as requested by n param starting from the last position.
*
* @param n the amount of elements to evict.
* @return itself
*/
public ActionsList<T> evictLastN(final int n) {
return evictLastN(new Func1Count() {
@Override public boolean call(int count) {
return true;
}
}, n);
}
/**
* Evict object at the first position of the cache.
*
* @param func1Count exposes the count of elements in the cache.
* @return itself
*/
public ActionsList<T> evictFirst(final Func1Count func1Count) {
Func3<T> firstPlusFunc1 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return position == 0 && func1Count.call(count);
}
};
return evict(firstPlusFunc1);
}
/**
* Evict as much objects as requested by n param starting from the first position.
*
* @param func1Count exposes the count of elements in the cache.
* @param n the amount of elements to evict.
* @return itself
*/
public ActionsList<T> evictFirstN(final Func1Count func1Count, final int n) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return position < n && func1Count.call(count);
}
};
return evictIterable(func3);
}
/**
* Evict object at the last position of the cache.
*
* @param func1Count exposes the count of elements in the cache.
* @return itself
*/
public ActionsList<T> evictLast(final Func1Count func1Count) {
Func3<T> lastPlusFunc1 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return position == count - 1 && func1Count.call(count);
}
};
return evict(lastPlusFunc1);
}
boolean startToEvict;
/**
* Evict as much objects as requested by n param starting from the last position.
*
* @param func1Count exposes the count of elements in the cache.
* @param n the amount of elements to evict.
* @return itself
*/
public ActionsList<T> evictLastN(final Func1Count func1Count, final int n) {
startToEvict = false;
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
if (!startToEvict) startToEvict = count - position == n;
if (startToEvict) {
return count - position <= n && func1Count.call(count);
} else {
return false;
}
}
};
return evictIterable(func3);
}
/**
* Func1Element will be called for every iteration until its condition returns true. When true,
* the element of the current iteration is evicted from the cache.
*
* @param func1Element exposes the element of the current iteration.
* @return itself
*/
public ActionsList<T> evict(final Func1Element<T> func1Element) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return func1Element.call(element);
}
};
return evict(func3);
}
/**
* Func3 will be called for every iteration until its condition returns true. When true, the
* element of the current iteration is evicted from the cache.
*
* @param func3 exposes the position of the current iteration, the count of elements in the cache
* and the element of the current iteration.
* @return itself
*/
public ActionsList<T> evict(final Func3<T> func3) {
cache = cache.map(new Function<List<T>, List<T>>() {
@Override public List<T> apply(List<T> elements) throws Exception {
int count = elements.size();
for (int position = 0; position < count; position++) {
if (func3.call(position, count, elements.get(position))) {
elements.remove(position);
break;
}
}
return elements;
}
});
return this;
}
/**
* Evict all elements from the cache
*
* @return itself
*/
public ActionsList<T> evictAll() {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return true;
}
};
return evictIterable(func3);
}
/**
* Evict elements from the cache starting from the first position until its count is equal to the
* value specified in n param.
*
* @param n the amount of elements to keep from evict.
* @return itself
*/
public ActionsList<T> evictAllKeepingFirstN(final int n) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
int positionToStartEvicting = count - (count - n);
return position >= positionToStartEvicting;
}
};
return evictIterable(func3);
}
/**
* Evict elements from the cache starting from the last position until its count is equal to the
* value specified in n param.
*
* @param n the amount of elements to keep from evict.
* @return itself
*/
public ActionsList<T> evictAllKeepingLastN(final int n) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
int elementsToEvict = count - n;
return position < elementsToEvict;
}
};
return evictIterable(func3);
}
/**
* Func3 will be called for every iteration. When true, the element of the current iteration is
* evicted from the cache.
*
* @param func3 exposes the position of the current iteration, the count of elements in the cache
* and the element of the current iteration.
* @return itself
*/
public ActionsList<T> evictIterable(final Func3<T> func3) {
cache = cache.map(new Function<List<T>, List<T>>() {
@Override public List<T> apply(List<T> elements) throws Exception {
int count = elements.size();
for (int position = 0; position < count; position++) {
if (func3.call(position, count, elements.get(position))) {
elements.set(position, null);
}
}
elements.removeAll(Collections.singleton(null));
return elements;
}
});
return this;
}
/**
* Func1Element will be called for every iteration until its condition returns true. When true,
* the element of the current iteration is updated.
*
* @param func1Element exposes the element of the current iteration.
* @param replace exposes the original element and expects back the one modified.
* @return itself
*/
public ActionsList<T> update(final Func1Element<T> func1Element, Replace<T> replace) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return func1Element.call(element);
}
};
return update(func3, replace);
}
/**
* Func3 will be called for every iteration until its condition returns true. When true, the
* element of the current iteration is updated.
*
* @param func3 exposes the position of the current iteration, the count of elements in the cache
* and the element of the current iteration.
* @param replace exposes the original element and expects back the one modified.
* @return itself
*/
public ActionsList<T> update(final Func3<T> func3, final Replace<T> replace) {
cache = cache.map(new Function<List<T>, List<T>>() {
@Override public List<T> apply(List<T> elements) throws Exception {
int count = elements.size();
for (int position = 0; position < count; position++) {
if (func3.call(position, count, elements.get(position))) {
elements.set(position, replace.call(elements.get(position)));
break;
}
}
return elements;
}
});
return this;
}
/**
* Func1Element will be called for every. When true, the element of the current iteration is
* updated.
*
* @param func1Element exposes the element of the current iteration.
* @param replace exposes the original element and expects back the one modified.
* @return itself
*/
public ActionsList<T> updateIterable(final Func1Element<T> func1Element, Replace<T> replace) {
Func3<T> func3 = new Func3<T>() {
@Override public boolean call(int position, int count, T element) {
return func1Element.call(element);
}
};
return updateIterable(func3, replace);
}
/**
* Func3 will be called for every iteration. When true, the element of the current iteration is
* updated.
*
* @param func3 exposes the position of the current iteration, the count of elements in the cache
* and the element of the current iteration.
* @param replace exposes the original element and expects back the one modified.
* @return itself
*/
public ActionsList<T> updateIterable(final Func3<T> func3, final Replace<T> replace) {
cache = cache.map(new Function<List<T>, List<T>>() {
@Override public List<T> apply(List<T> elements) throws Exception {
int count = elements.size();
for (int position = 0; position < count; position++) {
if (func3.call(position, count, elements.get(position))) {
elements.set(position, replace.call(elements.get(position)));
}
}
return elements;
}
});
return this;
}
public Observable<List<T>> toObservable() {
return evict.call(cache);
}
public interface Evict<T> {
Observable<List<T>> call(final Observable<List<T>> elements);
}
public interface Func1Count {
boolean call(final int count);
}
public interface Func1Element<T> {
boolean call(final T element);
}
public interface Func2 {
boolean call(final int position, final int count);
}
public interface Func3<T> {
boolean call(final int position, final int count, final T element);
}
public interface Replace<T> {
T call(T element);
}
}