001    package org.bukkit.plugin.messaging;
002    
003    import com.google.common.collect.ImmutableSet;
004    import com.google.common.collect.ImmutableSet.Builder;
005    import java.util.HashMap;
006    import java.util.HashSet;
007    import java.util.Map;
008    import java.util.Set;
009    import org.bukkit.entity.Player;
010    import org.bukkit.plugin.Plugin;
011    
012    /**
013     * Standard implementation to {@link Messenger}
014     */
015    public class StandardMessenger implements Messenger {
016        private final Map<String, Set<PluginMessageListenerRegistration>> incomingByChannel = new HashMap<String, Set<PluginMessageListenerRegistration>>();
017        private final Map<Plugin, Set<PluginMessageListenerRegistration>> incomingByPlugin = new HashMap<Plugin, Set<PluginMessageListenerRegistration>>();
018        private final Map<String, Set<Plugin>> outgoingByChannel = new HashMap<String, Set<Plugin>>();
019        private final Map<Plugin, Set<String>> outgoingByPlugin = new HashMap<Plugin, Set<String>>();
020        private final Object incomingLock = new Object();
021        private final Object outgoingLock = new Object();
022    
023        private void addToOutgoing(Plugin plugin, String channel) {
024            synchronized (outgoingLock) {
025                Set<Plugin> plugins = outgoingByChannel.get(channel);
026                Set<String> channels = outgoingByPlugin.get(plugin);
027    
028                if (plugins == null) {
029                    plugins = new HashSet<Plugin>();
030                    outgoingByChannel.put(channel, plugins);
031                }
032    
033                if (channels == null) {
034                    channels = new HashSet<String>();
035                    outgoingByPlugin.put(plugin, channels);
036                }
037    
038                plugins.add(plugin);
039                channels.add(channel);
040            }
041        }
042    
043        private void removeFromOutgoing(Plugin plugin, String channel) {
044            synchronized (outgoingLock) {
045                Set<Plugin> plugins = outgoingByChannel.get(channel);
046                Set<String> channels = outgoingByPlugin.get(plugin);
047    
048                if (plugins != null) {
049                    plugins.remove(plugin);
050    
051                    if (plugins.isEmpty()) {
052                        outgoingByChannel.remove(channel);
053                    }
054                }
055    
056                if (channels != null) {
057                    channels.remove(channel);
058    
059                    if (channels.isEmpty()) {
060                        outgoingByChannel.remove(channel);
061                    }
062                }
063            }
064        }
065    
066        private void removeFromOutgoing(Plugin plugin) {
067            synchronized (outgoingLock) {
068                Set<String> channels = outgoingByPlugin.get(plugin);
069    
070                if (channels != null) {
071                    String[] toRemove = channels.toArray(new String[0]);
072    
073                    outgoingByPlugin.remove(plugin);
074    
075                    for (String channel : toRemove) {
076                        removeFromOutgoing(plugin, channel);
077                    }
078                }
079            }
080        }
081    
082        private void addToIncoming(PluginMessageListenerRegistration registration) {
083            synchronized (incomingLock) {
084                Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(registration.getChannel());
085    
086                if (registrations == null) {
087                    registrations = new HashSet<PluginMessageListenerRegistration>();
088                    incomingByChannel.put(registration.getChannel(), registrations);
089                } else {
090                    if (registrations.contains(registration)) {
091                        throw new IllegalArgumentException("This registration already exists");
092                    }
093                }
094    
095                registrations.add(registration);
096    
097                registrations = incomingByPlugin.get(registration.getPlugin());
098    
099                if (registrations == null) {
100                    registrations = new HashSet<PluginMessageListenerRegistration>();
101                    incomingByPlugin.put(registration.getPlugin(), registrations);
102                } else {
103                    if (registrations.contains(registration)) {
104                        throw new IllegalArgumentException("This registration already exists");
105                    }
106                }
107    
108                registrations.add(registration);
109            }
110        }
111    
112        private void removeFromIncoming(PluginMessageListenerRegistration registration) {
113            synchronized (incomingLock) {
114                Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(registration.getChannel());
115    
116                if (registrations != null) {
117                    registrations.remove(registration);
118    
119                    if (registrations.isEmpty()) {
120                        incomingByChannel.remove(registration.getChannel());
121                    }
122                }
123    
124                registrations = incomingByPlugin.get(registration.getPlugin());
125    
126                if (registrations != null) {
127                    registrations.remove(registration);
128    
129                    if (registrations.isEmpty()) {
130                        incomingByPlugin.remove(registration.getPlugin());
131                    }
132                }
133            }
134        }
135    
136        private void removeFromIncoming(Plugin plugin, String channel) {
137            synchronized (incomingLock) {
138                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
139    
140                if (registrations != null) {
141                    PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]);
142    
143                    for (PluginMessageListenerRegistration registration : toRemove) {
144                        if (registration.getChannel().equals(channel)) {
145                            removeFromIncoming(registration);
146                        }
147                    }
148                }
149            }
150        }
151    
152        private void removeFromIncoming(Plugin plugin) {
153            synchronized (incomingLock) {
154                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
155    
156                if (registrations != null) {
157                    PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]);
158    
159                    incomingByPlugin.remove(plugin);
160    
161                    for (PluginMessageListenerRegistration registration : toRemove) {
162                        removeFromIncoming(registration);
163                    }
164                }
165            }
166        }
167    
168        public boolean isReservedChannel(String channel) {
169            validateChannel(channel);
170    
171            return channel.equals("REGISTER") || channel.equals("UNREGISTER");
172        }
173    
174        public void registerOutgoingPluginChannel(Plugin plugin, String channel) {
175            if (plugin == null) {
176                throw new IllegalArgumentException("Plugin cannot be null");
177            }
178            validateChannel(channel);
179            if (isReservedChannel(channel)) {
180                throw new ReservedChannelException(channel);
181            }
182    
183            addToOutgoing(plugin, channel);
184        }
185    
186        public void unregisterOutgoingPluginChannel(Plugin plugin, String channel) {
187            if (plugin == null) {
188                throw new IllegalArgumentException("Plugin cannot be null");
189            }
190            validateChannel(channel);
191    
192            removeFromOutgoing(plugin, channel);
193        }
194    
195        public void unregisterOutgoingPluginChannel(Plugin plugin) {
196            if (plugin == null) {
197                throw new IllegalArgumentException("Plugin cannot be null");
198            }
199    
200            removeFromOutgoing(plugin);
201        }
202    
203        public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) {
204            if (plugin == null) {
205                throw new IllegalArgumentException("Plugin cannot be null");
206            }
207            validateChannel(channel);
208            if (isReservedChannel(channel)) {
209                throw new ReservedChannelException(channel);
210            }
211            if (listener == null) {
212                throw new IllegalArgumentException("Listener cannot be null");
213            }
214    
215            PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener);
216    
217            addToIncoming(result);
218    
219            return result;
220        }
221    
222        public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) {
223            if (plugin == null) {
224                throw new IllegalArgumentException("Plugin cannot be null");
225            }
226            if (listener == null) {
227                throw new IllegalArgumentException("Listener cannot be null");
228            }
229            validateChannel(channel);
230    
231            removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener));
232        }
233    
234        public void unregisterIncomingPluginChannel(Plugin plugin, String channel) {
235            if (plugin == null) {
236                throw new IllegalArgumentException("Plugin cannot be null");
237            }
238            validateChannel(channel);
239    
240            removeFromIncoming(plugin, channel);
241        }
242    
243        public void unregisterIncomingPluginChannel(Plugin plugin) {
244            if (plugin == null) {
245                throw new IllegalArgumentException("Plugin cannot be null");
246            }
247    
248            removeFromIncoming(plugin);
249        }
250    
251        public Set<String> getOutgoingChannels() {
252            synchronized (outgoingLock) {
253                Set<String> keys = outgoingByChannel.keySet();
254                return ImmutableSet.copyOf(keys);
255            }
256        }
257    
258        public Set<String> getOutgoingChannels(Plugin plugin) {
259            if (plugin == null) {
260                throw new IllegalArgumentException("Plugin cannot be null");
261            }
262    
263            synchronized (outgoingLock) {
264                Set<String> channels = outgoingByPlugin.get(plugin);
265    
266                if (channels != null) {
267                    return ImmutableSet.copyOf(channels);
268                } else {
269                    return ImmutableSet.of();
270                }
271            }
272        }
273    
274        public Set<String> getIncomingChannels() {
275            synchronized (incomingLock) {
276                Set<String> keys = incomingByChannel.keySet();
277                return ImmutableSet.copyOf(keys);
278            }
279        }
280    
281        public Set<String> getIncomingChannels(Plugin plugin) {
282            if (plugin == null) {
283                throw new IllegalArgumentException("Plugin cannot be null");
284            }
285    
286            synchronized (incomingLock) {
287                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
288    
289                if (registrations != null) {
290                    Builder<String> builder = ImmutableSet.builder();
291    
292                    for (PluginMessageListenerRegistration registration : registrations) {
293                        builder.add(registration.getChannel());
294                    }
295    
296                    return builder.build();
297                } else {
298                    return ImmutableSet.of();
299                }
300            }
301        }
302    
303        public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin) {
304            if (plugin == null) {
305                throw new IllegalArgumentException("Plugin cannot be null");
306            }
307    
308            synchronized (incomingLock) {
309                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
310    
311                if (registrations != null) {
312                    return ImmutableSet.copyOf(registrations);
313                } else {
314                    return ImmutableSet.of();
315                }
316            }
317        }
318    
319        public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(String channel) {
320            validateChannel(channel);
321    
322            synchronized (incomingLock) {
323                Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(channel);
324    
325                if (registrations != null) {
326                    return ImmutableSet.copyOf(registrations);
327                } else {
328                    return ImmutableSet.of();
329                }
330            }
331        }
332    
333        public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin, String channel) {
334            if (plugin == null) {
335                throw new IllegalArgumentException("Plugin cannot be null");
336            }
337            validateChannel(channel);
338    
339            synchronized (incomingLock) {
340                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
341    
342                if (registrations != null) {
343                    Builder<PluginMessageListenerRegistration> builder = ImmutableSet.builder();
344    
345                    for (PluginMessageListenerRegistration registration : registrations) {
346                        if (registration.getChannel().equals(channel)) {
347                            builder.add(registration);
348                        }
349                    }
350    
351                    return builder.build();
352                } else {
353                    return ImmutableSet.of();
354                }
355            }
356        }
357    
358        public boolean isRegistrationValid(PluginMessageListenerRegistration registration) {
359            if (registration == null) {
360                throw new IllegalArgumentException("Registration cannot be null");
361            }
362    
363            synchronized (incomingLock) {
364                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(registration.getPlugin());
365    
366                if (registrations != null) {
367                    return registrations.contains(registration);
368                }
369    
370                return false;
371            }
372        }
373    
374        public boolean isIncomingChannelRegistered(Plugin plugin, String channel) {
375            if (plugin == null) {
376                throw new IllegalArgumentException("Plugin cannot be null");
377            }
378            validateChannel(channel);
379    
380            synchronized (incomingLock) {
381                Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin);
382    
383                if (registrations != null) {
384                    for (PluginMessageListenerRegistration registration : registrations) {
385                        if (registration.getChannel().equals(channel)) {
386                            return true;
387                        }
388                    }
389                }
390    
391                return false;
392            }
393        }
394    
395        public boolean isOutgoingChannelRegistered(Plugin plugin, String channel) {
396            if (plugin == null) {
397                throw new IllegalArgumentException("Plugin cannot be null");
398            }
399            validateChannel(channel);
400    
401            synchronized (outgoingLock) {
402                Set<String> channels = outgoingByPlugin.get(plugin);
403    
404                if (channels != null) {
405                    return channels.contains(channel);
406                }
407    
408                return false;
409            }
410        }
411    
412        public void dispatchIncomingMessage(Player source, String channel, byte[] message) {
413            if (source == null) {
414                throw new IllegalArgumentException("Player source cannot be null");
415            }
416            if (message == null) {
417                throw new IllegalArgumentException("Message cannot be null");
418            }
419            validateChannel(channel);
420    
421            Set<PluginMessageListenerRegistration> registrations = getIncomingChannelRegistrations(channel);
422    
423            for (PluginMessageListenerRegistration registration : registrations) {
424                registration.getListener().onPluginMessageReceived(channel, source, message);
425            }
426        }
427    
428        /**
429         * Validates a Plugin Channel name.
430         *
431         * @param channel Channel name to validate.
432         */
433        public static void validateChannel(String channel) {
434            if (channel == null) {
435                throw new IllegalArgumentException("Channel cannot be null");
436            }
437            if (channel.length() > Messenger.MAX_CHANNEL_SIZE) {
438                throw new ChannelNameTooLongException(channel);
439            }
440        }
441    
442        /**
443         * Validates the input of a Plugin Message, ensuring the arguments are all
444         * valid.
445         *
446         * @param messenger Messenger to use for validation.
447         * @param source Source plugin of the Message.
448         * @param channel Plugin Channel to send the message by.
449         * @param message Raw message payload to send.
450         * @throws IllegalArgumentException Thrown if the source plugin is
451         *     disabled.
452         * @throws IllegalArgumentException Thrown if source, channel or message
453         *     is null.
454         * @throws MessageTooLargeException Thrown if the message is too big.
455         * @throws ChannelNameTooLongException Thrown if the channel name is too
456         *     long.
457         * @throws ChannelNotRegisteredException Thrown if the channel is not
458         *     registered for this plugin.
459         */
460        public static void validatePluginMessage(Messenger messenger, Plugin source, String channel, byte[] message) {
461            if (messenger == null) {
462                throw new IllegalArgumentException("Messenger cannot be null");
463            }
464            if (source == null) {
465                throw new IllegalArgumentException("Plugin source cannot be null");
466            }
467            if (!source.isEnabled()) {
468                throw new IllegalArgumentException("Plugin must be enabled to send messages");
469            }
470            if (message == null) {
471                throw new IllegalArgumentException("Message cannot be null");
472            }
473            if (!messenger.isOutgoingChannelRegistered(source, channel)) {
474                throw new ChannelNotRegisteredException(channel);
475            }
476            if (message.length > Messenger.MAX_MESSAGE_SIZE) {
477                throw new MessageTooLargeException(message);
478            }
479            validateChannel(channel);
480        }
481    }