NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/components/registry/store/RegisteredItemStore.java

559 lines
19 KiB
Java

/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.components.registry.store;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import fr.neatmonster.nocheatplus.components.registry.exception.AlreadyRegisteredException;
import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder;
import fr.neatmonster.nocheatplus.components.registry.order.IRegisterWithOrder;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.AbstractRegistrationOrderSort;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterWithOrder;
import fr.neatmonster.nocheatplus.components.registry.order.SetupOrder;
/**
* Keep sorted arrays of registered (generic) items by type (support
* RegistrationOrder, IRegisterWithOrder, RegisterWithOrder, possibly
* other/deprecated). This is an internal registry object, not meant for direct
* external manipulation. Registering the same instance for several class types
* is possible. All registered items must differ by equals (!).
*
* @author asofold
*
*/
public class RegisteredItemStore {
// TODO: Interface
/**
* Newly created Order object and item instance. Allows sorting by
* RegistrationOrder (via
* {@link RegistrationOrder#sortIGetExtendsRegistrationOrder}), as well as
* sorting by internalId via the Comparable interface.
*
* @author asofold
*
*/
static class ItemNode <T> implements IGetRegistrationOrder, Comparable<ItemNode<T>> {
/*
* TODO: Looking ahead, should probably rather equal on base of the
* stored item (HashSet). Sorting by internalId may use a Comparator.
*/
// TODO: implement IGetItem
final RegistrationOrder order;
final T item;
/**
* A counter used to distinguish entries of otherwise equaling order.
* Always differs.
*/
final int internalCount;
<I extends T> ItemNode(RegistrationOrder order, I item, int internalCount) {
this.order = order;
this.item = item;
this.internalCount = internalCount;
}
@Override
public RegistrationOrder getRegistrationOrder() {
return order;
}
@Override
public int compareTo(ItemNode<T> o) {
return Integer.compare(internalCount, o.internalCount);
}
}
static final class SortItemNode<T> extends AbstractRegistrationOrderSort<ItemNode<T>> {
@Override
protected RegistrationOrder fetchRegistrationOrder(ItemNode<T> item) {
return item.getRegistrationOrder();
}
};
static final class ItemList <T> {
/*
* Looking ahead: Should probably rather use a LinkedHashSet for
* itemNodes, for faster removal and contains check. Sorting by
* internalId may use a Comparator.
*/
/*
* TODO: Consider only having sortedItems, doing without
* sortedItemNodes. Contra: future registry features, then stored
* meta-data.
*/
/** I bit heavy on the tip of the blade, java. */
private final SortItemNode<T> typedSort = new SortItemNode<T>();
/** Internal bookkeeping: all item nodes in order of registration. */
private final List<ItemNode<T>> itemNodes = new LinkedList<ItemNode<T>>();
/** All elements of itemNodes in sorted order, or null - lazy init, keep consistent. */
private ItemNode<T>[] sortedItemNodes = null;
/** All elements of itemNodes in sorted order, or null - lazy init, keep consistent. */
private T[] sortedItems = null;
/**
* Force sort, only should be called, if sortedItemNodes is null.
*/
@SuppressWarnings("unchecked")
void sort() {
// TODO: Might create the typed sort on the fly, instead of storing it ...
sortedItemNodes = typedSort.getSortedArray(itemNodes);
sortedItems = (T[]) new Object[sortedItemNodes.length];
for (int i = 0; i < sortedItemNodes.length; i++) {
sortedItems[i] = sortedItemNodes[i].item;
}
}
/**
* Invalidate sorted outputs.
*/
void invalidateSorted() {
sortedItemNodes = null;
sortedItems = null;
}
/**
* Must not be altered.
* @return
*/
T[] getSortedItemsReferenceArray() {
return sortedItems;
}
List<T> getSortedItemsCopyList() {
final LinkedList<T> out = new LinkedList<T>();
Collections.addAll(out, sortedItems);
return out;
}
/**
*
* @param order
* @param item Not null.
* @param internalCount
*/
void register(final RegistrationOrder order, final T item, final int internalCount) {
itemNodes.add(new ItemNode<T>(order, item, internalCount));
invalidateSorted();
}
/**
*
* @param item
* @return True if the list contained the item.
*/
boolean unregister(final T item) {
// (Sorting order should not change, if it were removed from all lists alike (!))
final Iterator<ItemNode<T>> it = itemNodes.iterator();
while (it.hasNext()) {
// TODO: equals or ==
if (it.next().item.equals(item)) {
invalidateSorted();
it.remove();
return true;
}
}
return false;
}
/**
* At your own risk :).
* @param item
* @return
*/
@SuppressWarnings("unchecked")
boolean unregisterObject(final Object item) {
return unregister((T) item);
}
boolean isRegisteredObject(final Object item) {
for (ItemNode<?> node : itemNodes) {
if (item.equals(node.item)) {
return true;
}
}
return false;
}
}
// TODO: Pre-register allowed types ?
// TODO: Thread safety on fetch / version with (abstract class with abstract methods to access store.)?
/** Registered items in self-sorting ItemListS by class. */
private final Map<Class<?>, ItemList<?>> itemListMap = new HashMap<Class<?>, ItemList<?>>();
/**
* Efficiently keep track of already registered items (registration of the
* same instance for multiple types is possible).
*/
private final Map<Object, Set<Class<?>>> items = new HashMap<Object, Set<Class<?>>>();
private int internalCount = 0; // TODO: Might support a counter object, passed from extern.
/**
* Convenience method to register without explicitly passing a
* RegistrationOrder instance. See
* {@link #register(Class, Object, RegistrationOrder)}.
*
* @param type
* The type to register the item for.
* @param item
* The item to register for the type.
* @throws NullPointerException
* If either of item or type is null.
* @throws AlreadyRegisteredException
* If the item is already registered for that type.
*/
public <T, I extends T> void register(Class<T> type, I item) {
register(type, item, null);
}
/**
* Register an item for the given type. If no order can be found, an attempt
* is made to fetch order from implemented interfaces or annotations, as a
* fall-back the indifferent default priority is used.
*
* @param type
* The type to register the item for.
* @param item
* The item to register for the type.
* @param order
* If null, it will be attempted to fetch the order by
* interfaces/annotation. If none is found the default
* indifferent order will be used.
* @throws NullPointerException
* If either of item or type is null.
* @throws AlreadyRegisteredException
* if the item is already registered for that type.
*/
public <T, I extends T> void register(Class<T> type, I item, RegistrationOrder order) {
if (item == null) {
throw new NullPointerException("Item must not be null.");
}
if (type == null) {
throw new NullPointerException("Type must not be null.");
}
// Check if already registered -> AlreadyRegisteredException
Set<Class<?>> registeredFor = items.get(item);
if (registeredFor != null && registeredFor.contains(type)) {
throw new AlreadyRegisteredException("Already registered for type: " + type.getName());
}
// Ensure to have a RegistrationOrder instance, copy external ones.
// (No merging of information is done here.)
// TODO: Try/Catch ?
// TODO: (Perhaps not ListenerOrder...)
// Check the most specific interface.
if (order == null && item instanceof IRegisterWithOrder) {
order = ((IRegisterWithOrder) item).getRegistrationOrder(type);
}
if (order == null && item instanceof IGetRegistrationOrder) {
// Better support this to avoid confusion (?).
order = ((IGetRegistrationOrder) item).getRegistrationOrder();
}
//
if (order == null) {
// Check Annotations.
RegisterWithOrder annoOrder = item.getClass().getAnnotation(RegisterWithOrder.class);
SetupOrder setupOrder = item.getClass().getAnnotation(SetupOrder.class); // To be deprecated.
if (annoOrder != null) {
order = new RegistrationOrder(annoOrder);
}
else if (setupOrder != null) { // To be deprecated.
order = new RegistrationOrder(setupOrder);
}
else {
// Default order.
order = RegistrationOrder.DEFAULT_ORDER;
}
}
else {
// Copy what has been found outside, huh.
order = new RegistrationOrder(order);
}
@SuppressWarnings("unchecked")
ItemList<T> itemList = (ItemList<T>) itemListMap.get(type);
if (itemList == null) {
itemList = new ItemList<T>();
itemListMap.put(type, itemList);
}
itemList.register(order, item, ++internalCount);
if (registeredFor == null) {
registeredFor = new HashSet<Class<?>>();
items.put(item, registeredFor);
}
registeredFor.add(type);
}
/**
* Unregister the item from the given type. It can still stay registered for
* other types.
*
* @param type
* @param item
* @return True, if the item has been removed for this type.
* @throws NullPointerException
* If either of item or type is null.
*/
public <T, I extends T> boolean unregister(final Class<T> type, final I item) {
if (item == null) {
throw new NullPointerException("Item must not be null.");
}
if (type == null) {
throw new NullPointerException("Type must not be null.");
}
int count = 0;
final Set<Class<?>> registeredFor = items.get(item);
if (registeredFor != null) {
if (registeredFor.remove(type)) {
count ++;
if (registeredFor.isEmpty()) {
items.remove(item);
}
}
}
@SuppressWarnings("unchecked")
final ItemList<T> itemList = (ItemList<T>) itemListMap.get(type);
if (itemList != null) {
if (itemList.unregister(item)) {
count ++;
if (itemList.itemNodes.isEmpty()) {
itemListMap.remove(type);
}
}
}
// TODO: If count is 1, throw some IllegalRegistryState (extends FatalRegistryException)?
/*
* TODO: ItemNotRegisteredException ? Not certain this is intended -
* could make that configurable (constructor or otherwise).
*/
return count > 0; // Was contained (consistently or not).
}
/**
* Unregister item from all types it is registered for.
*
* @param type
* @param item
* @return True, if the item has been removed for this type.
* @throws NullPointerException
* If either of item or type is null.
*/
public boolean unregister(final Object item) {
if (item == null) {
throw new NullPointerException("Item must not be null.");
}
/*
* TODO: ItemNotRegisteredException ? Not certain this is intended -
* could make that configurable (constructor or otherwise).
*/
final Set<Class<?>> registeredFor = items.remove(item);
if (registeredFor == null) {
return false;
}
// TODO: Track if consistent?
for (Class<?> type : registeredFor) {
final ItemList<?> itemList = itemListMap.get(type);
if (itemList != null) {
if (itemList.unregisterObject(item)) {
if (itemList.itemNodes.isEmpty()) {
itemListMap.remove(type);
}
}
}
// TODO: What if null or not contained - throw / notice somehow?
}
return true; // Was contained (consistently or not).
}
/**
* Unregister all items just from this type.
*
* @param type
* @return
* @throws NullPointerException
* If type is null.
*/
public boolean unregister(final Class<?> type) {
if (type == null) {
throw new NullPointerException("Type must not be null.");
}
final ItemList<?> itemList = itemListMap.remove(type);
if (itemList == null) {
return false;
}
for (ItemNode<?> node : itemList.itemNodes) {
final Object item = node.item;
final Set<Class<?>> registeredFor = items.get(item);
if (registeredFor != null) {
registeredFor.remove(type);
if (registeredFor.isEmpty()) {
items.remove(item);
}
}
}
return true;
}
/**
* Unregister each of all given items from all types it had been registered
* for.
*
* @param items
* @return
* @throws NullPointerException
* If items or any of the items within items is null.
*/
public boolean unregister(final Collection<Object> items) {
boolean had = false;
for (final Object item : items) {
had |= unregister(item);
}
return had;
}
/**
* Test if an item is registered for a given type.
*
* @param type
* @param item
* @return
* @throws NullPointerException
* If either of item or type is null.
*/
public <T, I extends T> boolean isRegistered(Class<T> type, I item) {
if (item == null) {
throw new NullPointerException("Item must not be null.");
}
if (type == null) {
throw new NullPointerException("Type must not be null.");
}
if (items.containsKey(item)) {
final ItemList<?> itemList = itemListMap.get(type);
return itemList != null && itemList.isRegisteredObject(item);
}
else {
return false;
}
}
/**
* Test if anything is registered for this type.
*
* @param type
* @return
* @throws NullPointerException
* If type is null.
*/
public boolean isRegistered(Class<?> type) {
if (type == null) {
throw new NullPointerException("Type must not be null.");
}
return itemListMap.containsKey(type);
}
/**
* Test if an item is registered at all.
*
* @param item
* @return
* @throws NullPointerException
* If item is null.
*/
public boolean isRegistered(Object item) {
if (item == null) {
throw new NullPointerException("Item must not be null.");
}
return items.containsKey(item);
}
/**
* Retrieve a copy of all registered items.
*
* @return
*/
public List<Object> getAllRegisteredItems() {
return new ArrayList<Object>(items.keySet());
}
/**
* Ensure all contained lists are sorted and optimized.
*/
public void sortAll() {
for (final ItemList<?> itemList : itemListMap.values()) {
if (itemList.sortedItemNodes == null) {
itemList.sort();
}
}
}
/**
* Get a new (linked) list with all registered items (ordered).
*
* @param type
* The type items are supposed to be registered for.
* @return A new list of the registered items. Note that this list does not
* contain extra information like Order, in case that had been given
* at the time of registration.
*/
@SuppressWarnings("unchecked")
public <T> List<T> getSortedItemsCopyList(Class<T> type) {
final ItemList<T> itemList = (ItemList<T>) itemListMap.get(type);
return itemList == null ? new LinkedList<T>() : itemList.getSortedItemsCopyList();
}
/**
* Get the reference of an internally stored array with all items that have
* been registered for the given type in sorted order. This would sort the
* items first, if the array is not set. It's imperative not to alter the
* array, because that would lead to an inconsistent internal state of the
* array.
*
* @param type
* @return
*/
@SuppressWarnings("unchecked")
public <T> T[] getSortedItemsReferenceArray(Class<T> type) {
final ItemList<T> itemList = (ItemList<T>) itemListMap.get(type);
return itemList == null ? null : itemList.getSortedItemsReferenceArray();
}
/**
* Remove everything.
*/
public void clear() {
items.clear();
itemListMap.clear();
}
}