All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.util.LastRecentlyUsed Maven / Gradle / Ivy

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
 * Institute of Molecular Cell Biology and Genetics.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. 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.
 * 
 * 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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS 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.
 * #L%
 */

package org.scijava.util;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * A simple container for {@code N} last-recently-used items.
 * 
 * @author Johannes Schindelin
 */
public class LastRecentlyUsed implements Collection {
	private final Object[] entries;
	private final Map map;
	/**
	 * The double-linked list pointers.
	 * 

* The {@code top} variable points to the most recently added, the * {@code bottom} variable to the oldest entry. The {@code next} and * {@code previous} arrays point to the next newer/next older entry. *

*

* For initialization performance, all of {@code next}, {@code previous}, * {@code top} and {@code bottom} are initialized to {@code 0}, meaning that * you need to decrement the values by one in order to obtain the entry index. * Example: the index of the most recently added entry is {@code top -1}, and * {@code next[top - 1]} is {@code 0} because there is no newer entry than the * newest entry. *

*/ private final int[] next, previous; private int top, bottom; public LastRecentlyUsed(int size) { entries = new Object[2 * size]; next = new int[2 * size]; previous = new int[2 * size]; map = new HashMap<>(); } /** * Given the index of an entry, returns the index of the next newer entry. * * @param index the index of the current entry, or -1 to wrap around to the oldest entry. * @return the index of the next newer entry, or -1 when there is no such entry. */ public int next(int index) { return index < 0 ? bottom - 1 : next[index] - 1; } /** * Given the index of an entry, returns the index of the next older entry. * * @param index the index of the current entry, or -1 to wrap around to the newest entry. * @return the index of the next older entry, or -1 when there is no such entry. */ public int previous(int index) { return index < 0 ? top - 1 : previous[index] - 1; } /** * Returns the entry for the given index. * * @param index the index of the entry * @return the entry */ @SuppressWarnings("unchecked") public T get(int index) { return (T) entries[index]; } /** * Looks up the index for a given entry. * * @param value the value of the entry to find * @return the corresponding index, or {@code -1} if the entry was not found */ public int lookup(final T value) { final Integer result = map.get(value); return result == null ? -1 : (int) result; } /** * Add a new newest entry. * * @param value the value of the entry * @return whether the entry was added */ @Override public boolean add(final T value) { return add(value, false); } /** * Add a new oldest entry. *

* This method helps recreating {@link LastRecentlyUsed} instances given the * entries in the order newest first, oldest last. *

* * @param value the value of the entry to add */ public void addToEnd(final T value) { add(value, true); } public boolean replace(final int index, T newValue) { final Object previousValue = get(index); if (previousValue == null) { throw new IllegalArgumentException("No current entry at position " + index); } if (newValue.equals(previous)) return false; map.remove(previousValue); map.put(newValue, index); entries[index] = newValue; return true; } /** * Empties the data structure. */ @Override public void clear() { top = bottom = 0; map.clear(); for (int i = 0; i < entries.length; i++) { entries[i] = null; next[i] = previous[i] = 0; } } @Override public boolean addAll(final Collection values) { for (final T value : values) { add(value); } return true; } @Override public boolean contains(final Object value) { return map.containsKey(value); } @Override public boolean containsAll(final Collection values) { return map.keySet().containsAll(values); } @Override public boolean isEmpty() { return top == 0; } @Override public boolean remove(Object value) { final Integer index = map.get(value); if (index == null) return false; remove(index.intValue()); return true; } @Override public boolean removeAll(Collection values) { boolean result = true; for (final Object value : values) { result = remove(value) && result; } return result; } @Override public boolean retainAll(Collection values) { for (int index = top - 1; index >= 0; ) { final int prev = previous[index] - 1; if (!values.contains(get(index))) { remove(index); } index = prev; } return containsAll(values); } @Override public int size() { return map.size(); } @Override public Object[] toArray() { final Object[] result = new Object[size()]; for (int i = 0, index = top - 1; index >= 0; i++, index = previous[index] - 1) { result[i] = get(index); } return result; } @SuppressWarnings("unchecked") @Override public S[] toArray(final S[] array) { final int size = size(); if (array.length >= size) { for (int i = 0, index = top - 1; index >= 0; i++, index = previous[index] - 1) { array[i] = (S) get(index); } return array; } final S[] result = (S[]) Array.newInstance(array.getClass().getComponentType(), size); for (int i = 0, index = top - 1; index >= 0; i++, index = previous[index] - 1) { result[i] = (S) get(index); } return result; } /** * Returns an {@link Iterator}. * * @return the iterator */ @Override public Iterator iterator() { return new Iterator() { private int position = top - 1; @Override public boolean hasNext() { return position >= 0; } @Override public T next() { @SuppressWarnings("unchecked") final T result = (T) entries[position]; position = previous[position] - 1; return result; } @Override public void remove() { LastRecentlyUsed.this.remove(position == 0 ? top - 1 : next[position] - 1); } }; } // -- private methods private void remove(int position) { assert(entries[position] != null); map.remove(entries[position]); entries[position] = null; if (next[position] == 0) { top = previous[position]; } else { previous[next[position] - 1] = previous[position]; } if (previous[position] == 0) { bottom = next[position]; } else { next[previous[position] - 1] = next[position]; } next[position] = previous[position] = 0; } private boolean add(final T value, boolean addAtEnd) { final Integer existing = map.get(value); int insert; if (existing != null) { insert = existing; remove(insert); } else if (map.size() == entries.length / 2) { insert = bottom - 1; remove(insert); } else { insert = value.hashCode() % entries.length; if (insert < 0) insert += entries.length; while (insert < entries.length && entries[insert] != null) insert++; } add(insert, value, addAtEnd); return existing == null; } private void add(int position, T value, boolean atEnd) { assert(next[position] == 0); assert(previous[position] == 0); assert(entries[position] == null); map.put(value, position); entries[position] = value; if (atEnd) { next[position] = bottom; if (bottom > 0) previous[bottom - 1] = position + 1; bottom = position + 1; if (top == 0) top = bottom; } else { previous[position] = top; if (top > 0) { next[top - 1] = position + 1; } top = position + 1; if (bottom == 0) bottom = top; } } // For testing protected void assertConsistency() { if (top == 0) { assert(bottom == 0); assert(map.size() == 0); for (int i = 0; i < entries.length; i++) { assert(entries[i] == null); assert(next[i] == 0); assert(previous[i] == 0); } return; } assert(bottom != 0); final Set indices = new HashSet<>(map.values()); assert(indices.size() == map.size()); for (int i = 0; i < entries.length; i++) { if (indices.contains(i)) { assert(entries[i] != null); assert(map.get(entries[i]) == i); if (i == top - 1 || top == bottom) { assert(next[i] == 0); } else { assert(next[i] > 0); assert(previous[next[i] - 1] == i + 1); } if (i == bottom - 1 || top == bottom) { assert(previous[i] == 0); } else { assert(previous[i] > 0); assert(next[previous[i] - 1] == i + 1); } } else { assert(entries[i] == null); assert(next[i] == 0); assert(previous[i] == 0); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy