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 }