2

I have a need to open N multicast sockets (where N comes from the size of an argument list). I will then send the same data to each of the N sockets within a loop, and finally, close each socket. My question is, how do I do this using the try-with-resources block? The following is how I would do this with a single resource:

final int port = ...;
try (final MulticastSocket socket = new MulticastSocket(port)) {
    // Do a bunch of sends of small packet data over a long period of time
    ...
}

The only way I can think of to do this with multiple ports is the following:

final List<Integer> ports = ...;
final List<MulticastSocket> sockets = new ArrayList<>(ports.size());
try {
    for (final Integer port : ports) {
        sockets.add(new MulticastSocket(port));
    }

    // Do a bunch of sends of small packet data over a long period of time
    ...
} finally {
    for (final MulticastSocket socket : sockets) {
        try {
            socket.close();
        } catch (final Throwable t) {
            // Eat the exception
        }
    }
}

Is there a more concise way to accomplish this, or is my proposed solution as good as it gets?

shmosel
  • 49,289
  • 6
  • 73
  • 138
Jeff G
  • 4,470
  • 2
  • 41
  • 76

4 Answers4

2

Do it recursively to keep the guarantees of try-with-resources:

void foo(List<Integer> ports, List<Socket> sockets) {
  if (sockets.size() == ports.size()) {
    // Do something with your sockets.
  } else {
    try (Socket s = new MulticastSocket(ports.get(sockets.size())) {
      sockets.add(s);
      foo(ports, sockets);
      // You could call sockets.remove(sockets.size()-1) here.
      // Not convinced whether it's worth it.
    }
  }
}
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • so you can't loop but you can create a recursive invocation? this is really nice – Eugene Jun 15 '17 at 19:52
  • I had to read this twice to understand what you were doing. +1 for creativity and brevity, but I think I still prefer Mike's suggestion of creating a utility `AutoCloseable` class instead. It seems to be more readable, and abusing recursion for this just feels... dirty. – Jeff G Jun 16 '17 at 12:43
  • @JeffG you should do what you feel comfortable with, because ultimately you have to maintain it; but I think you'll find it quite messy and difficult to implement exactly equivalent semantics of multiple TWR blocks without something like this. – Andy Turner Jun 16 '17 at 13:55
1

What you are doing is practically as good as it gets.

You could create an AutoCloseable general-purpose multi-closer which contains a List<AutoCloseable> and accepts as a constructor parameter a count of closeables and a factory to invoke to create each closeable, and then close them all when its close() is invoked, so that you can use it like this:

try( MultiCloser<MulticastSocket> multiCloser = 
         new MultiCloser<>( ports.size(), i -> new MulticastSocket( ports.get( i ) ) )
{
    for( MulticastSocket socket : multiCloser.getItems() )
    {
        do something with the socket
    }
}

...but it would probably be an overkill.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • What is `port` in the lambda expression? –  Jun 15 '17 at 20:46
  • @saka1029 argh, there, fixed it. But that's not the point. The point is the construct. The details are left as an exercise to the student. – Mike Nakis Jun 15 '17 at 20:49
0

What is the point to use an ArrayList to store the MulticastSocket instances ?

You said that :

I will then send the same data to each of the N sockets within a loop, and finally, close each socket.

So you can create them in a loop and send for each iteration the same processing.
To do it, you should a little change your design.
The processing task of the MulticastSocket could be performed by a functional interface that allows also to specify the port to use.

For example :

@FunctionalInterface
public interface SocketProcessor {
    void process(MulticastSocket multicastSocket) ;
}

You could have a method that takes as parameter this functional interface to apply the processing :

public static void processSocket(SocketProcessor socketProcessor, Integer port) throws IOException {
  try (final MulticastSocket socket = new MulticastSocket(port)) {
    socketProcessor.process(socket);
  }
}

At last from the client code, you could create a socketProcessor instance with a lambda :

SocketProcessor socketProcessor = (MulticastSocket socket) -> {
    socket.send(...);
    socket.send(...);
};

And then you could loop on the ports in order to invoke processSocket with the suitable port and the SocketProcessor instance just created :

for (final Integer port : ports) {
    try {
      processSocket(socketProcessor, port);
    } catch (IOException e) {
      // do processing
    }
}

This solution is not necessary shorter (without being really longer) but it is really clearer.
The two main concerns are separated :

  • processSocket(SocketProcessor) that performs the boiler plate code

  • SocketProcessor that defines the concrete task.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Your answer made me realize that I forgot to specify that the packets need to be sent interleaved (e.g., send packet 1 on all sockets, then send packet 2 on all sockets, ...). Given that is one of my requirements (which is admittedly absent in my question), this answer would require creating a new socket for each packet that needs to be sent. – Jeff G Jun 16 '17 at 12:33
  • @Jeff G In this case, indeed the proposed answer doesn't suit to your need. – davidxxx Jun 16 '17 at 13:09
0

Inspired by the idea proposed by Mike Nakis, I came up with the following class...

package myNamespace;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import myNamespace.ThrowingFunction;
import myNamespace.ThrowingSupplier;

/** Collection of AutoCloseable objects */
public class ResourceCollection<T extends AutoCloseable>
        implements Iterable<T>, AutoCloseable {

    /** Resources owned by this instance */
    private final List<T> myResources;

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param count     Number of times to call the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <E extends Throwable> ResourceCollection(
            final ThrowingSupplier<T, E> allocator, final int count)
            throws E {
        myResources = new ArrayList<>(count);
        try {
            while (myResources.size() < count) {
                final T resource = allocator.getThrows();
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param input     List of input parameters passed to the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <U, E extends Throwable> ResourceCollection(
            final ThrowingFunction<U, T, E> allocator, final Collection<U> input)
            throws E {
        myResources = new ArrayList<>(input.size());
        try {
            for (final U value : input) {
                final T resource = allocator.applyThrows(value);
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Gets the number of resources in the collection
     * @return The number of resources in the collection
     */
    public int size() {
        return myResources.size();
    }

    /**
     * Gets whether the collection contains no resources
     * @return Whether the collection contains no resources
     */
    public boolean isEmpty() {
        return myResources.isEmpty();
    }

    /**
     * Gets the resource at index i
     * @param i The index of a resource, in the range [0, size())
     * @return The resource at index i
     */
    public T get(final int i) {
        return myResources.get(i);
    }

    @Override
    public Iterator<T> iterator() {
        return myResources.iterator();
    }

    @Override
    public void close() {
        final ListIterator<T> resourceIter =
                myResources.listIterator(myResources.size());
        while (resourceIter.hasPrevious()) {
            final T resource = resourceIter.previous();
            if (resource != null) {
                try {
                    resource    .close ();
                    resourceIter.remove();
                } catch (final Throwable t) {
                    // Eat the exception
                }
            }
        }
    }

}
Jeff G
  • 4,470
  • 2
  • 41
  • 76