org.scijava.object.ObjectIndex Maven / Gradle / Ivy
Show all versions of scijava-common Show documentation
/*
* #%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, Max Planck
* Institute of Molecular Cell Biology and Genetics, University of
* Konstanz, and KNIME GmbH.
* %%
* 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.object;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.scijava.util.ClassUtils;
/**
* Data structure for managing lists of registered objects.
*
* The object index keeps lists of objects segregated by type. The type
* hierarchy beneath which each object is classified can be customized through
* subclassing (e.g., see {@link org.scijava.plugin.PluginIndex}), but by
* default, each registered object is added to all type lists with which its
* class is compatible. For example, an object of type {@link String} would be
* added to the following type lists: {@link String},
* {@link java.io.Serializable}, {@link Comparable}, {@link CharSequence} and
* {@link Object}. A subsequent request for all objects of type
* {@link Comparable} (via a call to {@link #get(Class)}) would return a list
* that includes the object.
*
*
* Note that similar to {@link List}, it is possible for the same object to be
* added to the index more than once, in which case it will appear on relevant
* type lists multiple times.
*
*
* Note that similar to {@link List}, it is possible for the same object to be
* added to the index more than once, in which case it will appear on compatible
* type lists multiple times.
*
*
* @author Curtis Rueden
*/
public class ObjectIndex implements Collection {
/**
* "Them as counts counts moren them as dont count."
* —Russell Hoban, Riddley Walker
*/
protected final Map, List> hoard =
new ConcurrentHashMap<>();
private final Class baseClass;
/** List of objects to add later as needed (i.e., lazily). */
private final List> pending =
new LinkedList<>();
public ObjectIndex(final Class baseClass) {
this.baseClass = baseClass;
}
// -- ObjectIndex methods --
/** Gets the base class of the items being managed. */
public Class getBaseClass() {
return baseClass;
}
/**
* Gets a list of all registered objects.
*
* @return Read-only list of all registered objects, or an empty list if none
* (this method never returns null).
*/
public List getAll() {
// NB: We return the special "All" list here, since in some cases,
// *no* other list contains *all* elements of the index.
// We cannot use Object.class, since interface type hierarchies do not
// extend Object. In particular, PluginIndex classifies objects beneath the
// SciJavaPlugin type hierarchy, which does not extend Object.
// And we cannot use getBaseClass() because the actual base class of the
// objects stored in the index may differ from the type hierarchy beneath
// which they are classified. In particular, PluginIndex classifies its
// PluginInfo objects beneath the SciJavaPlugin type hierarchy, and not that
// of PluginInfo.
return get(All.class);
}
/**
* Gets a list of registered objects compatible with the given type.
*
* @return New list of registered objects of the given type, or an empty
* list if no such objects exist (this method never returns null).
*/
public List get(final Class> type) {
// lazily register any pending objects
if (!pending.isEmpty()) resolvePending();
List list = retrieveList(type);
// NB: Return a copy of the data, to facilitate thread safety.
list = new ArrayList<>(list);
return list;
}
/**
* Registers objects which will be created lazily as needed.
*
* This is useful if creation of the objects is expensive for some reason. In
* that case, the object index can wait to actually request and register the
* objects until the next accessor method invocation (i.e.,
* {@link #get(Class)} or {@link #getAll()}).
*
*/
public void addLater(final LazyObjects extends E> c) {
synchronized (pending) {
pending.add(c);
}
}
// -- Collection methods --
@Override
public int size() {
return getAll().size();
}
@Override
public boolean isEmpty() {
return getAll().isEmpty();
}
@Override
public boolean contains(final Object o) {
if (!getBaseClass().isAssignableFrom(o.getClass())) return false;
@SuppressWarnings("unchecked")
final E typedObj = (E) o;
final Class> type = getType(typedObj);
return get(type).contains(o);
}
@Override
public Iterator iterator() {
return getAll().iterator();
}
@Override
public Object[] toArray() {
return getAll().toArray();
}
@Override
public T[] toArray(final T[] a) {
return getAll().toArray(a);
}
@Override
public boolean add(final E o) {
return add(o, false);
}
@Override
public boolean remove(final Object o) {
return remove(o, false);
}
@Override
public boolean containsAll(final Collection> c) {
return getAll().containsAll(c);
}
@Override
public boolean addAll(final Collection extends E> c) {
boolean changed = false;
for (final E o : c) {
final boolean result = add(o, true);
if (result) changed = true;
}
return changed;
}
@Override
public boolean removeAll(final Collection> c) {
boolean changed = false;
for (final Object o : c) {
final boolean result = remove(o, true);
if (result) changed = true;
}
return changed;
}
@Override
public boolean retainAll(final Collection> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
hoard.clear();
}
// -- Object methods --
@Override
public String toString() {
final List> classes = new ArrayList<>(hoard.keySet());
Collections.sort(classes, new Comparator>() {
@Override
public int compare(final Class> c1, final Class> c2) {
return ClassUtils.compare(c1, c2);
}
});
final String nl = System.getProperty("line.separator");
final StringBuilder sb = new StringBuilder();
for (final Class> c : classes) {
sb.append(c.getName() + ": {");
final List list = hoard.get(c);
boolean first = true;
for (final E element : list) {
if (first) first = false;
else sb.append(", ");
sb.append(element);
}
sb.append("}" + nl);
}
return sb.toString();
}
// -- Internal methods --
/** Adds the object to all compatible type lists. */
protected boolean add(final E o, final boolean batch) {
return add(o, getType(o), batch);
}
/** Return the type by which to index the object. */
protected Class> getType(final E o) {
return o.getClass();
}
/** Removes the object from all compatible type lists. */
protected boolean remove(final Object o, final boolean batch) {
if (!getBaseClass().isAssignableFrom(o.getClass())) return false;
@SuppressWarnings("unchecked")
final E e = (E) o;
return remove(o, getType(e), batch);
}
private Map, List[]> type2Lists =
new HashMap<>();
protected synchronized List[] retrieveListsForType(final Class> type) {
final List[] lists = type2Lists.get(type);
if (lists != null) return lists;
final ArrayList> listOfLists = new ArrayList<>();
for (final Class> c : getTypes(type)) {
listOfLists.add(retrieveList(c));
}
// convert list of lists to array of lists
@SuppressWarnings("rawtypes")
final List[] arrayOfRawLists =
listOfLists.toArray(new List[listOfLists.size()]);
@SuppressWarnings({ "unchecked" })
final List[] arrayOfLists = arrayOfRawLists;
type2Lists.put(type, arrayOfLists);
return arrayOfLists;
}
/** Adds an object to type lists beneath the given type hierarchy. */
@SuppressWarnings("unchecked")
protected boolean add(final E o, final Class> type, final boolean batch) {
boolean result = false;
for (final List> list : retrieveListsForType(type)) {
if (addToList(o, (List)list, batch)) result = true;
}
return result;
}
/** Removes an object from type lists beneath the given type hierarchy. */
protected boolean remove(final Object o, final Class> type,
final boolean batch)
{
boolean result = false;
for (final List list : retrieveListsForType(type)) {
if (removeFromList(o, list, batch)) result = true;
}
return result;
}
protected boolean addToList(final E obj, final List list,
@SuppressWarnings("unused") final boolean batch)
{
return list.add(obj);
}
protected boolean removeFromList(final Object obj, final List list,
@SuppressWarnings("unused") final boolean batch)
{
return list.remove(obj);
}
// -- Helper methods --
private static Map, Class>[]> typeMap =
new HashMap<>();
/** Gets a new set containing the type and all its supertypes. */
protected static synchronized Class>[] getTypes(final Class> type) {
Class>[] types = typeMap.get(type);
if (types != null) return types;
final Set>set = new LinkedHashSet<>();
set.add(All.class); // NB: Always include the "All" class.
getTypes(type, set);
types = set.toArray(new Class[set.size()]);
typeMap.put(type, types);
return types;
}
/** Recursively adds the type and all its supertypes to the given set. */
private static synchronized void getTypes(final Class> type,
final Set> types)
{
if (type == null) return;
types.add(type);
// recursively add to supertypes
getTypes(type.getSuperclass(), types);
for (final Class> iface : type.getInterfaces()) {
getTypes(iface, types);
}
}
/** Retrieves the type list for the given type, creating it if necessary. */
protected List retrieveList(final Class> type) {
List list = hoard.get(type);
if (list == null) {
list = new ArrayList<>();
hoard.put(type, list);
}
return list;
}
private void resolvePending() {
synchronized (pending) {
while (!pending.isEmpty()) {
final LazyObjects extends E> c = pending.remove(0);
addAll(c.get());
}
}
}
// -- Helper classes --
private static class All {
// NB: A special class beneath which *all* elements of the index are listed.
}
}