package at.kai.logic;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
/**
* Object to communicate with server.
* @author Kai
*/
public class Command implements CommandConstants
{
/**
* The pattern to split a string on semicolon.
*/
private static final Pattern PATTERN = Pattern.compile(";");
/**
* Contain all commands the read from inputstream and until they are popped from the queue.
*/
private static final LinkedBlockingQueue<Command> NEXT_COMMANDS_QUEUE = new LinkedBlockingQueue<Command>();
/**
* The thread, they read from InputStream and push it on the queue.
*/
private static IOOperationsThread operationsThread;
/**
* Lock for the parameter list.
*/
private final Lock PARAMETER_LIST_LOCK = new ReentrantLock();
/**
* The string to set the command to object. e. g. "Update something", "Send me something" et cetera.
*/
private Order order;
/**
* A list of all parameters in command.
*/
private List<String> parameterList;
/**
* Construct a new command with the specific command and location.
* @param command The command of this object.
* @param location The location of this object.
* @throws IllegalArgumentException If command or location is null.
*/
public Command(Order order) throws IllegalArgumentException
{
super();
if(order == null)
throw new IllegalArgumentException();
this.order = order;
}
/**
* Construct a new command from a string.
* @param readedData The string, as a rule receive about socket
*/
private Command(String readedData)
{
super();
String[] split = PATTERN.split(readedData, 0);
order = new Order(split[0]);
if(split.length > 1)
{
parameterList = new ArrayList<String>(split.length - 2);
for(int i = 1; i < split.length; i++)
parameterList.add(split[i].replace(":", ";"));
}
}
/**
* Add a new parameter to command. If the parameter
* is either null or empty, then he will add as " ".
* ':' is a registred keyword. If the parameter containing ':',
* a IllegalArgumentException is throwing.
* @param parameter The parameter to add.
*/
public void addParameter(String parameter)
{
if(parameter.contains(":"))
throw new IllegalArgumentException("Illegal character: ':'");
parameter = parameter.replaceAll(";", ":");
parameter = (parameter == null || parameter.equals("") ? " " : parameter);
PARAMETER_LIST_LOCK.lock();
try
{
parameterList = (parameterList == null ? new ArrayList<String>() : parameterList);
parameterList.add(parameter);
}
finally
{
PARAMETER_LIST_LOCK.unlock();
}
}
/**
* Add a new boolean as parameter to command.
* @param parameter The parameter to add.
* @see #addParameter(String)
*/
public void addParameter(boolean parameter)
{
addParameter(parameter ? "true" : "false");
}
/**
* Add a new int as parameter to command.
* @param parameter The parameter to add.
* @see #addParameter(String)
*/
public void addParameter(char parameter)
{
addParameter(String.valueOf((int)parameter));
}
/**
* Add a new int as parameter to command.
* @param parameter The parameter to add.
* @see #addParameter(String)
*/
public void addParameter(int parameter)
{
addParameter(String.valueOf(parameter));
}
/**
* Add a new long parameter to command.
* @param parameter The parameter to add.
* @see #addParameter(String)
*/
public void addParameter(long parameter)
{
addParameter(String.valueOf(parameter));
}
/**
* Send the command as a string about socket. Start() must be call befor.
*/
public void send()
{
if(operationsThread == null)
throw new RuntimeException("Not started yet.");
operationsThread.send(toString());
}
/**
* Push this command back on end of queue.
*/
public void pushBack()
{
try
{
NEXT_COMMANDS_QUEUE.put(this);
}
catch (InterruptedException e) {}
}
/**
* Push this command back on end of queue and wait the specific time.<br>
* (For braking thread, if only pushing commands are in queue.)
*/
public void pushBack(TimeUnit timeUnit, long timeFactor)
{
pushBack();
try
{
if(timeUnit != null && timeFactor > 0)
timeUnit.sleep(timeFactor);
}
catch (InterruptedException e) {}
}
/**
* See getParameter(int).
* @param i The field to return.
* @return The parameter as byte.
*/
public byte getParameterAsByte(int i)
{
return(Byte.parseByte(getParameter(i)));
}
/**
* See getParameter(int).
* @param i The field to return.
* @return The parameter as double.
*/
public double getParameterAsDouble(int i)
{
return(Double.parseDouble(getParameter(i)));
}
/**
* @see #hasOrder(Order)
*/
public boolean hasCommand(Order o)
{
return(hasOrder(o));
}
/**
* Check, wheter the parameter is equal the command of this object.
* @param s The string to check.
* @return true if the parameter is equal the command, false otherwise.
*/
public boolean hasOrder(Order o)
{
return(order.toString().equals(o.toString()));
}
/**
*
*/
public boolean getParameterAsBoolean(int i)
{
return(getParameter(i).equals("true"));
}
/**
* Return the count of parameters.
* @return The count of parameters.
*/
public int getParameterSize()
{
int size = 0;
PARAMETER_LIST_LOCK.lock();
try
{
size = (parameterList != null ? parameterList.size() : 0);
}
finally
{
PARAMETER_LIST_LOCK.unlock();
}
return(size);
}
/**
* @See {@link #getParameter(int)}
*/
public int getParameterAsInt(int i)
{
return(Integer.parseInt(getParameter(i)));
}
public long getParameterAsLong(int i)
{
return(Long.parseLong(getParameter(i)));
}
/**
* Return the command of this object.
* @return The command.
*/
public Order getCommand()
{
return(order);
}
/**
* Make this command to a single string to send about socket, debug, et cetra.
* @return The command as string.
*/
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(order);
builder.append(";");
if(parameterList != null)
for(int i = 0; i < parameterList.size(); i++)
{
builder.append(parameterList.get(i));
builder.append(";");
}
builder.append("$");
return(builder.toString());
}
/**
* Return the parameter from command on the specific field.
* @param field The field whence the parameter is get.
* @return The parameter from the field, or null, if no parameters exists<br>
* or the field is out of range.
*/
public String getParameter(int field)
{
String parameter = null;
PARAMETER_LIST_LOCK.lock();
try
{
if(parameterList != null && field >= 0 && field <= (parameterList.size() - 1))
parameter = parameterList.get(field);
}
finally
{
PARAMETER_LIST_LOCK.unlock();
}
return(parameter);
}
/**
* Start the thread.<br>
* Start the thread, they read from the socket. Needfull for Full-Duplez communicate.<br>
* @param socket The socket to read from.
*/
public synchronized static void start(Socket socket)
{
operationsThread = new IOOperationsThread(socket);
operationsThread.setName("IO Operations Thread");
operationsThread.start();
}
/**
* Start the thread.<br>
* Start the thread, they read from the socket. Needfull for Full-Duplez communicate.<br>
* @param socket The socket to read from.
*/
public synchronized static void start(String ip, int port) throws UnknownHostException, IOException
{
if(ip == null)
throw new NullPointerException("IP cannot be 'null'");
else if(port < 1 || port > 65535)
throw new IllegalArgumentException("Port out of range.");
operationsThread = new IOOperationsThread(new Socket(ip, port));
operationsThread.setName("IO Operations Thread");
operationsThread.start();
}
/**
* Check, wheter the thread to read from InputStream is started now.
* @return true, if the thread to read from InputStream is started now, false otherwise.
*/
public static boolean isStarted()
{
return(operationsThread != null);
}
/**
* Read the first Command from queue. Is the Queue is empty, this method will block the current Thread.
* @return The new command.
*/
public static Command read()
{
Command cmd = null;
do
{
try
{
cmd = NEXT_COMMANDS_QUEUE.take();
}
catch (NoSuchElementException e) {}
catch (InterruptedException e) {}
if(cmd == null)
{
try
{
TimeUnit.MILLISECONDS.sleep(10L);
}
catch (InterruptedException e) {}
}
}
while(cmd == null);
return(cmd);
}
/**
* Read the first Command from queue. Is the Queue is empty, this method will block the current Thread.
* If interrupt() from the current thread is called, this method will exit.
* @return The new command or null, if the current thread was interrupted.
*/
public static Command readInterruptable()
{
Command cmd = null;
try
{
do
{
cmd = NEXT_COMMANDS_QUEUE.take();
if(cmd == null)
{
TimeUnit.MILLISECONDS.sleep(10L);
}
}
while(cmd == null);
}
catch (InterruptedException e) {}
return(cmd);
}
public static Command read(Order... order)
{
return(read(null, 0L, order));
}
public static Command read(TimeUnit timeUnit, long timeFactor, Order... order)
{
Command cmd = null;
try
{
cmd = read(timeUnit, timeFactor, false, order);
}
catch (InterruptedException e) {}
return(cmd);
}
public static Command read(TimeUnit timeUnit, long timeFactor, boolean interruptable, Order... order)
throws InterruptedException
{
Command cmd = null;
boolean found;
do
{
boolean interrupted = Thread.interrupted();
if(interruptable && interrupted)
throw new InterruptedException();
cmd = read();
String commandText = cmd.getCommand().toString();
found = false;
for(Order o:order)
if(commandText.equals(o.toString()))
{
found = true;
break;
}
if(!found)
cmd.pushBack(timeUnit, timeFactor);
}
while(!found);
return(cmd);
}
/**
* Close the socket and terminate the thread, they read from them. All previously readet data are still
* available.
*/
public static synchronized void stop()
{
if(operationsThread != null)
{
operationsThread.terminate();
operationsThread = null;
}
}
/** */
private static class IOOperationsThread extends Thread
{
/**
* Object to lock the sendprogress.
*/
private static final Lock SEND_OBJECT_LOCK = new ReentrantLock();
/**
* Socket to read from or send to.
*/
private Socket socket;
/**
* The writer for output communicate.
*/
private PrintWriter writer;
/**
* Construct a new instance of thread.
* @param socket to communicate.
*/
private IOOperationsThread(Socket socket)
{
super();
this.socket = socket;
}
public void run()
{
int incoming = 0;
StringBuilder builder = new StringBuilder();
BufferedReader reader = null;
try
{
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
catch (IOException e1)
{
e1.printStackTrace();
}
do
{
builder.setLength(0);
try
{
//TODO Build in buffer.
while((incoming = reader.read()) != -1 && incoming != '$')
builder.append((char)incoming);
}
catch (IOException e)
{
Command.stop();
}
Command command = new Command(builder.toString());
try
{
NEXT_COMMANDS_QUEUE.put(command);
}
catch (InterruptedException e) {}
}
while(!socket.isClosed());
}
/**
* Send the string about socket.
* @param s String to send about socket.
*/
public void send(String s)
{
SEND_OBJECT_LOCK.lock();
try
{
writer = (writer == null ? new PrintWriter(socket.getOutputStream()) : writer);
writer.write(s);
writer.flush();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
SEND_OBJECT_LOCK.unlock();
}
}
/**
* Close the socket and terminate the thread.
*/
public void terminate()
{
try
{
socket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public char getParameterAsChar(int i)
{
return((char)getParameterAsInt(i));
}
}