/*
* 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.players;
import com.jcwhatever.nucleus.collections.CircularQueue;
import com.jcwhatever.nucleus.collections.players.PlayerElement.PlayerElementMatcher;
import com.jcwhatever.nucleus.collections.wrap.ConversionIteratorWrapper;
import com.jcwhatever.nucleus.collections.wrap.SyncStrategy;
import com.jcwhatever.nucleus.utils.CollectionUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.validate.IValidator;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* A {@link CircularQueue} for {@link Player} objects that automatically removes
* players when they log out.
*
* <p>Thread safe.</p>
*
* <p>The queues iterators must be used inside a synchronized block which locks the
* queue instance. Otherwise, a {@link java.lang.IllegalStateException} is thrown.</p>
*/
public class PlayerCircularQueue implements IPlayerCollection, Deque<Player> {
private final Plugin _plugin;
private final CircularQueue<PlayerElement> _queue = new CircularQueue<>();
private final PlayerCollectionTracker _tracker;
private final Object _sync;
private final SyncStrategy _strategy;
/**
* Constructor.
*
* @param plugin The owning plugin.
*/
public PlayerCircularQueue(Plugin plugin) {
PreCon.notNull(plugin);
_plugin = plugin;
_sync = this;
_strategy = new SyncStrategy(this);
_tracker = new PlayerCollectionTracker(this);
}
@Override
public Plugin getPlugin() {
return _plugin;
}
@Override
public void addFirst(Player player) {
PreCon.notNull(player);
synchronized (_sync) {
PlayerElement element = new PlayerElement(player);
if (!_queue.contains(element)) {
_tracker.notifyPlayerAdded(player.getUniqueId());
}
_queue.addFirst(element);
}
}
@Override
public void addLast(Player player) {
PreCon.notNull(player);
PlayerElement element = new PlayerElement(player);
synchronized (_sync) {
if (!_queue.contains(element)) {
_tracker.notifyPlayerAdded(player.getUniqueId());
}
_queue.addLast(element);
}
}
@Override
public boolean offerFirst(Player player) {
PreCon.notNull(player);
PlayerElement element = new PlayerElement(player);
synchronized (_sync) {
boolean has = _queue.contains(element);
if (_queue.offerFirst(element)) {
if (!has)
_tracker.notifyPlayerAdded(player.getUniqueId());
return true;
}
return false;
}
}
@Override
public boolean offerLast(Player player) {
PreCon.notNull(player);
PlayerElement element = new PlayerElement(player);
synchronized (_sync) {
boolean has = _queue.contains(element);
if (_queue.offerLast(element)) {
if (!has)
_tracker.notifyPlayerAdded(player.getUniqueId());
return true;
}
return false;
}
}
@Override
public Player removeFirst() {
synchronized (_sync) {
PlayerElement element = _queue.removeFirst();
Player result = element != null ? element.getPlayer() : null;
if (result != null && !_queue.contains(element)) {
_tracker.notifyPlayerRemoved(result.getUniqueId());
}
return result;
}
}
@Override
public Player removeLast() {
synchronized (_sync) {
PlayerElement element = _queue.removeLast();
Player result = element != null ? element.getPlayer() : null;
if (result != null && !_queue.contains(element)) {
_tracker.notifyPlayerRemoved(result.getUniqueId());
}
return result;
}
}
@Override
public Player pollFirst() {
synchronized (_sync) {
PlayerElement element = _queue.pollFirst();
Player result = element != null ? element.getPlayer() : null;
if (result != null && !_queue.contains(element)) {
_tracker.notifyPlayerRemoved(result.getUniqueId());
}
return result;
}
}
@Override
public Player pollLast() {
synchronized (_sync) {
PlayerElement element = _queue.pollLast();
Player result = element != null ? element.getPlayer() : null;
if (result != null && !_queue.contains(element)) {
_tracker.notifyPlayerRemoved(result.getUniqueId());
}
return result;
}
}
@Override
public Player getFirst() {
synchronized (_sync) {
PlayerElement entry = _queue.getFirst();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public Player getLast() {
synchronized (_sync) {
PlayerElement entry = _queue.getLast();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public Player peekFirst() {
synchronized (_sync) {
PlayerElement entry = _queue.peekFirst();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public Player peekLast() {
synchronized (_sync) {
PlayerElement entry = _queue.peekLast();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public boolean removeFirstOccurrence(Object o) {
synchronized (_sync) {
PlayerElementMatcher matcher = new PlayerElementMatcher(o);
if (_queue.removeFirstOccurrence(matcher)) {
//noinspection SuspiciousMethodCalls
if (matcher.getUniqueId() != null && !_queue.contains(matcher)) {
_tracker.notifyPlayerRemoved(matcher.getUniqueId());
}
return true;
}
return false;
}
}
@Override
public boolean removeLastOccurrence(Object o) {
synchronized (_sync) {
PlayerElementMatcher matcher = new PlayerElementMatcher(o);
if (_queue.removeLastOccurrence(matcher)) {
//noinspection SuspiciousMethodCalls
if (matcher.getUniqueId() != null && !_queue.contains(matcher)) {
_tracker.notifyPlayerRemoved(matcher.getUniqueId());
}
return true;
}
return false;
}
}
@Override
public boolean add(Player player) {
PreCon.notNull(player);
synchronized (_sync) {
PlayerElement element = new PlayerElement(player);
boolean has = _queue.contains(element);
if (_queue.add(element)) {
if (!has)
_tracker.notifyPlayerAdded(player.getUniqueId());
return true;
}
return false;
}
}
@Override
public boolean offer(Player player) {
PreCon.notNull(player);
synchronized (_sync) {
PlayerElement element = new PlayerElement(player);
boolean has = _queue.contains(element);
if (_queue.offer(element)) {
if (!has)
_tracker.notifyPlayerAdded(player.getUniqueId());
return true;
}
return false;
}
}
@Override
public Player remove() {
synchronized (_sync) {
PlayerElement entry = _queue.remove();
Player result = entry != null ? entry.getPlayer() : null;
if (result != null && !_queue.contains(entry))
_tracker.notifyPlayerRemoved(entry.getUniqueId());
return result;
}
}
@Override
public Player poll() {
synchronized (_sync) {
PlayerElement entry = _queue.poll();
Player result = entry != null ? entry.getPlayer() : null;
if (result != null && !_queue.contains(entry))
_tracker.notifyPlayerRemoved(entry.getUniqueId());
return result;
}
}
@Override
public Player element() {
synchronized (_sync) {
PlayerElement entry = _queue.element();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public Player peek() {
synchronized (_sync) {
PlayerElement entry = _queue.peek();
return entry != null ? entry.getPlayer() : null;
}
}
@Override
public void push(Player player) {
PreCon.notNull(player);
synchronized (_sync) {
PlayerElement element = new PlayerElement(player);
boolean has = _queue.contains(element);
_queue.push(element);
if (!has)
_tracker.notifyPlayerAdded(player.getUniqueId());
}
}
@Override
public Player pop() {
synchronized (_sync) {
PlayerElement entry = _queue.pop();
Player result = entry != null ? entry.getPlayer() : null;
if (result != null && !_queue.contains(entry))
_tracker.notifyPlayerRemoved(entry.getUniqueId());
return result;
}
}
@Override
public boolean remove(Object o) {
synchronized (_sync) {
PlayerElementMatcher matcher = new PlayerElementMatcher(o);
if (_queue.remove(matcher)) {
//noinspection SuspiciousMethodCalls
if (matcher.getUniqueId() != null && !_queue.contains(matcher))
_tracker.notifyPlayerRemoved(matcher.getUniqueId());
return true;
}
return false;
}
}
@Override
public boolean containsAll(Collection<?> c) {
PreCon.notNull(c);
for (Object obj : c) {
if (!contains(obj))
return false;
}
return true;
}
@Override
public boolean addAll(Collection<? extends Player> c) {
PreCon.notNull(c);
boolean isChanged = false;
synchronized (_sync) {
for (Player player : c) {
isChanged = add(player) || isChanged;
}
}
return isChanged;
}
@Override
public boolean removeAll(Collection<?> c) {
PreCon.notNull(c);
boolean isChanged = false;
synchronized (_sync) {
for (Object obj : c) {
isChanged = remove(obj) || isChanged;
}
}
return isChanged;
}
@Override
public boolean retainAll(Collection<?> c) {
PreCon.notNull(c);
final List<PlayerElement> toRemove = new ArrayList<>(_queue);
for (Object obj : c) {
PlayerElementMatcher matcher = new PlayerElementMatcher(obj);
//noinspection SuspiciousMethodCalls
toRemove.remove(matcher);
}
synchronized (_sync) {
List<PlayerElement> removed = CollectionUtils.retainAll(_queue, new IValidator<PlayerElement>() {
@Override
public boolean isValid(PlayerElement element) {
if (toRemove.contains(element)) {
_tracker.notifyPlayerRemoved(element.getUniqueId());
return false;
} else {
return true;
}
}
});
return !removed.isEmpty();
}
}
@Override
public void removePlayer(UUID playerId) {
PreCon.notNull(playerId);
synchronized (_sync) {
//noinspection StatementWithEmptyBody,SuspiciousMethodCalls
while (_queue.remove(new PlayerElementMatcher(playerId)));
}
}
@Override
public void clear() {
Set<PlayerElement> removed;
synchronized (_sync) {
removed = new HashSet<>(_queue);
_queue.clear();
}
for (PlayerElement element : removed) {
_tracker.notifyPlayerRemoved(element.getUniqueId());
}
}
@Override
public boolean contains(Object o) {
synchronized (_sync) {
return _queue.contains(new PlayerElementMatcher(o));
}
}
@Override
public int size() {
return _queue.size();
}
@Override
public boolean isEmpty() {
synchronized (_sync) {
return _queue.isEmpty();
}
}
@Override
public Iterator<Player> iterator() {
return new ItrRight();
}
@Override
public Object[] toArray() {
Object[] elements;
synchronized (_sync) {
elements = _queue.toArray();
}
Object[] results = new Object[elements.length];
for (int i=0; i < elements.length; i++) {
results[i] = ((PlayerElement)elements[i]).getPlayer();
}
return results;
}
@Override
public <T> T[] toArray(T[] a) {
PreCon.notNull(a);
Object[] elements;
synchronized (_sync) {
elements = _queue.toArray();
}
for (int i=0; i < elements.length; i++) {
@SuppressWarnings("unchecked")
T player = (T)((PlayerElement)elements[i]).getPlayer();
a[i] = player;
}
return a;
}
@Override
public Iterator<Player> descendingIterator() {
return new ItrLeft();
}
private final class ItrRight extends ConversionIteratorWrapper<Player, PlayerElement> {
ItrRight() {
super(PlayerCircularQueue.this._strategy);
}
Iterator<PlayerElement> iterator = _queue.iterator();
@Override
protected Player convert(PlayerElement internal) {
return internal.getPlayer();
}
@Override
protected Iterator<PlayerElement> iterator() {
return iterator;
}
@Override
public void remove() {
synchronized (_sync) {
PlayerElement removed = getCurrent();
iterator.remove();
if (!contains(removed)) {
_tracker.notifyPlayerRemoved(removed.getUniqueId());
}
}
}
}
private final class ItrLeft extends ConversionIteratorWrapper<Player, PlayerElement> {
ItrLeft() {
super(PlayerCircularQueue.this._strategy);
}
Iterator<PlayerElement> iterator = _queue.descendingIterator();
@Override
protected Player convert(PlayerElement internal) {
return internal.getPlayer();
}
@Override
protected Iterator<PlayerElement> iterator() {
return iterator;
}
@Override
public void remove() {
synchronized (_sync) {
PlayerElement removed = getCurrent();
iterator.remove();
if (!contains(removed)) {
_tracker.notifyPlayerRemoved(removed.getUniqueId());
}
}
}
}
}