001    package org.bukkit.conversations;
002    
003    import org.bukkit.entity.Player;
004    import org.bukkit.plugin.Plugin;
005    
006    import java.util.ArrayList;
007    import java.util.HashMap;
008    import java.util.List;
009    import java.util.Map;
010    
011    /**
012     * A ConversationFactory is responsible for creating a {@link Conversation}
013     * from a predefined template. A ConversationFactory is typically created when
014     * a plugin is instantiated and builds a Conversation each time a user
015     * initiates a conversation with the plugin. Each Conversation maintains its
016     * own state and calls back as needed into the plugin.
017     * <p>
018     * The ConversationFactory implements a fluid API, allowing parameters to be
019     * set as an extension to the constructor.
020     */
021    public class ConversationFactory {
022    
023        protected Plugin plugin;
024        protected boolean isModal;
025        protected boolean localEchoEnabled;
026        protected ConversationPrefix prefix;
027        protected Prompt firstPrompt;
028        protected Map<Object, Object> initialSessionData;
029        protected String playerOnlyMessage;
030        protected List<ConversationCanceller> cancellers;
031        protected List<ConversationAbandonedListener> abandonedListeners;
032    
033        /**
034         * Constructs a ConversationFactory.
035         *
036         * @param plugin The plugin that owns the factory.
037         */
038        public ConversationFactory(Plugin plugin)
039        {
040            this.plugin = plugin;
041            isModal = true;
042            localEchoEnabled = true;
043            prefix = new NullConversationPrefix();
044            firstPrompt = Prompt.END_OF_CONVERSATION;
045            initialSessionData = new HashMap<Object, Object>();
046            playerOnlyMessage = null;
047            cancellers = new ArrayList<ConversationCanceller>();
048            abandonedListeners = new ArrayList<ConversationAbandonedListener>();
049        }
050    
051        /**
052         * Sets the modality of all {@link Conversation}s created by this factory.
053         * If a conversation is modal, all messages directed to the player are
054         * suppressed for the duration of the conversation.
055         * <p>
056         * The default is True.
057         *
058         * @param modal The modality of all conversations to be created.
059         * @return This object.
060         */
061        public ConversationFactory withModality(boolean modal)
062        {
063            isModal = modal;
064            return this;
065        }
066    
067        /**
068         * Sets the local echo status for all {@link Conversation}s created by
069         * this factory. If local echo is enabled, any text submitted to a
070         * conversation gets echoed back into the submitter's chat window.
071         *
072         * @param localEchoEnabled The status of local echo.
073         * @return This object.
074         */
075        public ConversationFactory withLocalEcho(boolean localEchoEnabled) {
076            this.localEchoEnabled = localEchoEnabled;
077            return this;
078        }
079    
080        /**
081         * Sets the {@link ConversationPrefix} that prepends all output from all
082         * generated conversations.
083         * <p>
084         * The default is a {@link NullConversationPrefix};
085         *
086         * @param prefix The ConversationPrefix to use.
087         * @return This object.
088         */
089        public ConversationFactory withPrefix(ConversationPrefix prefix) {
090            this.prefix = prefix;
091            return this;
092        }
093    
094        /**
095         * Sets the number of inactive seconds to wait before automatically
096         * abandoning all generated conversations.
097         * <p>
098         * The default is 600 seconds (5 minutes).
099         *
100         * @param timeoutSeconds The number of seconds to wait.
101         * @return This object.
102         */
103        public ConversationFactory withTimeout(int timeoutSeconds) {
104            return withConversationCanceller(new InactivityConversationCanceller(plugin, timeoutSeconds));
105        }
106    
107        /**
108         * Sets the first prompt to use in all generated conversations.
109         * <p>
110         * The default is Prompt.END_OF_CONVERSATION.
111         *
112         * @param firstPrompt The first prompt.
113         * @return This object.
114         */
115        public ConversationFactory withFirstPrompt(Prompt firstPrompt) {
116            this.firstPrompt = firstPrompt;
117            return this;
118        }
119    
120        /**
121         * Sets any initial data with which to populate the conversation context
122         * sessionData map.
123         *
124         * @param initialSessionData The conversation context's initial
125         *     sessionData.
126         * @return This object.
127         */
128        public ConversationFactory withInitialSessionData(Map<Object, Object> initialSessionData) {
129            this.initialSessionData = initialSessionData;
130            return this;
131        }
132    
133        /**
134         * Sets the player input that, when received, will immediately terminate
135         * the conversation.
136         *
137         * @param escapeSequence Input to terminate the conversation.
138         * @return This object.
139         */
140        public ConversationFactory withEscapeSequence(String escapeSequence) {
141            return withConversationCanceller(new ExactMatchConversationCanceller(escapeSequence));
142        }
143    
144    
145        /**
146         * Adds a {@link ConversationCanceller} to constructed conversations.
147         *
148         * @param canceller The {@link ConversationCanceller} to add.
149         * @return This object.
150         */
151        public ConversationFactory withConversationCanceller(ConversationCanceller canceller) {
152            cancellers.add(canceller);
153            return this;
154        }
155    
156        /**
157         * Prevents this factory from creating a conversation for non-player
158         * {@link Conversable} objects.
159         *
160         * @param playerOnlyMessage The message to return to a non-play in lieu of
161         *     starting a conversation.
162         * @return This object.
163         */
164        public ConversationFactory thatExcludesNonPlayersWithMessage(String playerOnlyMessage) {
165            this.playerOnlyMessage = playerOnlyMessage;
166            return this;
167        }
168    
169        /**
170         * Adds a {@link ConversationAbandonedListener} to all conversations
171         * constructed by this factory.
172         *
173         * @param listener The listener to add.
174         * @return This object.
175         */
176        public ConversationFactory addConversationAbandonedListener(ConversationAbandonedListener listener) {
177            abandonedListeners.add(listener);
178            return this;
179        }
180    
181        /**
182         * Constructs a {@link Conversation} in accordance with the defaults set
183         * for this factory.
184         *
185         * @param forWhom The entity for whom the new conversation is mediating.
186         * @return A new conversation.
187         */
188        public Conversation buildConversation(Conversable forWhom) {
189            //Abort conversation construction if we aren't supposed to talk to non-players
190            if (playerOnlyMessage != null && !(forWhom instanceof Player)) {
191                return new Conversation(plugin, forWhom, new NotPlayerMessagePrompt());
192            }
193    
194            //Clone any initial session data
195            Map<Object, Object> copiedInitialSessionData = new HashMap<Object, Object>();
196            copiedInitialSessionData.putAll(initialSessionData);
197    
198            //Build and return a conversation
199            Conversation conversation = new Conversation(plugin, forWhom, firstPrompt, copiedInitialSessionData);
200            conversation.setModal(isModal);
201            conversation.setLocalEchoEnabled(localEchoEnabled);
202            conversation.setPrefix(prefix);
203    
204            //Clone the conversation cancellers
205            for (ConversationCanceller canceller : cancellers) {
206                conversation.addConversationCanceller(canceller.clone());
207            }
208    
209            //Add the ConversationAbandonedListeners
210            for (ConversationAbandonedListener listener : abandonedListeners) {
211                conversation.addConversationAbandonedListener(listener);
212            }
213    
214            return conversation;
215        }
216    
217        private class NotPlayerMessagePrompt extends MessagePrompt {
218    
219            public String getPromptText(ConversationContext context) {
220                return playerOnlyMessage;
221            }
222    
223            @Override
224            protected Prompt getNextPrompt(ConversationContext context) {
225                return Prompt.END_OF_CONVERSATION;
226            }
227        }
228    }