7
\$\begingroup\$

I'm having trouble finding a clean way to handle checked exceptions thrown by a lambda function passed as a parameter in Java.

I'm writing a Client which will interact via RMI with a Server to play a game; a Client needs to be registered on the Server to be able to login, and it needs to be logged in to play a game. It's a college project, so I have some constraint on what technologies I can use and what I need to do. In particular, when a method raises a RemoteException I've been asked to retry to call the same method a few times. I'm using Java 8 and I wanted to experiment a little bit with lambdas.

The class I'm focusing on right now will be the interface the client will use to communicate with the server; as of now I have the following structure.

Note: most of the methods I need to implement are still to be written; I'm using a custom exception to remember to implement them.

ServerRMI.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ServerRMI extends Remote {

    public static final String name = "hangman_server_rmi_name";

    public int login(String nick, String password)
        throws WrongPasswordException,
               RemoteException;

    public void logout(String nick, int cookie)
        throws UserNotLoggedException,
               RemoteException;

    public void register(String nick, String password)
        throws UserAlreadyRegisteredException,
               RemoteException;
}

ServerInterface.java

import java.net.InetAddress;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ServerInterface { 

    private String nickname;
    private InetAddress address;
    private int port;
    private ServerRMI serverRMI;

    public ServerInterface(String nickname, InetAddress address, int port) 
            throws RemoteException, NotBoundException{
        this.nickname = nickname;
        this.address = address;
        this.port = port;
        this.serverRMI = getServerRMI(address, port);
    }

    private interface ConnectionAction<R,E extends Throwable> {
        public R action() throws E;
    }

    private static <E extends Throwable,R> R tryManyTimes(int times, ConnectionAction<R,E> action)
        throws E {
        if (times <= 0) {
            throw new IllegalArgumentException("Expected times > 0");
        }
        E lastException = null;
        boolean done = false;
        while (times > 0 && !done) {
            try {
                return action.action();
            } catch (Exception e) {
                times--;
                lastException = (E) e;
            }
        }
        throw lastException; 
    }

    private static ServerRMI getServerRMI(InetAddress parAddress, int parPort) 
        throws RemoteException, NotBoundException {
        final InetAddress address = parAddress;
        final int port = parPort;
        try {
            final Registry registry = (Registry) tryManyTimes(3, 
                                                              () -> //\
                                            LocateRegistry.getRegistry(address.getHostAddress(), port));
            return (ServerRMI) tryManyTimes(3, () -> registry.lookup(ServerRMI.name));
        } catch (Exception e) {
            if (e instanceof RemoteException) { 
                throw (RemoteException) e;
            } else if (e instanceof NotBoundException) {
                throw (NotBoundException) e;
            } else {
                throw new RuntimeException("Unexpected exception");
            }
        }
    }

    public void register(String password)
        throws UserAlreadyRegisteredException, 
               ConnectionFailureException {
        throw new NotImplementedException();
    }

    public void login(String password)
        throws WrongPasswordException,
               ConnectionFailureException {
        throw new NotImplementedException();
    }

    public void logout()
            throws IllegalStateException,
                   ConnectionFailureException {
        throw new NotImplementedException();
    }

    public Game openGame(int players)
        throws IllegalStateException,
               GameAlreadyExistsException,
               ConnectionFailureException {
        throw new NotImplementedException();
    }

    public Game joinGame(String name)
        throws IllegalStateException,
               GameNotFoundException,
               ConnectionFailureException {
        throw new NotImplementedException();

    }

Game.java

public class Game {
}

I'm not including the files with the custom exceptions I've defined; every custom exception, right now, extends RuntimeException.

Is there some way to organize the code that lets me avoid the instanceof uses in the body of getServerRMI? Ideally, I would like to do something like:

catch (RemoteException | NotBoundException e) {
    throw e;
}

Any suggestion on any other part of the code will be appreciated.

\$\endgroup\$
  • 2
    \$\begingroup\$ > ideally, I'd like to be able to write something like this - So, why don't you? You can, just add a } catch (Exception e) {....} as well. \$\endgroup\$ – rolfl Jun 18 '15 at 14:51
  • \$\begingroup\$ I didn't think about adding the final catch (Exception e) block; it works perfectly. If anyone wants to write an answer I'll accept it, otherwise I'll write one later. \$\endgroup\$ – gcali Jun 18 '15 at 15:06
  • \$\begingroup\$ as already mentioned in the 2nd monitor... there is a library for this \$\endgroup\$ – Vogel612 Jun 18 '15 at 15:36
3
\$\begingroup\$

Firstly, a couple of comments:

ServerInterface is a bad name for something that isn't an interface.

Why do you have //\ before your lambda's body? Java is perfectly content with having it on a new line. As far as I can tell, this is useless.

Now, with regards to your actual question: Yes, you can do exactly that. It's literally that syntax. You can also put multiple catch blocks, so instead of that instanceof mess, you can have this:

catch (RemoteException | NotBoundException e) {
    throw e;
} catch (Exception e) {
    throw new RuntimeException("Unexpected exception");
}

It checks each in order, and subclasses are caught with any statement that matches their superclass.

Now, there are a couple of issues with your approach:

  1. You're throwing a plain RuntimeException. This is typically a bad idea, because if you throw a more specific one, people can catch situations where their own exceptional case occurs.
  2. You don't give any information about what caused the exception, neither in the message nor through the exception itself.

You can fix both problems by either:

  1. Creating your own custom exception class that carries the information, or
  2. Use the RuntimeException(String, Throwable) constructor, and use a more informative message. That call would look something like:

    throw new RuntimeException("Unexpected exception: " + e.getMessage(), e);
    

Aside from that, I can't see that much wrong with your code. Once I get a chance I'll run it through Eclipse and see if it turns up anything -- if I haven't in 24 hours or so, please reply here and remind me.

\$\endgroup\$
  • \$\begingroup\$ I've been having a hard time coming up with a different name for ServerInterface; it is, after all, what I use to interface with the server. Maybe something on the line of ServerCommunicator might be an alternative. Nice suggestion about the RuntimeException, I didn't think of fixing it this way! Thanks for the tips \$\endgroup\$ – gcali Jul 1 '15 at 7:53
  • \$\begingroup\$ @Odexios Hmm, good point -- there is the alternate definition that has nothing to do with programming. It's not a bad name, it just might be confusion, since "interface" has a specific meaning in Java. ServerConnection or ServerCommunicator could be a better name, but in the end, it's not that bad, and could probably be left alone. \$\endgroup\$ – Nic Hartley Jul 1 '15 at 7:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.