org.semanticweb.elk.util.collections.RecencyEvictor Maven / Gradle / Ivy
/*-
* #%L
* ELK Utilities Collections
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2011 - 2017 Department of Computer Science, University of Oxford
* %%
* 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.
* #L%
*/
package org.semanticweb.elk.util.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import org.semanticweb.elk.util.statistics.Stat;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
/**
* Evicts least recent elements after the capacity is exceeded.
* Eviction is trying to reduce the number of elements in this evictor to
* capacity times load factor. However, it still does not evict the
* elements that should be retained.
*
* @author Peter Skocovsky
*
* @param
* The type of the elements.
*/
public class RecencyEvictor extends AbstractEvictor {
private static final int DEFAULT_ELEMENTS_CAPACITY_ = Builder.DEFAULT_CAPACITY;
private static final float DEFAULT_ELEMENTS_LOAD_FACTOR_ = 0.75f;
private final LinkedHashMap elements_ = new LinkedHashMap(
DEFAULT_ELEMENTS_CAPACITY_, DEFAULT_ELEMENTS_LOAD_FACTOR_, true);
private final double loadFactor_;
private int capacity_;
RecencyEvictor(final int capacity, final double loadFactor) {
this.capacity_ = capacity;
this.loadFactor_ = loadFactor;
this.stats = new Stats();
}
@Override
public void add(final E element) {
elements_.put(element, true);
}
@Override
public Iterator evict(final Predicate retain) {
Preconditions.checkNotNull(retain);
if (elements_.size() <= capacity_) {
// Evict nothing.
return Collections. emptyList().iterator();
}
// else
final int goalCapacity = (int) (capacity_ * loadFactor_);
final List evicted = new ArrayList(goalCapacity < elements_.size()
? elements_.size() - goalCapacity : 0);
final Iterator iterator = elements_.keySet().iterator();
while (iterator.hasNext() && elements_.size() > goalCapacity) {
final E element = iterator.next();
if (!retain.apply(element)) {
evicted.add(element);
iterator.remove();
}
}
return evicted.iterator();
}
public int getCapacity() {
return capacity_;
}
public void setCapacity(final int capacity) {
if (0 > capacity) {
throw new IllegalArgumentException("Capacity cannot be negative!");
}
this.capacity_ = capacity;
}
public int size() {
return elements_.size();
}
protected static abstract class ProtectedBuilder> {
public static final int DEFAULT_CAPACITY = 128;
public static final double DEFAULT_LOAD_FACTOR = 0.75;
protected int capacity_ = DEFAULT_CAPACITY;
protected double loadFactor_ = DEFAULT_LOAD_FACTOR;
/**
* When the provided capacity is exceeded, elements will be evicted.
* Capacity not must be negative!
*
* If not called, capacity defaults to {@link #DEFAULT_CAPACITY}.
*
* @param capacity
* The capacity of the evictor.
* @return This builder.
* @throws IllegalArgumentException
* When the argument is negative.
*/
public B capacity(final int capacity) throws IllegalArgumentException {
if (0 > capacity) {
throw new IllegalArgumentException(
"Capacity cannot be negative!");
}
this.capacity_ = capacity;
return convertThis();
}
/**
* Load factor is the proportion of the capacity that should be achieved
* when evicting. Eviction is trying to reduce the number of elements in
* this evictor to capacity times load factor. Load factor must be
* between 0 and 1 inclusive!
*
* If not called, load factor defaults to {@link #DEFAULT_LOAD_FACTOR}.
*
* @param loadFactor
* The load factor of this evictor.
* @return This builder.
* @throws IllegalArgumentException
* When the argument is not between 0 and 1 inclusive.
*/
public B loadFactor(final double loadFactor)
throws IllegalArgumentException {
if (0 > loadFactor || loadFactor > 1) {
throw new IllegalArgumentException(
"Load factor must be between 0 and 1 inclusive!");
}
this.loadFactor_ = loadFactor;
return convertThis();
}
public Evictor build() {
return new RecencyEvictor(capacity_, loadFactor_);
}
protected abstract B convertThis();
}
public static class Builder extends ProtectedBuilder
implements Evictor.Builder {
@Override
protected Builder convertThis() {
return this;
}
public static Builder valueOf(final String value) {
final String[] args = Evictors.parseArgs(value,
RecencyEvictor.class, 2);
final String capacityArg = args[0].trim();
final String loadFactorArg = args[1].trim();
final int capacity = capacityArg.isEmpty() ? DEFAULT_CAPACITY
: Integer.valueOf(capacityArg);
final double loadFactor = loadFactorArg.isEmpty()
? DEFAULT_LOAD_FACTOR : Double.valueOf(loadFactorArg);
return new Builder()
.capacity(capacity < 0 ? Integer.MAX_VALUE : capacity)
.loadFactor(loadFactor);
}
@Override
public String toString() {
return String.format("%s(%d,%f)", RecencyEvictor.class.getName(),
capacity_, loadFactor_);
}
}
// Stats.
protected class Stats {
@Stat
public int capacity() {
return getCapacity();
}
@Stat
public int size() {
return RecencyEvictor.this.size();
}
}
}