package games.strategy.util; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Optional; import java.util.function.Function; import com.google.common.base.Throwables; /** * Utility class for opening input streams from URL and URI objects. */ public final class UrlStreams { /** * Opens an input stream to a given url. Returns Optional.empty() in case there is a failure. * The failure message is logged to the user. * * @return Optional.empty() if there was a failure opening the strema, otherwise an optional * containing an input stream to the parameter uri. */ public static Optional<InputStream> openStream(final URL url) { return new UrlStreams().newStream(url); } /** * Opens an input stream to a given uri. * * @return Optional.empty() if there was a failure opening the strema, otherwise an optional * containing an input stream to the parameter uri. * * @throws IllegalStateException if the given uri is malformed */ public static Optional<InputStream> openStream(final URI uri) { try { return UrlStreams.openStream(uri.toURL()); } catch (final MalformedURLException e) { throw new IllegalStateException("Bad uri specified: " + uri, e); } } /** Used to obtain a connection from a given URL. */ private final Function<URL, URLConnection> urlConnectionFactory; protected UrlStreams() { // By default just try open a connection, raise any exceptions encountered this.urlConnectionFactory = (url) -> { try { return url.openConnection(); } catch (final IOException e) { throw Throwables.propagate(e); } }; } /** * For test, a constructor that allows mock object injection. */ protected UrlStreams(final Function<URL, URLConnection> connectionFactory) { this.urlConnectionFactory = connectionFactory; } protected Optional<InputStream> newStream(final URL url) { try { final URLConnection connection = urlConnectionFactory.apply(url); // Turn off URL connection caching to avoid open file leaks. When caching is on, the InputStream // returned is left open, even after you call 'InputStream.close()' connection.setDefaultUseCaches(false); // TODO: verify - setDefaultUseCaches(false) may not be necessary connection.setUseCaches(false); return Optional.of(connection.getInputStream()); } catch (final IOException e) { return Optional.empty(); } } }