diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java index 9f941bc7..f2735874 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java @@ -119,11 +119,17 @@ public interface ProtocolManager { public PacketContainer createPacket(int id, boolean skipDefaults); /** - * Retieves a set of every enabled packet. - * @return Every packet filter. + * Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners. + * @return Every filtered server packet. */ - public Set getPacketFilters(); - + public Set getSendingFilters(); + + /** + * Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners. + * @return Every filtered client packet. + */ + public Set getReceivingFilters(); + /** * Determines whether or not this protocol mananger has been disabled. * @return TRUE if it has, FALSE otherwise. diff --git a/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java b/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java index f3d27dfd..ba8d77c4 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.events; /** diff --git a/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java new file mode 100644 index 00000000..ee1a3870 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java @@ -0,0 +1,110 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.events; + +import java.util.Set; + +import com.google.common.base.Objects; +import com.google.common.collect.Sets; + +/** + * Determines which packets will be observed by a listener, and with what priority. + + * @author Kristian + */ +public class ListeningWhitelist { + + /** + * A whitelist with no packets - indicates that the listener shouldn't observe any packets. + */ + public static ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW); + + private ListenerPriority priority; + private Set whitelist; + + /** + * Creates a packet whitelist for a given priority with a set of packet IDs. + * @param priority - the listener priority. + * @param whitelist - set of IDs to observe/enable. + */ + public ListeningWhitelist(ListenerPriority priority, Set whitelist) { + this.priority = priority; + this.whitelist = whitelist; + } + + /** + * Creates a packet whitelist of a given priority for a list of packets. + * @param priority - the listener priority. + * @param whitelist - list of packet IDs to observe/enable. + */ + public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) { + this.priority = priority; + this.whitelist = Sets.newHashSet(whitelist); + } + + /** + * Whether or not this whitelist has any enabled packets. + * @return TRUE if there are any packets, FALSE otherwise. + */ + public boolean isEnabled() { + return whitelist != null || whitelist.size() > 0; + } + + /** + * Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last. + * @return Execution order in terms of priority. + */ + public ListenerPriority getPriority() { + return priority; + } + + /** + * Retrieves the list of packets that will be observed by the listeners. + * @return Packet whitelist. + */ + public Set getWhitelist() { + return whitelist; + } + + @Override + public int hashCode(){ + return Objects.hashCode(priority, whitelist); + } + + @Override + public boolean equals(final Object obj){ + if(obj instanceof ListeningWhitelist){ + final ListeningWhitelist other = (ListeningWhitelist) obj; + return Objects.equal(priority, other.priority) + && Objects.equal(whitelist, other.whitelist); + } else{ + return false; + } + } + + @Override + public String toString() { + if (this == EMPTY_WHITELIST) + return "EMPTY_WHITELIST"; + else + return Objects.toStringHelper(this) + .add("priority", priority) + .add("packets", whitelist).toString(); + } + +} diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java index d5aea595..9a1dc40d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java @@ -17,12 +17,8 @@ package com.comphenix.protocol.events; -import java.util.Set; - import org.bukkit.plugin.Plugin; -import com.google.common.base.Joiner; -import com.google.common.collect.Sets; /** * Represents a packet listener with useful constructors. @@ -32,10 +28,10 @@ import com.google.common.collect.Sets; public abstract class PacketAdapter implements PacketListener { protected Plugin plugin; - protected Set packetsID; protected ConnectionSide connectionSide; - protected ListenerPriority listenerPriority; - + protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST; + protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST; + /** * Initialize a packet listener. * @param plugin - the plugin that spawned this listener. @@ -54,10 +50,23 @@ public abstract class PacketAdapter implements PacketListener { * @param packets - the packet IDs the listener is looking for. */ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) { + if (plugin == null) + throw new IllegalArgumentException("plugin cannot be null"); + if (connectionSide == null) + throw new IllegalArgumentException("connectionSide cannot be null"); + if (listenerPriority == null) + throw new IllegalArgumentException("listenerPriority cannot be null"); + if (packets == null) + throw new IllegalArgumentException("packets cannot be null"); + + // Add whitelists + if (connectionSide.isForServer()) + sendingWhitelist = new ListeningWhitelist(listenerPriority, packets); + if (connectionSide.isForClient()) + receivingWhitelist = new ListeningWhitelist(listenerPriority, packets); + this.plugin = plugin; this.connectionSide = connectionSide; - this.packetsID = Sets.newHashSet(packets); - this.listenerPriority = listenerPriority; } @Override @@ -71,13 +80,13 @@ public abstract class PacketAdapter implements PacketListener { } @Override - public ConnectionSide getConnectionSide() { - return connectionSide; + public ListeningWhitelist getReceivingWhitelist() { + return receivingWhitelist; } @Override - public Set getPacketsID() { - return packetsID; + public ListeningWhitelist getSendingWhitelist() { + return sendingWhitelist; } @Override @@ -85,11 +94,6 @@ public abstract class PacketAdapter implements PacketListener { return plugin; } - @Override - public ListenerPriority getListenerPriority() { - return listenerPriority; - } - /** * Retrieves the name of the plugin that has been associated with the listener. * @return Name of the associated plugin. @@ -113,8 +117,9 @@ public abstract class PacketAdapter implements PacketListener { @Override public String toString() { // This is used by the error reporter - return String.format("PacketAdapter[plugin=%s, side=%s, packets=%s]", - getPluginName(this), getConnectionSide().name(), - Joiner.on(", ").join(packetsID)); + return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]", + getPluginName(this), + sendingWhitelist, + receivingWhitelist); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java index 682b3780..aab19240 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java @@ -17,8 +17,6 @@ package com.comphenix.protocol.events; -import java.util.Set; - import org.bukkit.plugin.Plugin; /** @@ -43,22 +41,16 @@ public interface PacketListener { public void onPacketReceiving(PacketEvent event); /** - * Retrieve whether or not we're listening for client or server packets. - * @return The type of packets we expect. + * Retrieve which packets sent by the server this listener will observe. + * @return List of server packets to observe, along with the priority. */ - public ConnectionSide getConnectionSide(); + public ListeningWhitelist getSendingWhitelist(); /** - * Retrieve the priority in the execution order of this packet listener. Highest priority will be executed last. - * @return Execution order in terms of priority. + * Retrieve which packets sent by the client this listener will observe. + * @return List of server packets to observe, along with the priority. */ - public ListenerPriority getListenerPriority(); - - /** - * Set of packet ids we expect to recieve. - * @return Packets IDs. - */ - public Set getPacketsID(); + public ListeningWhitelist getReceivingWhitelist(); /** * Retrieve the plugin that created list packet listener. diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java index 9fac1868..d12faec2 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java @@ -1,7 +1,6 @@ package com.comphenix.protocol.injector; import java.util.ArrayList; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -9,10 +8,11 @@ import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; -import com.google.common.base.Objects; import com.google.common.primitives.Ints; /** @@ -23,36 +23,32 @@ import com.google.common.primitives.Ints; public class ConcurrentListenerMultimap { // The core of our map - protected ConcurrentMap> listeners = - new ConcurrentHashMap>(); + protected ConcurrentMap> listeners = + new ConcurrentHashMap>(); /** * Adds a listener to its requested list of packet recievers. * @param listener - listener with a list of packets to recieve notifcations for. */ - public void addListener(PacketListener listener) { - for (Integer packetID : listener.getPacketsID()) { - addListener(packetID, listener); + public void addListener(PacketListener listener, ListeningWhitelist whitelist) { + + PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority()); + + for (Integer packetID : whitelist.getWhitelist()) { + addListener(packetID, prioritized); } } // Add the listener to a specific packet notifcation list - private void addListener(Integer packetID, PacketListener listener) { + private void addListener(Integer packetID, PrioritizedListener listener) { - SortedArrayList list = listeners.get(packetID); + SortedCopyOnWriteArray list = listeners.get(packetID); // We don't want to create this for every lookup if (list == null) { // It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order, // which is a essential feature for our purposes. - final SortedArrayList value = new SortedArrayList(new Comparator() { - @Override - public int compare(PacketListener o1, PacketListener o2) { - // This ensures that lower priority listeners are executed first - return Ints.compare(o1.getListenerPriority().getSlot(), - o2.getListenerPriority().getSlot()); - } - }); + final SortedCopyOnWriteArray value = new SortedCopyOnWriteArray(); list = listeners.putIfAbsent(packetID, value); @@ -74,21 +70,26 @@ public class ConcurrentListenerMultimap { * @param listener - listener to remove. * @return Every packet ID that was removed due to no listeners. */ - public List removeListener(PacketListener listener) { + public List removeListener(PacketListener listener, ListeningWhitelist whitelist) { List removedPackets = new ArrayList(); // Again, not terribly efficient. But adding or removing listeners should be a rare event. - for (Integer packetID : listener.getPacketsID()) { + for (Integer packetID : whitelist.getWhitelist()) { - SortedArrayList list = listeners.get(packetID); + SortedCopyOnWriteArray list = listeners.get(packetID); // Remove any listeners if (list != null) { synchronized(list) { // Don't remove from newly created lists if (list.size() > 0) { - list.removeAll(listener); + // Remove this listener + for (Iterator it = list.iterator(); it.hasNext(); ) { + if (it.next().getListener().equals(list)) { + it.remove(); + } + } if (list.size() == 0) { listeners.remove(packetID); @@ -110,20 +111,21 @@ public class ConcurrentListenerMultimap { * @param event - the packet event to invoke. */ public void invokePacketRecieving(Logger logger, PacketEvent event) { - SortedArrayList list = listeners.get(event.getPacketID()); + SortedCopyOnWriteArray list = listeners.get(event.getPacketID()); if (list == null) return; // We have to be careful. Cannot modify the underlying list when sending notifications. synchronized (list) { - for (PacketListener listener : list) { + for (PrioritizedListener element : list) { try { - listener.onPacketReceiving(event); + element.getListener().onPacketReceiving(event); } catch (Exception e) { // Minecraft doesn't want your Exception. logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + PacketAdapter.getPluginName(listener), e); + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), e); } } } @@ -135,75 +137,50 @@ public class ConcurrentListenerMultimap { * @param event - the packet event to invoke. */ public void invokePacketSending(Logger logger, PacketEvent event) { - SortedArrayList list = listeners.get(event.getPacketID()); + SortedCopyOnWriteArray list = listeners.get(event.getPacketID()); if (list == null) return; synchronized (list) { - for (PacketListener listener : list) { + for (PrioritizedListener element : list) { try { - listener.onPacketSending(event); + element.getListener().onPacketSending(event); } catch (Exception e) { // Minecraft doesn't want your Exception. logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + PacketAdapter.getPluginName(listener), e); + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), e); } } } } /** - * An implicitly sorted array list that preserves insertion order and maintains duplicates. - * - * Note that only the {@link insertSorted} method will update the list correctly, - * @param - type of the sorted list. + * A listener with an associated priority. */ - private class SortedArrayList implements Iterable { - - private Comparator comparator; - private List list = new ArrayList(); + private class PrioritizedListener implements Comparable { + private PacketListener listener; + private ListenerPriority priority; - public SortedArrayList(Comparator comparator) { - this.comparator = comparator; + public PrioritizedListener(PacketListener listener, ListenerPriority priority) { + this.listener = listener; + this.priority = priority; } - - /** - * Inserts the given element in the proper location. - * @param value - element to insert. - */ - public void insertSorted(T value) { - list.add(value); - for (int i = list.size() - 1; i > 0 && comparator.compare(value, list.get(i-1)) < 0; i--) { - T tmp = list.get(i); - list.set(i, list.get(i-1)); - list.set(i-1, tmp); - } - } - - /** - * Removes every instance of the given element. - * @param element - element to remove. - */ - public void removeAll(T element) { - for (Iterator it = list.iterator(); it.hasNext(); ) { - if (Objects.equal(it.next(), element)) { - it.remove(); - } - } - } - - /** - * Retrieve the size of the list. - * @return Size of the list. - */ - public int size() { - return list.size(); - } @Override - public Iterator iterator() { - return list.iterator(); + public int compareTo(PrioritizedListener other) { + // This ensures that lower priority listeners are executed first + return Ints.compare(this.getPriority().getSlot(), + other.getPriority().getSlot()); + } + + public PacketListener getListener() { + return listener; + } + + public ListenerPriority getPriority() { + return priority; } } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index e9099f81..f79a88d3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,11 +49,12 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; public final class PacketFilterManager implements ProtocolManager { - private Set packetListeners = new CopyOnWriteArraySet(); + // Create a concurrent set + private Set packetListeners = + Collections.newSetFromMap(new ConcurrentHashMap()); // Player injection private Map connectionLookup = new ConcurrentHashMap(); @@ -111,37 +111,52 @@ public final class PacketFilterManager implements ProtocolManager { public void addPacketListener(PacketListener listener) { if (listener == null) throw new IllegalArgumentException("listener cannot be NULL."); - - ConnectionSide side = listener.getConnectionSide(); - packetListeners.add(listener); - // Add listeners - if (side.isForServer()) - sendingListeners.addListener(listener); - if (side.isForClient()) - recievedListeners.addListener(listener); + // A listener can only be added once + if (packetListeners.contains(listener)) + return; - // Inform our injected hooks - enablePacketFilters(side, listener.getPacketsID()); + ListeningWhitelist sending = listener.getSendingWhitelist(); + ListeningWhitelist receiving = listener.getReceivingWhitelist(); + boolean hasSending = sending != null && sending.isEnabled(); + boolean hasReceiving = receiving != null && receiving.isEnabled(); + + if (hasSending || hasReceiving) { + // Add listeners and hooks + if (hasSending) { + sendingListeners.addListener(listener, sending); + enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist()); + } + if (hasReceiving) { + recievedListeners.addListener(listener, receiving); + enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); + } + + // Inform our injected hooks + packetListeners.add(listener); + } } @Override public void removePacketListener(PacketListener listener) { if (listener == null) throw new IllegalArgumentException("listener cannot be NULL"); - - ConnectionSide side = listener.getConnectionSide(); + List sendingRemoved = null; List receivingRemoved = null; + ListeningWhitelist sending = listener.getSendingWhitelist(); + ListeningWhitelist receiving = listener.getReceivingWhitelist(); + // Remove from the overal list of listeners - packetListeners.remove(listener); + if (!packetListeners.remove(listener)) + return; // Add listeners - if (side.isForServer()) - sendingRemoved = sendingListeners.removeListener(listener); - if (side.isForClient()) - receivingRemoved = recievedListeners.removeListener(listener); + if (sending != null && sending.isEnabled()) + sendingRemoved = sendingListeners.removeListener(listener, sending); + if (receiving != null && receiving.isEnabled()) + receivingRemoved = recievedListeners.removeListener(listener, receiving); // Remove hooks, if needed if (sendingRemoved != null && sendingRemoved.size() > 0) @@ -154,9 +169,7 @@ public final class PacketFilterManager implements ProtocolManager { public void removePacketListeners(Plugin plugin) { // Iterate through every packet listener - for (Object element : packetListeners.toArray()) { - PacketListener listener = (PacketListener) element; - + for (PacketListener listener : packetListeners) { // Remove the listener if (Objects.equal(listener.getPlugin(), plugin)) { removePacketListener(listener); @@ -277,11 +290,13 @@ public final class PacketFilterManager implements ProtocolManager { } @Override - public Set getPacketFilters() { - if (packetInjector != null) - return Sets.union(sendingFilters, packetInjector.getPacketHandlers()); - else - return sendingFilters; + public Set getSendingFilters() { + return ImmutableSet.copyOf(sendingFilters); + } + + @Override + public Set getReceivingFilters() { + return ImmutableSet.copyOf(packetInjector.getPacketHandlers()); } /** @@ -362,7 +377,7 @@ public final class PacketFilterManager implements ProtocolManager { // Find the register event method Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent", eventTypes, Listener.class, eventPriority, Plugin.class); - + Enhancer ex = new Enhancer(); ex.setSuperclass(playerListener); ex.setClassLoader(classLoader); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/SortedCopyOnWriteArray.java b/ProtocolLib/src/com/comphenix/protocol/injector/SortedCopyOnWriteArray.java new file mode 100644 index 00000000..e0208ddc --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/SortedCopyOnWriteArray.java @@ -0,0 +1,73 @@ +package com.comphenix.protocol.injector; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An implicitly sorted array list that preserves insertion order and maintains duplicates. + * + * Note that only the {@link insertSorted} method will update the list correctly, + * @param - type of the sorted list. + */ +class SortedCopyOnWriteArray implements Iterable { + // Prevent reordering + private volatile List list; + + public SortedCopyOnWriteArray() { + this(new ArrayList()); + } + + public SortedCopyOnWriteArray(List wrapped) { + this.list = wrapped; + } + + /** + * Inserts the given element in the proper location. + * @param value - element to insert. + */ + @SuppressWarnings("unchecked") + public synchronized void insertSorted(T value) { + + // We use NULL as a special marker, so we don't allow it + if (value == null) + throw new IllegalArgumentException("value cannot be NULL"); + + List copy = new ArrayList(); + Comparable compare = (Comparable) value; + + for (T element : list) { + // If the value is now greater than the current element, it should be placed right before it + if (value != null && compare.compareTo(element) < 0) { + copy.add(value); + value = null; + } + copy.add(element); + } + + // Don't forget to add it + if (value != null) + copy.add(value); + + list = copy; + } + + public T get(int index) { + return list.get(index); + } + + /** + * Retrieve the size of the list. + * @return Size of the list. + */ + public int size() { + return list.size(); + } + + /** + * Retrieves an iterator over the elements in the given list. + */ + public Iterator iterator() { + return list.iterator(); + } +} diff --git a/ProtocolLib/test/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java b/ProtocolLib/test/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java new file mode 100644 index 00000000..9a9bcd9c --- /dev/null +++ b/ProtocolLib/test/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java @@ -0,0 +1,76 @@ +package com.comphenix.protocol.injector; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import com.comphenix.protocol.events.ListenerPriority; +import com.google.common.primitives.Ints; + +public class SortedCopyOnWriteArrayTest { + + @Test + public void testInsertion() { + + final int MAX_NUMBER = 50; + + SortedCopyOnWriteArray test = new SortedCopyOnWriteArray(); + + // Generate some numbers + List numbers = new ArrayList(); + + for (int i = 0; i < MAX_NUMBER; i++) { + numbers.add(i); + } + + // Random insertion to test it all + Collections.shuffle(numbers); + + // O(n^2) of course, so don't use too large numbers + for (int i = 0; i < MAX_NUMBER; i++) { + test.insertSorted(numbers.get(i)); + } + + // Check that everything is correct + for (int i = 0; i < MAX_NUMBER; i++) { + assertEquals((Integer) i, test.get(i)); + } + } + + @Test + public void testOrder() { + PriorityStuff a = new PriorityStuff(ListenerPriority.HIGH, 1); + PriorityStuff b = new PriorityStuff(ListenerPriority.NORMAL, 2); + PriorityStuff c = new PriorityStuff(ListenerPriority.NORMAL, 3); + SortedCopyOnWriteArray test = new SortedCopyOnWriteArray(); + + test.insertSorted(a); + test.insertSorted(b); + test.insertSorted(c); + + // Make sure the normal's are in the right order + assertEquals(2, test.get(0).id); + assertEquals(3, test.get(1).id); + } + + private class PriorityStuff implements Comparable { + public ListenerPriority priority; + public int id; + + public PriorityStuff(ListenerPriority priority, int id) { + this.priority = priority; + this.id = id; + } + + @Override + public int compareTo(PriorityStuff other) { + // This ensures that lower priority listeners are executed first + return Ints.compare(priority.getSlot(), + other.priority.getSlot()); + } + } +}