/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.collections; import com.jcwhatever.nucleus.collections.ElementCounter.ElementCount; import com.jcwhatever.nucleus.utils.PreCon; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * Counts the number of times an item is added but only holds a single reference to the item. * * <p>Depending on the removal policy, when the items count reaches 0 the item is or isn't removed.</p> * * @param <E> The element type. */ public class ElementCounter<E> implements Iterable<ElementCount<E>> { private Map<E, ElementCount<E>> _countMap; private RemovalPolicy _policy; // cache to quickly increment a value without having to look it up in the type count map. private transient E _current; private transient ElementCount<E> _currentCounter; /** * Used to specify if the counter should remove * an item when its count reaches 0 or continue counting * into negative numbers. */ public enum RemovalPolicy { /** * Remove item if its count reaches 0. */ REMOVE, /** * Allow item count to go below 0. The item is not removed. */ KEEP_COUNTING, /** * Item count never goes below 0. The item is not removed. */ BOTTOM_OUT } /** * Constructor. * * @param removalPolicy Specify how items are handled when their count reaches 0. */ public ElementCounter(RemovalPolicy removalPolicy) { this(removalPolicy, 10); } /** * Constructor. * * @param removalPolicy Specify how items are handled when their count reaches 0. * @param capacity The initial capacity of the internal collection. */ public ElementCounter(RemovalPolicy removalPolicy, int capacity) { PreCon.notNull(removalPolicy); _policy = removalPolicy; _countMap = new HashMap<>(capacity); } /** * Constructor. * * @param removalPolicy Specify how items are handled when their count reaches 0. * @param iterable The initial elements to count. */ public ElementCounter(RemovalPolicy removalPolicy, Iterable<? extends E> iterable) { PreCon.notNull(removalPolicy); PreCon.notNull(iterable); _policy = removalPolicy; _countMap = new HashMap<>(10); for (E element : iterable) { add(element); } } /** * Constructor. * * @param removalPolicy Specify how items are handled when their count reaches 0. * @param collection The initial collection of elements to count. */ public ElementCounter(RemovalPolicy removalPolicy, Collection<? extends E> collection) { PreCon.notNull(removalPolicy); PreCon.notNull(collection); _policy = removalPolicy; _countMap = new HashMap<>(collection.size()); for (E element : collection) { add(element); } } /** * Get the number of elements that are counted. */ public int size() { return _countMap.keySet().size(); } /** * Determine if an element is in the counter. * * @param element The element to check. */ public boolean contains(Object element) { PreCon.notNull(element); //noinspection SuspiciousMethodCalls return _countMap.keySet().contains(element); } /** * Increment elements in the counter. * * @param iterable The elements to increment. */ public void addAll(Iterable<? extends E> iterable) { addAll(iterable, 1); } /** * Increment elements in the counter. * * @param iterable The elements to increment. * @param amount The amount to add to each element. */ public void addAll(Iterable<? extends E> iterable, int amount) { PreCon.notNull(iterable); for (E element : iterable) modifyCount(element, amount); } /** * Increment an element in the counter. * * @param element The element to increment. * * @return The new count for the element. */ public int add(E element) { return modifyCount(element, 1); } /** * Increment an element in the counter. * * @param element The element to increment. * @param amount The amount to increment. * * @return The new count for the element. */ public int add(E element, int amount) { PreCon.notNull(element); return modifyCount(element, amount); } /** * Add from the specified {@link ElementCounter}'s counts * to the current {@link ElementCounter}. * * @param counter The counter. * * @return Self for chaining. */ public ElementCounter<E> add(ElementCounter<E> counter) { PreCon.notNull(counter); for (ElementCount<E> element : counter) { modifyCount(element.getElement(), element.getCount()); } return this; } /** * Decrement elements in the counter. * * @param iterable The iterable collection of elements to subtract. */ public void subtractAll(Iterable<? extends E> iterable) { subtractAll(iterable, 1); } /** * Decrement elements in the counter. * * @param iterable The iterable collection of elements to subtract. * @param amount The amount to subtract from each element. */ public void subtractAll(Iterable<? extends E> iterable, int amount) { PreCon.notNull(iterable); for (E element : iterable) { if (element == null) continue; modifyCount(element, -amount); } } /** * Decrement an elements count. * * @param element The element to subtract. * * @return The new count for the element. */ public int subtract(E element) { PreCon.notNull(element); return modifyCount(element, -1); } /** * Decrement an elements count. * * @param element The element to subtract. * @param amount The amount to subtract. * * @return The new count for the element. */ public int subtract(E element, int amount) { PreCon.notNull(element); return modifyCount(element, -amount); } /** * Subtract from the current {@link ElementCounter} all the items * counted by the specified {@link ElementCounter}. * * @param counter The counter. * * @return Self for chaining. */ public ElementCounter<E> subtract(ElementCounter<E> counter) { PreCon.notNull(counter); for (ElementCount<E> element : counter) { modifyCount(element.getElement(), -element.getCount()); } return this; } /** * Get the current counter value for the specified item. * * @param element The item to get the count value for */ public int count(Object element) { PreCon.notNull(element); if (_current != null && _current.equals(element)) { return _currentCounter.count; } //noinspection SuspiciousMethodCalls ElementCount counter = _countMap.get(element); if (counter == null) { return 0; } return counter.count; } /** * Get a new hash set containing the items that were counted. */ public Set<E> getElements() { return new HashSet<E>(_countMap.keySet()); } /** * Get a new hash set containing the items that were counted. * * @param output The output collection to add results to. */ public <T extends Collection<E>> T getElements(T output) { PreCon.notNull(output); output.addAll(_countMap.keySet()); return output; } /** * Clear all items and counts. */ public void reset() { _countMap.clear(); _current = null; _currentCounter = null; } /** * Get an iterator to iterate over the items in the counter. * * <p>The iterator returned is from a copied list and does not affect * the collection.</p> * * <p>Each item appears only once in the iteration regardless of its count.</p> */ @Override public Iterator<ElementCount<E>> iterator() { return _countMap.values().iterator(); } /** * Modify the count of an element */ private int modifyCount(E element, int amount) { PreCon.notNull(element); ElementCount<E> counter; counter = _current != null && _current.equals(element) ? _currentCounter : _countMap.get(element); // Check if item is in counter if (counter == null) { if (_policy == RemovalPolicy.REMOVE && amount <= 0) { return 0; } else { counter = new ElementCount<>(_policy, element, amount); _countMap.put(element, counter); } } // modify count else { counter.increment(amount); } // check if the item needs to be removed if (_policy == RemovalPolicy.REMOVE && counter.count <= 0) { _current = null; _currentCounter = null; _countMap.remove(element); return 0; } // place value in cache _current = counter.element; _currentCounter = counter; return counter.count; } /** * Contains count information for a single element. * * @param <E> The element type. */ public static class ElementCount<E> { final RemovalPolicy policy; final E element; int count; ElementCount(RemovalPolicy policy, E element, int count) { this.policy = policy; this.element = element; this.count = policy == RemovalPolicy.BOTTOM_OUT ? Math.max(0, count) : count; } /** * Get the elements count. */ public int getCount() { return count; } /** * Get the element. */ public E getElement() { return element; } void increment(int amount) { if (policy == RemovalPolicy.BOTTOM_OUT) count = Math.max(0, count + amount); else count += amount; } } }