Improved the Command - CommandFactory implementation.

* Moved from 'init' method to constructor.
* Expected format for the constructor described in the Command
interface comments.
* Distinguish between exceptions that occur during construction of the
command, log these and inform the user of an error; and exception
occurring during the execution of the command.
* Better error handling for commands.

To do:
* Add 'help' method to the Command interface.
* Catch IllegalArgumentException and automatically display help()
information to the user.
cefexperiments
Danny van Heumen 12 years ago
parent 6cb3c0e2de
commit d750dab798

@ -1012,6 +1012,18 @@ public void sendMessage(final Message message)
.UNSUPPORTED_OPERATION,
e.getMessage(), new Date(), message);
}
catch (BadCommandException e)
{
LOGGER.error("An error occurred while constructing "
+ "the command. This is most likely due to a bug "
+ "in the implementation of the command. Message: "
+ message + "'", e);
this.fireMessageDeliveryFailedEvent(
ChatRoomMessageDeliveryFailedEvent.INTERNAL_ERROR,
"Command cannot be executed. This is most likely due "
+ "to a bug in the implementation.", new Date(),
message);
}
}
else
{

@ -6,35 +6,39 @@
*/
package net.java.sip.communicator.impl.protocol.irc;
import net.java.sip.communicator.impl.protocol.irc.exception.*;
/**
* Interface for the implementation of individual IRC commands.
*
* This interface defines the format for the implementation of commands that can
* be called for via the messaging input field.
*
* A command is instantiated for each encounter. {@link #init} will be upon
* instantiation to set up the state before actually performing the command.
* Then {@link #execute} is called in order to execute the command.
* A new command object is instantiated for each encounter. Then
* {@link #execute} is called to execute the command.
*
* Instantiation will be done by the CommandFactory. The CommandFactory expects
* to find a constructor with the following types as arguments (in order):
* <ol>
* <li>{@link ProtocolProviderServiceIrcImpl}</li>
* <li>{@link IrcConnection}</li>
* </ol>
*
* If no suitable constructor is found, an {@link BadCommandException} will be
* thrown.
*
* For more advanced IRC commands, one can register listeners with the
* underlying IRC client. This way you can intercept IRC messages as they are
* received. Using this method you can send messages, receive replies and act on
* (other) events.
*
* FIXME Additionally, describe the AbstractIrcMessageListener type once this
* implementation is merged with Jitsi main line.
*
* @author Danny van Heumen
*/
public interface Command
{
/**
* Initialize this instance of a command. Initialization may include
* registering listeners for the current connection such that a command can
* act upon the server's response.
*
* {@link #init} is guaranteed to be called before the command gets
* executed and thus can be used to initialize any dependencies or listeners
* that are required for the command to be executed.
*
* @param provider the protocol provider service
* @param connection the IRC connection instance
*/
void init(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection);
/**
* Execute the command using the full line.
*

@ -6,6 +6,7 @@
*/
package net.java.sip.communicator.impl.protocol.irc;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
@ -15,6 +16,9 @@
/**
* Command factory.
*
* TODO Consider implementing 1 "management" command for querying the registered
* commands at runtime and maybe some other things. Not very urgent, though.
*
* @author Danny van Heumen
*/
public class CommandFactory
@ -134,9 +138,12 @@ public static synchronized void unregisterCommand(
* @param command the command to look up and instantiate
* @return returns a command instance
* @throws UnsupportedCommandException in case command cannot be found
* @throws BadCommandException In case of a incompatible command or bad
* implementation.
*/
public Command createCommand(final String command)
throws UnsupportedCommandException
throws UnsupportedCommandException,
BadCommandException
{
if (command == null || command.isEmpty())
{
@ -148,29 +155,40 @@ public Command createCommand(final String command)
{
throw new UnsupportedCommandException(command);
}
final Command cmd;
try
{
// FIXME change to construct with provider and connection instance,
// remove init() method from interface
cmd = type.newInstance();
cmd.init(this.provider, this.connection);
return cmd;
Constructor<? extends Command> cmdCtor =
type.getConstructor(ProtocolProviderServiceIrcImpl.class,
IrcConnection.class);
return cmdCtor.newInstance(this.provider, this.connection);
}
catch (NoSuchMethodException e)
{
throw new BadCommandException(command, type,
"There is no compatible constructor for instantiating this "
+ "command.", e);
}
catch (InstantiationException e)
{
throw new BadCommandException(command, type,
"Unable to instantiate this command class.", e);
}
catch (IllegalAccessException e)
{
throw new BadCommandException(command, type,
"Unable to access the constructor of this command class.", e);
}
catch (InstantiationException ex)
catch (InvocationTargetException e)
{
throw new IllegalStateException(
"A bad command implementation has been registered. It fails"
+ " to instantiate.",
ex);
throw new BadCommandException(command, type,
"An exception occurred while executing the constructor.", e);
}
catch (IllegalAccessException ex)
catch (IllegalArgumentException e)
{
throw new IllegalStateException(
"A bad command implementation has been registered. It does "
+ "not allow access to its constructor and therefore cannot"
+ " be instantiated.",
ex);
throw new BadCommandException(command, type,
"Invalid arguments were passed to the "
+ "constructor. This may be a bug in the CommandFactory "
+ "implementation.", e);
}
}
}

@ -24,6 +24,8 @@
* TODO Implement messaging service for offline messages and such. (MemoServ -
* message relaying services)
*
* TODO move IRC commands to separate package
*
* @author Danny van Heumen
*/
public class MessageManager
@ -141,9 +143,12 @@ public MessageManager(final IrcConnection connection, final IRCApi irc,
* @param chatroom the chat room
* @param message the command message
* @throws UnsupportedCommandException for unknown or unsupported commands
* @throws BadCommandException in case of incompatible command or bad
* implementation
*/
public void command(final ChatRoomIrcImpl chatroom, final String message)
throws UnsupportedCommandException
throws UnsupportedCommandException,
BadCommandException
{
if (!this.connectionState.isConnected())
{
@ -158,9 +163,11 @@ public void command(final ChatRoomIrcImpl chatroom, final String message)
* @param contact the chat room
* @param message the command message
* @throws UnsupportedCommandException for unknown or unsupported commands
* @throws BadCommandException in case of a bad command implementation
*/
public void command(final Contact contact, final MessageIrcImpl message)
throws UnsupportedCommandException
throws UnsupportedCommandException,
BadCommandException
{
if (!this.connectionState.isConnected())
{
@ -174,9 +181,14 @@ public void command(final Contact contact, final MessageIrcImpl message)
*
* @param source Source contact or chat room from which the message is sent.
* @param message Command message
* @throws UnsupportedCommandException in case a suitable command could not
* be found
* @throws BadCommandException in case of an incompatible command or a bad
* implementation
*/
private void command(final String source, final String message)
throws UnsupportedCommandException
throws UnsupportedCommandException,
BadCommandException
{
final String msg = message.toLowerCase();
final int end = msg.indexOf(' ');

@ -134,6 +134,14 @@ public void sendInstantMessage(final Contact to, final Message original)
MessageDeliveryFailedEvent
.UNSUPPORTED_OPERATION);
}
catch (BadCommandException e)
{
LOGGER.error("Error during command execution. "
+ "This is most likely due to a bug in the "
+ "implementation of the command.", e);
fireMessageDeliveryFailed(message, to,
MessageDeliveryFailedEvent.INTERNAL_ERROR);
}
}
else
{

@ -24,7 +24,7 @@ public class Join implements Command
/**
* Instance of the IRC connection.
*/
private IrcConnection connection;
private final IrcConnection connection;
/**
* Initialization of the /join command. Join a channel.
@ -32,9 +32,8 @@ public class Join implements Command
* @param provider the provider instance
* @param connection the IRC connection instance
*/
@Override
public void init(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
public Join(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
{
if (connection == null)
{

@ -15,7 +15,7 @@
* @author Danny van Heumen
*/
public class Me
implements Command
implements Command
{
/**
* Me command prefix index.
@ -25,7 +25,7 @@ public class Me
/**
* IRC connection instance.
*/
private IrcConnection connection;
private final IrcConnection connection;
/**
* Initialization of the /me command.
@ -33,8 +33,7 @@ public class Me
* @param provider the provider instance
* @param connection the connection instance
*/
@Override
public void init(final ProtocolProviderServiceIrcImpl provider,
public Me(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
{
if (connection == null)

@ -31,8 +31,7 @@ public class Mode implements Command
* @param provider the provider instance
* @param connection the connection instance
*/
@Override
public void init(final ProtocolProviderServiceIrcImpl provider,
public Mode(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
{
if (connection == null)

@ -31,8 +31,7 @@ public class Msg implements Command
* @param provider the provider instance
* @param connection the connection instance
*/
@Override
public void init(final ProtocolProviderServiceIrcImpl provider,
public Msg(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
{
if (connection == null)

@ -26,8 +26,7 @@ public class Nick implements Command
* @param provider the provider instance
* @param connection the connection instance
*/
@Override
public void init(final ProtocolProviderServiceIrcImpl provider,
public Nick(final ProtocolProviderServiceIrcImpl provider,
final IrcConnection connection)
{
if (connection == null)

@ -0,0 +1,70 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc.exception;
import net.java.sip.communicator.impl.protocol.irc.*;
/**
* Exception indicating a bad command implementation.
*
* @author Danny van Heumen
*/
public class BadCommandException
extends Exception
{
/**
* Serialization id.
*/
private static final long serialVersionUID = 1L;
/**
* The command name used to look up the type.
*/
private final String command;
/**
* The command type that caused the exception.
*/
private Class<? extends Command> type;
/**
* Constructor.
*
* @param command the command
* @param type the command implementation
* @param message the error message
* @param e the cause
*/
public BadCommandException(final String command,
final Class<? extends Command> type, final String message,
final Throwable e)
{
super("An error occurred while preparing command '" + command + "' ("
+ type.getCanonicalName() + "): " + message, e);
this.command = command;
}
/**
* Get the command.
*
* @return returns the name of the failed command
*/
public String getCommand()
{
return this.command;
}
/**
* Get the type of the implementation.
*
* @return returns the type that implements the command
*/
public Class<? extends Command> getType()
{
return this.type;
}
}

@ -23,19 +23,7 @@ public void testRegisterNullCommand()
{
try
{
CommandFactory.registerCommand(null, new Command()
{
@Override
public void init(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection)
{
}
@Override
public void execute(String source, String line)
{
}
}.getClass());
CommandFactory.registerCommand(null, Test.class);
Assert.fail();
}
catch (IllegalArgumentException e)
@ -82,12 +70,6 @@ public void testUnregisterMultipleAmongOtherTypes()
{
Command anotherType = new Command() {
@Override
public void init(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection)
{
}
@Override
public void execute(String source, String line)
{
@ -124,9 +106,7 @@ public void testUnregisterOneAmongMultipleSameType()
public static class Test implements Command
{
@Override
public void init(ProtocolProviderServiceIrcImpl provider,
public Test(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection)
{
}
@ -177,7 +157,7 @@ public void testConstruction()
new CommandFactory(provider, connection);
}
public void testNonExistingCommand()
public void testNonExistingCommand() throws BadCommandException
{
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
@ -194,7 +174,7 @@ public void testNonExistingCommand()
}
}
public void testCreateNullCommandName() throws UnsupportedCommandException
public void testCreateNullCommandName() throws UnsupportedCommandException, BadCommandException
{
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
@ -213,7 +193,7 @@ public void testCreateNullCommandName() throws UnsupportedCommandException
CommandFactory.unregisterCommand(Test.class, null);
}
public void testCreateEmptyCommandName() throws UnsupportedCommandException
public void testCreateEmptyCommandName() throws UnsupportedCommandException, BadCommandException
{
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
@ -235,7 +215,7 @@ public void testCreateEmptyCommandName() throws UnsupportedCommandException
}
}
public void testExistingCommand() throws UnsupportedCommandException
public void testExistingCommand() throws UnsupportedCommandException, BadCommandException
{
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
@ -268,7 +248,7 @@ public void testUnreachableCommand() throws UnsupportedCommandException
factory.createCommand("test");
Assert.fail();
}
catch (IllegalStateException e)
catch (BadCommandException e)
{
}
finally
@ -290,7 +270,7 @@ public void testBadCommand() throws UnsupportedCommandException
factory.createCommand("test");
Assert.fail();
}
catch (IllegalStateException e)
catch (BadCommandException e)
{
}
finally
@ -299,11 +279,9 @@ public void testBadCommand() throws UnsupportedCommandException
}
}
private static class Unreachable implements Command
private static final class Unreachable implements Command
{
@Override
public void init(ProtocolProviderServiceIrcImpl provider,
private Unreachable(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection)
{
}
@ -316,5 +294,9 @@ public void execute(String source, String line)
public abstract static class BadImplementation implements Command
{
public BadImplementation(ProtocolProviderServiceIrcImpl provider,
IrcConnection connection)
{
}
}
}

@ -17,18 +17,12 @@ public class JoinTest
extends TestCase
{
public void testConstruction()
{
new Join();
}
public void testGoodInit()
{
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Join join = new Join();
join.init(null, connection);
new Join(null, connection);
}
public void testBadInit()
@ -36,10 +30,9 @@ public void testBadInit()
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
EasyMock.replay(provider);
Join join = new Join();
try
{
join.init(provider, null);
new Join(provider, null);
Assert.fail();
}
catch (IllegalArgumentException e)
@ -52,8 +45,7 @@ public void testEmptyJoin()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Join join = new Join();
join.init(null, connection);
Join join = new Join(null, connection);
join.execute("#test", "/join");
}
@ -62,8 +54,7 @@ public void testJoinEmptyChannelNoPassword()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Join join = new Join();
join.init(null, connection);
Join join = new Join(null, connection);
try
{
join.execute("#test", "/join ");
@ -83,8 +74,7 @@ public void testJoinWithChannelNoPassword()
EasyMock.expectLastCall();
EasyMock.replay(connection, client);
Join join = new Join();
join.init(null, connection);
Join join = new Join(null, connection);
join.execute("#test", "/join #test");
}
@ -97,8 +87,7 @@ public void testJoinWithChannelWithPassword()
EasyMock.expectLastCall();
EasyMock.replay(connection, client);
Join join = new Join();
join.init(null, connection);
Join join = new Join(null, connection);
join.execute("#test", "/join #test top-secret");
}
}

@ -16,18 +16,12 @@
public class MeTest extends TestCase
{
public void testConstruction()
{
new Me();
}
public void testGoodInit()
{
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Me me = new Me();
me.init(null, connection);
Me me = new Me(null, connection);
}
public void testBadInit()
@ -35,10 +29,9 @@ public void testBadInit()
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
EasyMock.replay(provider);
Me me = new Me();
try
{
me.init(provider, null);
Me me = new Me(provider, null);
Assert.fail();
}
catch (IllegalArgumentException e)
@ -52,8 +45,7 @@ public void testNoMessage()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Me me = new Me();
me.init(provider, connection);
Me me = new Me(provider, connection);
me.execute("#test", "/me");
}
@ -63,8 +55,7 @@ public void testZeroLengthMessage()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Me me = new Me();
me.init(provider, connection);
Me me = new Me(provider, connection);
try
{
me.execute("#test", "/me ");
@ -85,8 +76,7 @@ public void testSendMessage()
EasyMock.expectLastCall();
EasyMock.replay(provider, connection, client);
Me me = new Me();
me.init(provider, connection);
Me me = new Me(provider, connection);
me.execute("#test", "/me says hello world!");
}
}

@ -17,18 +17,12 @@ public class ModeTest
extends TestCase
{
public void testConstruction()
{
new Mode();
}
public void testGoodInit()
{
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Mode mode = new Mode();
mode.init(null, connection);
Mode mode = new Mode(null, connection);
}
public void testBadInit()
@ -37,10 +31,9 @@ public void testBadInit()
EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
EasyMock.replay(provider);
Mode mode = new Mode();
try
{
mode.init(provider, null);
Mode mode = new Mode(provider, null);
Assert.fail();
}
catch (IllegalArgumentException e)
@ -53,8 +46,7 @@ public void testEmptyCommand()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Mode mode = new Mode();
mode.init(null, connection);
Mode mode = new Mode(null, connection);
mode.execute("#test", "/mode");
}
@ -63,8 +55,7 @@ public void testEmptyModeLine()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Mode mode = new Mode();
mode.init(null, connection);
Mode mode = new Mode(null, connection);
mode.execute("#test", "/mode ");
}
@ -73,8 +64,7 @@ public void testSpacesModeLine()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Mode mode = new Mode();
mode.init(null, connection);
Mode mode = new Mode(null, connection);
try
{
mode.execute("#test", "/mode ");
@ -93,8 +83,7 @@ public void testCorrectModeCommand()
EasyMock.expectLastCall();
EasyMock.replay(connection, client);
Mode mode = new Mode();
mode.init(null, connection);
Mode mode = new Mode(null, connection);
mode.execute("#test", "/mode +o ThaDud3");
}
}

@ -19,18 +19,12 @@
public class MsgTest
extends TestCase
{
public void testConstruction()
{
new Msg();
}
public void testGoodInit()
{
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Msg msg = new Msg();
msg.init(null, connection);
new Msg(null, connection);
}
public void testBadInit()
@ -38,10 +32,9 @@ public void testBadInit()
ProtocolProviderServiceIrcImpl provider = EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
EasyMock.replay(provider);
Msg msg = new Msg();
try
{
msg.init(provider, null);
new Msg(provider, null);
Assert.fail("Should not reach this, expected IAE.");
}
catch (IllegalArgumentException e)
@ -55,8 +48,7 @@ public void testMessageNoNickNoMsg()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Msg msg = new Msg();
msg.init(provider, connection);
Msg msg = new Msg(provider, connection);
msg.execute("#test", "/msg");
}
@ -66,8 +58,7 @@ public void testMessageZeroLengthMsg()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Msg msg = new Msg();
msg.init(provider, connection);
Msg msg = new Msg(provider, connection);
try
{
msg.execute("#test", "/msg ");
@ -84,8 +75,7 @@ public void testMessageZeroLengthNickMsg()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Msg msg = new Msg();
msg.init(provider, connection);
Msg msg = new Msg(provider, connection);
try
{
msg.execute("#test", "/msg ");
@ -102,8 +92,7 @@ public void testMessageNickZeroLengthMsg()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Msg msg = new Msg();
msg.init(provider, connection);
Msg msg = new Msg(provider, connection);
try
{
msg.execute("#test", "/msg target ");
@ -124,8 +113,7 @@ public void testPrivateMessage()
EasyMock.expectLastCall();
EasyMock.replay(provider, connection);
Msg msg = new Msg();
msg.init(provider, connection);
Msg msg = new Msg(provider, connection);
msg.execute("#test", "/msg target This is my target message.");
}
}

@ -23,18 +23,12 @@ public NickTest(String testName)
super(testName);
}
public void testConstruction()
{
new Nick();
}
public void testNullProviderInit()
{
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(connection);
Nick nick = new Nick();
nick.init(null, connection);
new Nick(null, connection);
}
public void testNullConnectionInit()
@ -43,10 +37,9 @@ public void testNullConnectionInit()
EasyMock.createMock(ProtocolProviderServiceIrcImpl.class);
EasyMock.replay(provider);
Nick nick = new Nick();
try
{
nick.init(provider, null);
new Nick(provider, null);
Assert.fail("Should not reach this as we expected an IAE for null"
+ " connection.");
}
@ -63,8 +56,7 @@ public void testEmptyNickCommand()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Nick nick = new Nick();
nick.init(provider, connection);
Nick nick = new Nick(provider, connection);
nick.execute("#test", "/nick");
}
@ -75,8 +67,7 @@ public void testEmptyNickCommandWithSpace()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Nick nick = new Nick();
nick.init(provider, connection);
Nick nick = new Nick(provider, connection);
nick.execute("#test", "/nick ");
}
@ -87,8 +78,7 @@ public void testEmptyNickCommandWithDoubleSpace()
IrcConnection connection = EasyMock.createMock(IrcConnection.class);
EasyMock.replay(provider, connection);
Nick nick = new Nick();
nick.init(provider, connection);
Nick nick = new Nick(provider, connection);
nick.execute("#test", "/nick ");
}
@ -104,8 +94,7 @@ public void testNickCommandWithNickAndSpace()
EasyMock.expectLastCall();
EasyMock.replay(provider, connection, idmgr);
Nick nick = new Nick();
nick.init(provider, connection);
Nick nick = new Nick(provider, connection);
nick.execute("#test", "/nick myNewN1ck ");
}
}

Loading…
Cancel
Save