diff options
9 files changed, 149 insertions, 6 deletions
diff --git a/src/main/java/org/bukkit/conversations/Conversable.java b/src/main/java/org/bukkit/conversations/Conversable.java index 0633c1d9..4b6b568e 100644 --- a/src/main/java/org/bukkit/conversations/Conversable.java +++ b/src/main/java/org/bukkit/conversations/Conversable.java @@ -34,6 +34,13 @@ public interface Conversable { public void abandonConversation(Conversation conversation); /** + * Abandons an active conversation. + * @param conversation The conversation to abandon + * @param details Details about why the conversation was abandoned + */ + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details); + + /** * Sends this sender a message raw * * @param message Message to be displayed diff --git a/src/main/java/org/bukkit/conversations/Conversation.java b/src/main/java/org/bukkit/conversations/Conversation.java index 55592552..43cc362b 100644 --- a/src/main/java/org/bukkit/conversations/Conversation.java +++ b/src/main/java/org/bukkit/conversations/Conversation.java @@ -1,6 +1,5 @@ package org.bukkit.conversations; -import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; import java.util.ArrayList; @@ -37,6 +36,7 @@ public class Conversation { protected boolean localEchoEnabled; protected ConversationPrefix prefix; protected List<ConversationCanceller> cancellers; + protected List<ConversationAbandonedListener> abandonedListeners; /** * Initializes a new Conversation. @@ -62,6 +62,7 @@ public class Conversation { this.localEchoEnabled = true; this.prefix = new NullConversationPrefix(); this.cancellers = new ArrayList<ConversationCanceller>(); + this.abandonedListeners = new ArrayList<ConversationAbandonedListener>(); } /** @@ -190,7 +191,7 @@ public class Conversation { // Test for conversation abandonment based on input for(ConversationCanceller canceller : cancellers) { if (canceller.cancelBasedOnInput(context, input)) { - abandon(); + abandon(new ConversationAbandonedEvent(this, canceller)); return; } } @@ -202,13 +203,40 @@ public class Conversation { } /** + * Adds a {@link ConversationAbandonedListener}. + * @param listener The listener to add. + */ + public synchronized void addConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + } + + /** + * Removes a {@link ConversationAbandonedListener}. + * @param listener The listener to remove. + */ + public synchronized void removeConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.remove(listener); + } + + /** * Abandons and resets the current conversation. Restores the user's normal chat behavior. */ public void abandon() { + abandon(new ConversationAbandonedEvent(this, new ManuallyAbandonedConversationCanceller())); + } + + /** + * Abandons and resets the current conversation. Restores the user's normal chat behavior. + * @param details Details about why the conversation was abandoned + */ + public synchronized void abandon(ConversationAbandonedEvent details) { if (!abandoned) { abandoned = true; currentPrompt = null; context.getForWhom().abandonConversation(this); + for (ConversationAbandonedListener listener : abandonedListeners) { + listener.conversationAbandoned(details); + } } } @@ -217,7 +245,7 @@ public class Conversation { */ public void outputNextPrompt() { if (currentPrompt == null) { - abandon(); + abandon(new ConversationAbandonedEvent(this)); } else { context.getForWhom().sendRawMessage(prefix.getPrefix(context) + currentPrompt.getPromptText(context)); if (!currentPrompt.blocksForInput(context)) { diff --git a/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java b/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java new file mode 100644 index 00000000..720c87f0 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.conversations; + +import java.util.EventObject; + +/** + * ConversationAbandonedEvent contains information about an abandoned conversation. + */ +public class ConversationAbandonedEvent extends EventObject { + + private ConversationContext context; + private ConversationCanceller canceller; + + public ConversationAbandonedEvent(Conversation conversation) { + this(conversation, null); + } + + public ConversationAbandonedEvent(Conversation conversation, ConversationCanceller canceller) { + super(conversation); + this.context = conversation.getContext(); + this.canceller = canceller; + } + + /** + * Gets the object that caused the conversation to be abandoned. + * @return The object that abandoned the conversation. + */ + public ConversationCanceller getCanceller() { + return canceller; + } + + /** + * Gets the abandoned conversation's conversation context. + * @return The abandoned conversation's conversation context. + */ + public ConversationContext getContext() { + return context; + } + + /** + * Indicates how the conversation was abandoned - naturally as part of the prompt chain or prematurely via a + * {@link ConversationCanceller}. + * @return True if the conversation is abandoned gracefully by a {@link Prompt} returning null + * or the next prompt. False of the conversations is abandoned prematurely by a ConversationCanceller. + */ + public boolean gracefulExit() { + return canceller == null; + } +} diff --git a/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java b/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java new file mode 100644 index 00000000..325f91a7 --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java @@ -0,0 +1,13 @@ +package org.bukkit.conversations; + +import java.util.EventListener; + +/** + */ +public interface ConversationAbandonedListener extends EventListener { + /** + * Called whenever a {@link Conversation} is abandoned. + * @param abandonedEvent Contains details about the abandoned conversation. + */ + public void conversationAbandoned(ConversationAbandonedEvent abandonedEvent); +} diff --git a/src/main/java/org/bukkit/conversations/ConversationFactory.java b/src/main/java/org/bukkit/conversations/ConversationFactory.java index 6eb28692..d31a74d9 100644 --- a/src/main/java/org/bukkit/conversations/ConversationFactory.java +++ b/src/main/java/org/bukkit/conversations/ConversationFactory.java @@ -25,6 +25,7 @@ public class ConversationFactory { protected Map<Object, Object> initialSessionData; protected String playerOnlyMessage; protected List<ConversationCanceller> cancellers; + protected List<ConversationAbandonedListener> abandonedListeners; /** * Constructs a ConversationFactory. @@ -39,6 +40,7 @@ public class ConversationFactory { initialSessionData = new HashMap<Object, Object>(); playerOnlyMessage = null; cancellers = new ArrayList<ConversationCanceller>(); + abandonedListeners = new ArrayList<ConversationAbandonedListener>(); } /** @@ -142,13 +144,23 @@ public class ConversationFactory { } /** + * Adds a {@link ConversationAbandonedListener} to all conversations constructed by this factory. + * @param listener The listener to add. + * @return This object. + */ + public ConversationFactory addConversationAbandonedListener(ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + return this; + } + + /** * Constructs a {@link Conversation} in accordance with the defaults set for this factory. * @param forWhom The entity for whom the new conversation is mediating. * @return A new conversation. */ public Conversation buildConversation(Conversable forWhom) { //Abort conversation construction if we aren't supposed to talk to non-players - if(playerOnlyMessage != null && !(forWhom instanceof Player)) { + if (playerOnlyMessage != null && !(forWhom instanceof Player)) { return new Conversation(plugin, forWhom, new NotPlayerMessagePrompt()); } @@ -163,9 +175,14 @@ public class ConversationFactory { conversation.setPrefix(prefix); //Clone the conversation cancellers - for(ConversationCanceller canceller : cancellers) { + for (ConversationCanceller canceller : cancellers) { conversation.addConversationCanceller(canceller.clone()); } + + //Add the ConversationAbandonedListeners + for (ConversationAbandonedListener listener : abandonedListeners) { + conversation.addConversationAbandonedListener(listener); + } return conversation; } diff --git a/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java b/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java index ed0ec952..c1d0c9fd 100644 --- a/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java +++ b/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java @@ -48,7 +48,7 @@ public class InactivityConversationCanceller implements ConversationCanceller { startTimer(); } else if (conversation.getState() == Conversation.ConversationState.STARTED) { cancelling(conversation); - conversation.abandon(); + conversation.abandon(new ConversationAbandonedEvent(conversation, InactivityConversationCanceller.this)); } } }, timeoutSeconds * 20); diff --git a/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java b/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java new file mode 100644 index 00000000..6d7bd8fb --- /dev/null +++ b/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java @@ -0,0 +1,19 @@ +package org.bukkit.conversations; + +/** + * The ManuallyAbandonedConversationCanceller is only used as part of a {@link ConversationAbandonedEvent} to indicate + * that the conversation was manually abandoned by programatically calling the abandon() method on it. + */ +public class ManuallyAbandonedConversationCanceller implements ConversationCanceller{ + public void setConversation(Conversation conversation) { + throw new UnsupportedOperationException(); + } + + public boolean cancelBasedOnInput(ConversationContext context, String input) { + throw new UnsupportedOperationException(); + } + + public ConversationCanceller clone() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/org/bukkit/conversations/FakeConversable.java b/src/test/java/org/bukkit/conversations/FakeConversable.java index a04bfc8c..87fb3113 100644 --- a/src/test/java/org/bukkit/conversations/FakeConversable.java +++ b/src/test/java/org/bukkit/conversations/FakeConversable.java @@ -14,6 +14,7 @@ public class FakeConversable implements Conversable { public String lastSentMessage; public Conversation begunConversation; public Conversation abandonedConverstion; + public ConversationAbandonedEvent abandonedConversationEvent; public boolean isConversing() { return false; @@ -33,6 +34,11 @@ public class FakeConversable implements Conversable { abandonedConverstion = conversation; } + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + abandonedConverstion = conversation; + abandonedConversationEvent = details; + } + public void sendRawMessage(String message) { lastSentMessage = message; } diff --git a/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java b/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java index 9b5aa5e4..d812f5a7 100644 --- a/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java +++ b/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java @@ -10,6 +10,7 @@ import java.util.UUID; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; import org.bukkit.entity.Arrow; import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; @@ -739,4 +740,8 @@ public class TestPlayer implements Player { public void abandonConversation(Conversation conversation) { throw new UnsupportedOperationException("Not supported yet."); } + + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + throw new UnsupportedOperationException("Not supported yet."); + } } |