jDoc, tests, Server fixes
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / ConnectAction.java
index 2a25c5b1c62e9f6a15778e200b04603637fa747a..dfc1a2bb422138ea8acbf930a831d0c6d7a7a0da 100644 (file)
@@ -8,20 +8,71 @@ import java.net.Socket;
 
 import be.nikiroo.utils.Version;
 
+/**
+ * Base class used for the client/server basic handling.
+ * <p>
+ * It represents a single action: a client is expected to only execute one
+ * action, while a server is expected to execute one action for each client
+ * action.
+ * 
+ * @author niki
+ */
 abstract class ConnectAction {
        private Socket s;
        private boolean server;
        private Version version;
+       private Version clientVersion;
 
        private Object lock = new Object();
        private BufferedReader in;
        private OutputStreamWriter out;
        private boolean contentToSend;
 
-       // serverVersion = null on server (or bad clients)
-       abstract public void action(Version serverVersion) throws Exception;
+       /**
+        * Method that will be called when an action is performed on either the
+        * client or server this {@link ConnectAction} represent.
+        * 
+        * @param version
+        *            the counter part version
+        * 
+        * @throws Exception
+        *             in case of I/O error
+        */
+       abstract protected void action(Version version) throws Exception;
+
+       /**
+        * Method called when we negotiate the version with the client.
+        * <p>
+        * Thus, it is only called on the server.
+        * <p>
+        * Will return the actual server version by default.
+        * 
+        * @param clientVersion
+        *            the client version
+        * 
+        * @return the version to send to the client
+        */
+       abstract protected Version negotiateVersion(Version clientVersion);
+
+       /**
+        * Handler called when an unexpected error occurs in the code.
+        * 
+        * @param e
+        *            the exception that occurred
+        */
+       abstract protected void onError(Exception e);
 
-       // server = version NULL
+       /**
+        * Create a new {@link ConnectAction}.
+        * 
+        * @param s
+        *            the socket to bind to
+        * @param server
+        *            TRUE for a server action, FALSE for a client action (will
+        *            impact the process)
+        * @param version
+        *            the version of this client-or-server
+        */
        protected ConnectAction(Socket s, boolean server, Version version) {
                this.s = s;
                this.server = server;
@@ -31,18 +82,22 @@ abstract class ConnectAction {
                } else {
                        this.version = version;
                }
+
+               clientVersion = new Version();
        }
 
-       public void connectAsync() {
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               connect();
-                       }
-               }).start();
+       /**
+        * The version of this client-or-server.
+        * 
+        * @return the version
+        */
+       public Version getVersion() {
+               return version;
        }
 
-       // connect, do the action (sync)
+       /**
+        * Actually start the process (this is synchronous).
+        */
        public void connect() {
                try {
                        in = new BufferedReader(new InputStreamReader(s.getInputStream(),
@@ -51,7 +106,7 @@ abstract class ConnectAction {
                                out = new OutputStreamWriter(s.getOutputStream(), "UTF-8");
                                try {
                                        if (server) {
-                                               action(version);
+                                               action(clientVersion);
                                        } else {
                                                String v = sendString("VERSION " + version.toString());
                                                if (v != null && v.startsWith("VERSION ")) {
@@ -77,9 +132,30 @@ abstract class ConnectAction {
                }
        }
 
-       // (also, server never get anything)
-       public Object send(Object data) throws IOException, NoSuchFieldException,
-                       NoSuchMethodException, ClassNotFoundException {
+       /**
+        * Serialise and send the given object to the counter part (and, only for
+        * client, return the deserialised answer -- the server will always receive
+        * NULL).
+        * 
+        * @param data
+        *            the data to send
+        * 
+        * @return the answer (which can be NULL) if this action is a client, always
+        *         NULL if it is a server
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        * @throws NoSuchFieldException
+        *             if the serialised data contains information about a field
+        *             which does actually not exist in the class we know of
+        * @throws NoSuchMethodException
+        *             if a class described in the serialised data cannot be created
+        *             because it is not compatible with this code
+        * @throws ClassNotFoundException
+        *             if a class described in the serialised data cannot be found
+        */
+       protected Object send(Object data) throws IOException,
+                       NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
                synchronized (lock) {
                        String rep = sendString(new Exporter().append(data).toString(true));
                        if (rep != null) {
@@ -90,37 +166,55 @@ abstract class ConnectAction {
                }
        }
 
-       public Object flush() throws NoSuchFieldException, NoSuchMethodException,
-                       ClassNotFoundException, IOException, java.lang.NullPointerException {
+       /**
+        * Reserved for the server: flush the data to the client and retrieve its
+        * answer.
+        * <p>
+        * Also used internally for the client (only do something if there is
+        * contentToSend).
+        * <p>
+        * Will only flush the data if there is contentToSend.
+        * 
+        * @return the deserialised answer (which can actually be NULL)
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        * @throws NoSuchFieldException
+        *             if the serialised data contains information about a field
+        *             which does actually not exist in the class we know of
+        * @throws NoSuchMethodException
+        *             if a class described in the serialised data cannot be created
+        *             because it is not compatible with this code
+        * @throws ClassNotFoundException
+        *             if a class described in the serialised data cannot be found
+        * @throws java.lang.NullPointerException
+        *             if the counter part has no data to send
+        */
+       protected Object rec() throws IOException, NoSuchFieldException,
+                       NoSuchMethodException, ClassNotFoundException,
+                       java.lang.NullPointerException {
                String str = flushString();
                if (str == null) {
-                       throw new NullPointerException("No more data from client");
+                       throw new NullPointerException("No more data available");
                }
 
                return new Importer().read(str).getValue();
        }
 
        /**
-        * Handler called when the client {@link Version} is received.
+        * Send the given string to the counter part (and, only for client, return
+        * the answer -- the server will always receive NULL).
         * 
-        * @param clientVersion
-        *            the client {@link Version}
-        */
-       protected void onClientVersionReceived(
-                       @SuppressWarnings("unused") Version clientVersion) {
-       }
-
-       /**
-        * Handler called when an unexpected error occurs in the code.
+        * @param line
+        *            the data to send (we will add a line feed)
         * 
-        * @param e
-        *            the exception that occurred
+        * @return the answer if this action is a client (without the added line
+        *         feed), NULL if it is a server
+        * 
+        * @throws IOException
+        *             in case of I/O error
         */
-       protected void onError(@SuppressWarnings("unused") Exception e) {
-       }
-
-       // \n included in line, but not in rep (also, server never get anything)
-       private String sendString(String line) throws IOException {
+       protected String sendString(String line) throws IOException {
                synchronized (lock) {
                        out.write(line);
                        out.write("\n");
@@ -135,8 +229,21 @@ abstract class ConnectAction {
                }
        }
 
-       // server can receive something even without pending content
-       private String flushString() throws IOException {
+       /**
+        * Reserved for the server (externally): flush the data to the client and
+        * retrieve its answer.
+        * <p>
+        * Also used internally for the client (only do something if there is
+        * contentToSend).
+        * <p>
+        * Will only flush the data if there is contentToSend.
+        * 
+        * @return the answer (which can be NULL)
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       protected String flushString() throws IOException {
                synchronized (lock) {
                        if (server || contentToSend) {
                                if (contentToSend) {
@@ -149,8 +256,12 @@ abstract class ConnectAction {
                                        // "VERSION client-version" (VERSION 1.0.0)
                                        Version clientVersion = new Version(
                                                        line.substring("VERSION ".length()));
-                                       onClientVersionReceived(clientVersion);
-                                       sendString("VERSION " + version.toString());
+                                       this.clientVersion = clientVersion;
+                                       Version v = negotiateVersion(clientVersion);
+                                       if (v == null) {
+                                               v = new Version();
+                                       }
+                                       sendString("VERSION " + v.toString());
 
                                        line = in.readLine();
                                }