[Bleeding] Added ConversationAbandonedEvent and supporting infrastructure. Whenever a conversation exits, the ConversationAbandonedEvent is triggered with details about how the conversation ended and what, if anything caused it to end. Fixes BUKKIT-986

By: rmichela <deltahat@gmail.com>
This commit is contained in:
Bukkit/Spigot 2012-03-04 16:29:56 -05:00
parent 819611b351
commit 4b5a0b8ed8
9 changed files with 149 additions and 6 deletions

View file

@ -33,6 +33,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
*

View file

@ -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;
}
}
@ -201,14 +202,41 @@ 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)) {

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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>();
}
/**
@ -141,6 +143,16 @@ public class ConversationFactory {
return this;
}
/**
* 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.
@ -148,7 +160,7 @@ public class ConversationFactory {
*/
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;
}

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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.");
}
}