//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.common.files;
import static com.github.protobufel.common.verifications.Verifications.*;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
class HistoryCaches {
private HistoryCaches() {
}
private static final class FakeHistoryCache<K, V> implements IHistoryCache<K, V> {
private volatile int size;
FakeHistoryCache() {
}
FakeHistoryCache(final int initialSize) {
this.size = initialSize;
}
@Override
public void clear() {
size = 0;
}
@Override
public void pop() {
if (size > 0) {
size--;
}
}
@Override
public void pop(int historySize) {
size = (size > historySize) ? (size - historySize) : 0;
}
@Override
public void push() {
size++;
}
@Override
public IHistoryCacheView<K, V> getCacheView() {
return new IHistoryCacheView<K, V>() {
@Override
public boolean setCachedValue(K key, V value) {
return false;
}
@Override
public @Nullable V getCachedValue(K key) {
return null;
}
@Override
public int currentDepth() {
return size;
}
};
}
@Override
public int size() {
return size;
}
}
public static <K, V> IHistoryCache<K, V> fakeInstance() {
return new FakeHistoryCache<K, V>();
}
public static <K, V> IHistoryCache<K, V> fakeInstance(final int initialSize) {
return new FakeHistoryCache<K, V>(initialSize);
}
static class HistoryCache<K, V> implements IHistoryCache<K, V> {
private static final HistoryCache<?, ?> EMPTY = new HistoryCache<Object, Object>();
private final Deque<Map<K, V>> history;
private int maxSize;
private Map<K, V> prevCache;
private final IHistoryCacheView<K, V> cacheView;
private HistoryCache() {
// virtually unmodifiable; ideally would be Collections.umodifiableDeque()!
this.history = new LinkedList<>();
this.maxSize = 0;
@SuppressWarnings("null")
@NonNull Map<K, V> emptyMap = Collections.emptyMap();
this.prevCache = emptyMap;
this.cacheView = new IHistoryCacheView<K, V>() {
@Override
public boolean setCachedValue(K key, V value) {
return false;
}
@Override
public @Nullable V getCachedValue(K key) {
return null;
}
@Override
public int currentDepth() {
return -1;
}
};
}
public HistoryCache(int maxSize) {
@SuppressWarnings("null")
final @NonNull Integer maxSizeInteger = maxSize;
this.maxSize = verifyArgument(maxSize > 0, maxSizeInteger);
this.history = new LinkedList<>();
this.history.add(new IdentityHashMap<K, V>());
@SuppressWarnings("null")
@NonNull Map<K, V> emptyMap = Collections.emptyMap();
this.prevCache = emptyMap;
this.cacheView = new IHistoryCacheView<K, V>() {
@Override
public boolean setCachedValue(K key, V value) {
return HistoryCache.this.setValue(key, value);
}
@Override
public @Nullable V getCachedValue(K key) {
return HistoryCache.this.getValue(key);
}
@Override
public int currentDepth() {
return HistoryCache.this.size();
}
};
}
@SuppressWarnings("unchecked")
public static final <K, V> IHistoryCache<K, V> emptyInstance() {
return (IHistoryCache<K, V>) EMPTY;
}
@Override
public IHistoryCacheView<K, V> getCacheView() {
return cacheView;
}
@Override
public int size() {
if (this == EMPTY) {
return 0;
}
return history.size();
}
private boolean setValue(final K key, final V value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
if (this == EMPTY) {
return false;
}
history.getFirst().put(key, value);
return true;
}
private @Nullable V getValue(final K key) {
Objects.requireNonNull(key);
if (this == EMPTY) {
return null;
}
return prevCache.get(key);
}
@Override
public void clear() {
if (this == EMPTY) {
return;
}
if (history.size() == 1) {
history.getFirst().clear();
} else {
history.clear();
history.addFirst(new IdentityHashMap<K, V>());
}
@SuppressWarnings("null")
@NonNull Map<K, V> emptyMap = Collections.emptyMap();
prevCache = emptyMap;
}
@Override
public void pop() {
if (this == EMPTY) {
return;
}
prevCache = assertNonNull(history.removeFirst());
if (history.size() == 0) {
history.addFirst(new IdentityHashMap<K, V>());
}
}
@Override
public void pop(final int historySize) {
verifyCondition(historySize > 0, "historySize must be positive");
if (this == EMPTY) {
return;
}
final int size = Math.min(history.size(), historySize) - 1;
for (int i = 0; i < size; i++) {
history.removeFirst();
}
@SuppressWarnings("null")
final @NonNull Map<K, V> removeFirst = history.removeFirst();
prevCache = removeFirst;
if (history.size() == 0) {
history.addFirst(new IdentityHashMap<K, V>());
}
}
@Override
public void push() {
if (this == EMPTY) {
return;
}
prevCache = assertNonNull(history.getFirst());
if ((maxSize > 1) && (history.size() == maxSize)) {
history.removeLast();
}
history.addFirst(new IdentityHashMap<K, V>());
}
}
static class SimpleEntryHistoryCache<K, V> {
protected static final SimpleEntryHistoryCache<?, ?> EMPTY = new SimpleEntryHistoryCache<Object, Object>();
private final Deque<Entry<K, V>> history;
private int maxSize;
private SimpleEntryHistoryCache() {
// virtually unmodifiable; ideally would be Collections.umodifiableDeque()!
this.history = new LinkedList<>();
this.maxSize = 0;
}
public SimpleEntryHistoryCache(int maxSize) {
this.maxSize = verifyArgument(maxSize > 0, maxSize);
this.history = new LinkedList<>();
}
@SuppressWarnings("unchecked")
public static <K, V> SimpleEntryHistoryCache<K, V> emptyCache() {
return (SimpleEntryHistoryCache<K, V>) EMPTY;
}
public void push(final K key, final V value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
if (this == EMPTY) {
return;
}
if ((maxSize > 1) && (history.size() == maxSize)) {
history.removeLast();
}
history.addFirst(new SimpleImmutableEntry<>(key, value));
}
public void clear() {
if (this == EMPTY) {
return;
}
history.clear();
}
public @Nullable Entry<K, V> peek() {
if (this == EMPTY) {
return null;
}
return history.peekFirst();
}
public void pop() {
if (this == EMPTY) {
return;
}
history.pollFirst();
}
public void pop(final int historySize) {
verifyCondition(historySize > 0, "historySize must be positive");
if (this == EMPTY) {
return;
}
final int size = Math.min(history.size(), historySize) - 1;
for (int i = 0; i < size; i++) {
history.removeFirst();
}
history.removeFirst();
}
public void adjustCache(final int historySize) {
verifyCondition((historySize - history.size() <= 1),
"historySize cannot exceed cache size by 1");
if (historySize < history.size()) {
pop(historySize);
}
}
public boolean isEmpty() {
return history.isEmpty();
}
}
static class SimpleHistoryCache<T> {
protected static final SimpleHistoryCache<?> EMPTY = new SimpleHistoryCache<Object>();
private final Deque<PositionValue<T>> history;
private int maxSize;
private int currentPos;
private SimpleHistoryCache() {
// virtually unmodifiable; ideally would be Collections.umodifiableDeque()!
this.history = new LinkedList<>();
this.maxSize = 0;
this.currentPos = -1;
}
public SimpleHistoryCache(int maxSize) {
this.maxSize = verifyArgument(maxSize > 0, maxSize);
this.history = new LinkedList<>();
this.currentPos = -1;
}
@SuppressWarnings("unchecked")
public static <T> SimpleHistoryCache<T> emptyCache() {
return (SimpleHistoryCache<T>) EMPTY;
}
public void push(final T value) {
if (this == EMPTY) {
return;
}
verifyCondition(currentPos != -1, "call adjustCache before calling push");
if ((maxSize > 1) && (history.size() == maxSize)) {
history.removeLast();
}
history.addFirst(new PositionValue<T>(currentPos, value));
}
public void clear() {
if (this == EMPTY) {
return;
}
history.clear();
currentPos = -1;
}
public @Nullable T peek() {
if ((this == EMPTY) || history.isEmpty()) {
return null;
}
return history.peekFirst().getValue();
}
public @Nullable T pop() {
if ((this == EMPTY) || history.isEmpty()) {
return null;
}
return history.pollFirst().getValue();
}
public @Nullable T pop(final int historySize) {
verifyCondition(historySize > 0, "historySize must be positive");
currentPos = -1;
final int historyDepth = getHistoryDepth();
if ((this == EMPTY) || history.isEmpty() || (historyDepth <= historySize)) {
return null;
}
PositionValue<T> element = history.removeFirst();
while (!history.isEmpty() && (element.getPosition() > historySize)) {
element = history.removeFirst();
}
return element.getValue();
}
public void adjustCache(final int historySize) {
if (historySize < history.size()) {
pop(historySize);
}
currentPos = historySize;
}
public int getHistoryDepth() {
return history.isEmpty() ? 0 : history.peekFirst().getPosition();
}
public boolean isEmpty() {
return history.isEmpty();
}
}
private static final class PositionValue<T> {
private final int position;
private final T value;
public PositionValue(int position, T value) {
this.position = position;
this.value = value;
}
public int getPosition() {
return position;
}
public T getValue() {
return value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + position;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@NonNullByDefault(false)
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof PositionValue)) {
return false;
}
PositionValue<?> other = (PositionValue<?>) obj;
if (position != other.position) {
return false;
}
if (!value.equals(other.value)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PositionValue [position=").append(position).append(", value=").append(value)
.append("]");
@SuppressWarnings("null")
@NonNull String string = builder.toString();
return string;
}
}
}