Rewrote messaging structure for more abstractness.

This commit adds a new boolean-configurable feature called last-message-reply-recipient, defaults to true for new installs and false for old installs, which states whether to use the new messaging functionality or not.
This commit deprecates Console#getCommandSender(Server) and provides Console#getInstance()#getCommandSender() for future usability.
This commit is contained in:
Ali Moghnieh 2015-10-27 17:34:59 +00:00 committed by vemacs
parent 77eb430b0b
commit 447b9db397
14 changed files with 444 additions and 122 deletions

View File

@ -4,8 +4,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class CommandSource implements IReplyTo {
private CommandSource replyTo = null;
public class CommandSource {
protected CommandSender sender;
public CommandSource(final CommandSender base) {
@ -37,14 +36,4 @@ public class CommandSource implements IReplyTo {
sender.sendMessage(message);
}
}
@Override
public void setReplyTo(final CommandSource user) {
replyTo = user;
}
@Override
public CommandSource getReplyTo() {
return replyTo;
}
}

View File

@ -1,32 +1,73 @@
package com.earth2me.essentials;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.messaging.SimpleMessageRecipient;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
public final class Console implements IReplyTo {
private static final Console instance = new Console();
private CommandSource replyTo;
public final static String NAME = "Console";
public final class Console implements IMessageRecipient {
public static final String NAME = "Console";
private static Console instance; // Set in essentials
private final IEssentials ess;
private final IMessageRecipient messageRecipient;
private Console() {
public static Console getInstance() {
return instance;
}
static void setInstance(IEssentials ess) { // Called in Essentials#onEnable()
instance = new Console(ess);
}
/**
* @deprecated Use {@link Console#getCommandSender()}
*/
@Deprecated
public static CommandSender getCommandSender(Server server) throws Exception {
return server.getConsoleSender();
}
@Override
public void setReplyTo(CommandSource user) {
replyTo = user;
private Console(IEssentials ess) {
this.ess = ess;
this.messageRecipient = new SimpleMessageRecipient(ess, this);
}
@Override
public CommandSource getReplyTo() {
return replyTo;
public CommandSender getCommandSender() {
return ess.getServer().getConsoleSender();
}
public static Console getConsoleReplyTo() {
return instance;
@Override public String getName() {
return Console.NAME;
}
@Override public String getDisplayName() {
return Console.NAME;
}
@Override public void sendMessage(String message) {
getCommandSender().sendMessage(message);
}
/* ================================
* >> DELEGATE METHODS
* ================================ */
@Override public MessageResponse sendMessage(IMessageRecipient recipient, String message) {
return this.messageRecipient.sendMessage(recipient, message);
}
@Override public MessageResponse onReceiveMessage(IMessageRecipient sender, String message) {
return this.messageRecipient.onReceiveMessage(sender, message);
}
@Override public IMessageRecipient getReplyRecipient() {
return this.messageRecipient.getReplyRecipient();
}
@Override public void setReplyRecipient(IMessageRecipient recipient) {
this.messageRecipient.setReplyRecipient(recipient);
}
}

View File

@ -119,6 +119,8 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
i18n = new I18n(this);
i18n.onEnable();
i18n.updateLocale("en");
Console.setInstance(this);
LOGGER.log(Level.INFO, tl("usingTempFolderForTesting"));
LOGGER.log(Level.INFO, dataFolder.toString());
this.initialize(null, server, new PluginDescriptionFile(new FileReader(new File("src" + File.separator + "plugin.yml"))), dataFolder, null, null);
@ -140,6 +142,9 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
i18n = new I18n(this);
i18n.onEnable();
execTimer.mark("I18n1");
Console.setInstance(this);
String serverString = Bukkit.getServer().getClass().getName();
for (int i = 1; i <= 7; i++) {
if (serverString.contains(".v1_" + i + "_R")) {

View File

@ -1,17 +0,0 @@
package com.earth2me.essentials;
public interface IReplyTo {
/**
* Sets the user to reply to
*
* @param user
*/
void setReplyTo(CommandSource user);
/**
* Gets the user the sender should reply to
*
* @return
*/
CommandSource getReplyTo();
}

View File

@ -225,4 +225,6 @@ public interface ISettings extends IConf {
boolean isNotifyNoNewMail();
boolean isDropItemsIfFull();
boolean isLastMessageReplyRecipient();
}

View File

@ -1112,4 +1112,8 @@ public class Settings implements net.ess3.api.ISettings {
long count = Runtime.getRuntime().maxMemory() / 1024 / 96;
return config.getInt("max-user-cache-count", (int) count);
}
@Override public boolean isLastMessageReplyRecipient() {
return config.getBoolean("last-message-reply-recipient", false);
}
}

View File

@ -1,6 +1,8 @@
package com.earth2me.essentials;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.messaging.SimpleMessageRecipient;
import com.earth2me.essentials.register.payment.Method;
import com.earth2me.essentials.register.payment.Methods;
import com.earth2me.essentials.utils.DateUtil;
@ -27,9 +29,9 @@ import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl;
public class User extends UserData implements Comparable<User>, IReplyTo, net.ess3.api.IUser {
public class User extends UserData implements Comparable<User>, IMessageRecipient, net.ess3.api.IUser {
private static final Logger logger = Logger.getLogger("Essentials");
private CommandSource replyTo = null;
private IMessageRecipient messageRecipient;
private transient UUID teleportRequester;
private transient boolean teleportRequestHere;
private transient Location teleportLocation;
@ -57,6 +59,7 @@ public class User extends UserData implements Comparable<User>, IReplyTo, net.es
if (this.getBase().isOnline()) {
lastOnlineActivity = System.currentTimeMillis();
}
this.messageRecipient = new SimpleMessageRecipient(ess, this);
}
User update(final Player base) {
@ -680,16 +683,6 @@ public class User extends UserData implements Comparable<User>, IReplyTo, net.es
}
}
@Override
public void setReplyTo(final CommandSource user) {
replyTo = user;
}
@Override
public CommandSource getReplyTo() {
return replyTo;
}
@Override
public int compareTo(final User other) {
return FormatUtil.stripFormat(getDisplayName()).compareToIgnoreCase(FormatUtil.stripFormat(other.getDisplayName()));
@ -718,4 +711,20 @@ public class User extends UserData implements Comparable<User>, IReplyTo, net.es
public String getName() {
return this.getBase().getName();
}
@Override public MessageResponse sendMessage(IMessageRecipient recipient, String message) {
return this.messageRecipient.sendMessage(recipient, message);
}
@Override public MessageResponse onReceiveMessage(IMessageRecipient sender, String message) {
return this.messageRecipient.onReceiveMessage(sender, message);
}
@Override public IMessageRecipient getReplyRecipient() {
return this.messageRecipient.getReplyRecipient();
}
@Override public void setReplyRecipient(IMessageRecipient recipient) {
this.messageRecipient.setReplyRecipient(recipient);
}
}

View File

@ -1,18 +1,17 @@
package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.IReplyTo;
import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import static com.earth2me.essentials.I18n.tl;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.User;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Server;
public class Commandmsg extends EssentialsLoopCommand {
final String translatedMe = tl("me");
public Commandmsg() {
super("msg");
@ -38,15 +37,10 @@ public class Commandmsg extends EssentialsLoopCommand {
canWildcard = true;
}
// Sending messages to console
if (args[0].equalsIgnoreCase(Console.NAME)) {
final IReplyTo replyTo = sender.isPlayer() ? ess.getUser(sender.getPlayer()) : Console.getConsoleReplyTo();
final String senderName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.NAME;
sender.sendMessage(tl("msgFormat", translatedMe, Console.NAME, message));
CommandSender cs = Console.getCommandSender(server);
cs.sendMessage(tl("msgFormat", senderName, translatedMe, message));
replyTo.setReplyTo(new CommandSource(cs));
Console.getConsoleReplyTo().setReplyTo(sender);
IMessageRecipient messageSender = sender.isPlayer() ? ess.getUser(sender.getPlayer()) : Console.getInstance();
messageSender.sendMessage(Console.getInstance(), message);
return;
}
@ -54,26 +48,8 @@ public class Commandmsg extends EssentialsLoopCommand {
}
@Override
protected void updatePlayer(final Server server, final CommandSource sender, final User matchedUser, final String[] args) {
final IReplyTo replyTo = sender.isPlayer() ? ess.getUser(sender.getPlayer()) : Console.getConsoleReplyTo();
final String senderName = sender.isPlayer() ? sender.getPlayer().getDisplayName() : Console.NAME;
if (matchedUser.isAfk()) {
sender.sendMessage(tl("userAFK", matchedUser.getDisplayName()));
}
sender.sendMessage(tl("msgFormat", translatedMe, matchedUser.getDisplayName(), args[0]));
if (sender.isPlayer() && matchedUser.isIgnoredPlayer(ess.getUser(sender.getPlayer()))) {
return;
}
if (matchedUser.isIgnoreMsg()) {
sender.sendMessage(tl("msgIgnore", matchedUser.getDisplayName()));
return;
}
matchedUser.sendMessage(tl("msgFormat", senderName, translatedMe, args[0]));
replyTo.setReplyTo(matchedUser.getSource());
matchedUser.setReplyTo(sender);
protected void updatePlayer(final Server server, final CommandSource sender, final User messageReceiver, final String[] args) {
IMessageRecipient messageSender = sender.isPlayer() ? ess.getUser(sender.getPlayer()) : Console.getInstance();
messageSender.sendMessage(messageReceiver, args[0]); // args[0] is the message.
}
}

View File

@ -2,8 +2,8 @@ package com.earth2me.essentials.commands;
import com.earth2me.essentials.CommandSource;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.IReplyTo;
import com.earth2me.essentials.User;
import com.earth2me.essentials.messaging.IMessageRecipient;
import com.earth2me.essentials.utils.FormatUtil;
import org.bukkit.Server;
@ -22,43 +22,23 @@ public class Commandr extends EssentialsCommand {
}
String message = getFinalArg(args, 0);
IReplyTo replyTo;
String senderName;
IMessageRecipient messageSender;
if (sender.isPlayer()) {
User user = ess.getUser(sender.getPlayer());
message = FormatUtil.formatMessage(user, "essentials.msg", message);
replyTo = user;
senderName = user.getDisplayName();
messageSender = user;
} else {
message = FormatUtil.replaceFormat(message);
replyTo = Console.getConsoleReplyTo();
senderName = Console.NAME;
messageSender = Console.getInstance();
}
final CommandSource target = replyTo.getReplyTo();
if (target == null || (target.isPlayer() && !target.getPlayer().isOnline())) {
final IMessageRecipient target = messageSender.getReplyRecipient();
// Check to make sure the sender does have a quick-reply recipient, and that the recipient is online.
if (target == null || (target instanceof User && !((User) target).getBase().isOnline())) {
throw new Exception(tl("foreverAlone"));
}
final String targetName = target.isPlayer() ? target.getPlayer().getDisplayName() : Console.NAME;
sender.sendMessage(tl("msgFormat", tl("me"), targetName, message));
if (target.isPlayer()) {
User player = ess.getUser(target.getPlayer());
if (sender.isPlayer() && player.isIgnoredPlayer(ess.getUser(sender.getPlayer()))) {
return;
}
}
target.sendMessage(tl("msgFormat", senderName, tl("me"), message));
replyTo.setReplyTo(target);
if (target != sender) {
if (target.isPlayer()) {
ess.getUser(target.getPlayer()).setReplyTo(sender);
} else {
Console.getConsoleReplyTo().setReplyTo(sender);
}
}
messageSender.sendMessage(target, message);
}
}

View File

@ -0,0 +1,96 @@
package com.earth2me.essentials.messaging;
/**
* Represents an interface for message recipients.
*/
public interface IMessageRecipient {
/**
* Sends (prints) a message to this recipient.
*
* @param message message
*/
void sendMessage(String message);
/**
* This method is called when this {@link IMessageRecipient} is sending a message to another {@link IMessageRecipient}.
* <br />
* The {@link MessageResponse} that is returned is used to determine what exactly should happen in the {@link #sendMessage(IMessageRecipient,
* String)} implementation by the {@code sender}.
*
* @param recipient recipient to receive the {@code message}
* @param message message to send
*
* @return the response of the message
*/
MessageResponse sendMessage(IMessageRecipient recipient, String message);
/**
* This method is called when this recipient is receiving a message from another {@link IMessageRecipient}.
* <br />
* The {@link MessageResponse} that is returned is used to determine what exactly should happen in the {@link #sendMessage(IMessageRecipient,
* String)} implementation by the {@code sender}.
* <p />
* <b>This method should only be called by {@link #sendMessage(IMessageRecipient, String)}.</b>
*
* @param sender sender of the {@code message}
* @param message message being received
*
* @return the response of the message
*/
MessageResponse onReceiveMessage(IMessageRecipient sender, String message);
/**
* Returns the name of this recipient. This name is typically used internally to identify this recipient.
*
* @return name of this recipient
*
* @see #getDisplayName()
*/
String getName();
/**
* Returns the display name of this recipient. This name is typically used when formatting messages.
*
* @return display name of this recipient
*/
String getDisplayName();
/**
* Returns the {@link IMessageRecipient} this recipient should send replies to.
*
* @return message recipient
*/
IMessageRecipient getReplyRecipient();
/**
* Sets the {@link IMessageRecipient} this recipient should send replies to.
*
* @param recipient message recipient to set
*/
void setReplyRecipient(IMessageRecipient recipient);
/**
* Represents a response for sending or receiving a message when using {@link IMessageRecipient#sendMessage(IMessageRecipient, String)} or
* {@link IMessageRecipient#onReceiveMessage(IMessageRecipient, String)}.
*/
enum MessageResponse {
/** States that the message was received and assumed readable by the receiver. */
SUCCESS,
/** States that the message was received, but the receiver was away, assuming the message was not read. */
SUCCESS_BUT_AFK,
/** States that the message was <b>NOT</b> received as a result of the receiver ignoring all messages. */
MESSAGES_IGNORED,
/** States that the message was <b>NOT</b> received as a result of the sender being ignored by the recipient. */
SENDER_IGNORED;
/**
* Returns whether this response is a success. In other words equal to {@link #SUCCESS} or {@link #SUCCESS_BUT_AFK}
*
* @return whether the response is a success
*/
public boolean isSuccess() {
return this == SUCCESS || this == SUCCESS_BUT_AFK;
}
}
}

View File

@ -0,0 +1,129 @@
package com.earth2me.essentials.messaging;
import static com.earth2me.essentials.I18n.tl;
import com.earth2me.essentials.Console;
import com.earth2me.essentials.IEssentials;
import com.earth2me.essentials.IUser;
import com.earth2me.essentials.User;
import java.lang.ref.WeakReference;
/**
* Represents a simple reusable implementation of {@link IMessageRecipient}. This class provides functionality for the following methods:
* <ul>
* <li>{@link IMessageRecipient#sendMessage(IMessageRecipient, String)}</li>
* <li>{@link IMessageRecipient#onReceiveMessage(IMessageRecipient, String)}</li>
* <li>{@link IMessageRecipient#getReplyRecipient()}</li>
* <li>{@link IMessageRecipient#setReplyRecipient(IMessageRecipient)}</li>
* </ul>
*
* <b>The given {@code parent} must implement the following methods to prevent overflow:</b>
* <ul>
* <li>{@link IMessageRecipient#sendMessage(String)}</li>
* <li>{@link IMessageRecipient#getName()}</li>
* <li>{@link IMessageRecipient#getDisplayName()}</li>
* </ul>
*
* The reply-recipient is wrapped in a {@link WeakReference}.
*/
public class SimpleMessageRecipient implements IMessageRecipient {
private final IEssentials ess;
private final IMessageRecipient parent;
private WeakReference<IMessageRecipient> replyRecipient;
public SimpleMessageRecipient(IEssentials ess, IMessageRecipient parent) {
this.ess = ess;
this.parent = parent;
}
@Override
public void sendMessage(String message) {
this.parent.sendMessage(message);
}
@Override
public String getName() {
return this.parent.getName();
}
@Override public String getDisplayName() {
return this.parent.getDisplayName();
}
@Override public MessageResponse sendMessage(IMessageRecipient recipient, String message) {
MessageResponse messageResponse = recipient.onReceiveMessage(this.parent, message);
switch (messageResponse) {
case MESSAGES_IGNORED:
sendMessage(tl("msgIgnore", getDisplayName()));
break;
case SENDER_IGNORED:
break;
// When this recipient is AFK, notify the sender. Then, proceed to send the message.
case SUCCESS_BUT_AFK: // TODO double check this functionality!
sendMessage(tl("userAFK", getDisplayName()));
default:
sendMessage(tl("msgFormat", tl("me"), recipient.getDisplayName(), message));
}
if (ess.getSettings().isLastMessageReplyRecipient()) {
// If the message was a success, set this sender's reply-recipient to the current recipient.
if (messageResponse.isSuccess()) {
setReplyRecipient(recipient);
}
}
return messageResponse;
}
@Override
public MessageResponse onReceiveMessage(IMessageRecipient sender, String message) {
User user = this.parent instanceof User ? (User) this.parent : null;
boolean afk = false;
if (user != null) {
if (user.isIgnoreMsg()
&& !(sender instanceof Console)) { // Console must never be ignored.
return MessageResponse.MESSAGES_IGNORED;
}
afk = user.isAfk();
// Check whether this recipient ignores the sender, only if the sender is not the console.
if (sender instanceof IUser && user.isIgnoredPlayer((IUser) sender)) {
return MessageResponse.SENDER_IGNORED;
}
}
// Display the formatted message to this recipient.
sendMessage(tl("msgFormat", sender.getDisplayName(), tl("me"), message));
if (ess.getSettings().isLastMessageReplyRecipient()) {
// If this recipient doesn't have a reply recipient, initiate by setting the first
// message sender to this recipient's replyRecipient.
if (getReplyRecipient() == null) {
setReplyRecipient(sender);
}
} else { // Old message functionality, always set the reply recipient to the last person who sent us a message.
setReplyRecipient(sender);
}
return afk ? MessageResponse.SUCCESS_BUT_AFK : MessageResponse.SUCCESS;
}
/**
* {@inheritDoc}
* <p />
* <b>This {@link com.earth2me.essentials.messaging.SimpleMessageRecipient} implementation stores the a weak reference to the recipient.</b>
*/
@Override
public IMessageRecipient getReplyRecipient() {
return replyRecipient == null ? null : replyRecipient.get();
}
/**
* {@inheritDoc}
* <p />
* <b>This {@link com.earth2me.essentials.messaging.SimpleMessageRecipient} implementation stores the a weak reference to the recipient.</b>
*/
@Override
public void setReplyRecipient(final IMessageRecipient replyRecipient) {
this.replyRecipient = new WeakReference<>(replyRecipient);
}
}

View File

@ -452,6 +452,10 @@ mails-per-minute: 1000
# Set to -1 to disable, and essentials.tempban.unlimited can be used to override.
max-tempban-time: -1
# Changes /reply functionality. If true, /r goes to the person you messaged last, otherwise the first person that messaged you.
# If false, /r goes to the last person that messaged you.
last-message-reply-recipient: true
############################################################
# +------------------------------------------------------+ #
# | EssentialsHome | #

View File

@ -0,0 +1,104 @@
package com.earth2me.essentials;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.commands.NoChargeException;
import org.bukkit.World.Environment;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.InvalidDescriptionException;
import org.junit.Test;
import java.io.IOException;
public class MessagingTest {
private final OfflinePlayer base1;
private final Essentials ess;
private final FakeServer server;
public MessagingTest() {
server = new FakeServer();
server.createWorld("testWorld", Environment.NORMAL);
ess = new Essentials(server);
try {
ess.setupForTesting(server);
} catch (InvalidDescriptionException ex) {
fail("InvalidDescriptionException");
} catch (IOException ex) {
fail("IOException");
}
base1 = server.createPlayer("testPlayer1");
server.addPlayer(base1);
ess.getUser(base1);
}
private void runCommand(String command, User user, String args) throws Exception {
runCommand(command, user, args.split("\\s+"));
}
private void runCommand(String command, User user, String[] args) throws Exception {
IEssentialsCommand cmd;
try {
cmd = (IEssentialsCommand) Essentials.class.getClassLoader()
.loadClass("com.earth2me.essentials.commands.Command" + command).newInstance();
cmd.setEssentials(ess);
cmd.run(server, user, command, null, args);
} catch (NoChargeException ex) {
}
}
private void runConsoleCommand(String command, String args) throws Exception {
runConsoleCommand(command, args.split("\\s+"));
}
private void runConsoleCommand(String command, String[] args) throws Exception {
IEssentialsCommand cmd;
CommandSender sender = server.getConsoleSender();
try {
cmd = (IEssentialsCommand) Essentials.class.getClassLoader()
.loadClass("com.earth2me.essentials.commands.Command" + command).newInstance();
cmd.setEssentials(ess);
cmd.run(server, new CommandSource(sender), command, null, args);
} catch (NoChargeException ex) {
}
}
@Test(expected = Exception.class) // I really don't like this, but see note below about console reply
public void testMessage() throws Exception {
User user1 = ess.getUser(base1);
Console console = Console.getInstance();
if (ess.getSettings().isLastMessageReplyRecipient()) {
assertNull(console.getReplyRecipient()); // console never messaged or received messages from anyone.
// user1 messages console saying "Hey, master!"
runCommand("msg", user1, console.getName() + " Hey, master!");
// console should now have its reply-recipient as user1, since the console doesn't have a previous recipient.
assertEquals(console.getReplyRecipient(), user1);
if (ess.getSettings().isLastMessageReplyRecipient()) {
runCommand("r", user1, "This is me sending you a message using /r without you replying!");
}
// Not really much of a strict test, but just "testing" console output.
user1._setAfk(true);
// Console replies using "/r Hey, son!"
//
// This throws Exception because the base1 is an OfflinePlayer (isOnline() returns false).
runConsoleCommand("r", "Hey, son!");
} else {
assertNull(console.getReplyRecipient()); // user2 never received messages from anyone.
}
}
}

View File

@ -304,7 +304,7 @@ public class XMPPManager extends Handler implements MessageListener, ChatManager
private void sendCommand(final Chat chat, final String message) {
if (config.getStringList("op-users").contains(StringUtils.parseBareAddress(chat.getParticipant()))) {
try {
parent.getServer().dispatchCommand(Console.getCommandSender(parent.getServer()), message.substring(1));
parent.getServer().dispatchCommand(Console.getInstance().getCommandSender(), message.substring(1));
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
}