All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.swak.license.api.io.Socket Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2017 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.swak.license.api.io;


import com.swak.license.api.io.function.XConsumer;
import com.swak.license.api.io.function.XFunction;
import com.swak.license.api.io.function.XSupplier;

import java.util.Objects;

/**
 * A socket is a reusable object for safe and simple automatic resource management.
 * It loans {@linkplain AutoCloseable auto-closeable resources} of type {@code } to
 * {@linkplain XConsumer consumers} or {@linkplain XFunction functions} and ensures that the resource gets
 * automatically {@linkplain AutoCloseable#close() closed} when the consumer or function terminates.
 * It can also transform resources by applying a {@linkplain XFunction function} while ensuring that the resource gets
 * closed if the function fails with an exception.
 * 

* The canonical way to create a socket is to use a lambda expression. * The following example creates a socket which provides write access for appending bytes to the file {@code test.txt}: *

{@code
 * File file = new File("test.txt");
 * Socket foss = () -> new FileOutputStream(file, true);
 * }
* The canonical way to use a socket is to provide a lambda expression for a consumer or function to its * {@link #accept(XConsumer)} or {@link #apply(XFunction)} methods. * The following example uses the preceding socket to append {@code "Hello world!"} to the file {@code test.txt}: *
{@code
 * foss.accept(fos -> new PrintStream(fos).println("Hello world!"));
 * }
* A socket can also get transformed in a fail-safe way by calling its {@link #map(XFunction)} or * {@link #flatMap(XFunction)} methods. * The following example first filters the preceding file output stream socket into a print stream socket and then * appends {@code "Hello world!"} to the file {@code test.txt} again: *
{@code
 * Socket pss = foss.map(PrintStream::new);
 * pss.accept(ps -> ps.println("Hello world!"));
 * }
* The preceding example can be simplified as follows: *
{@code
 * foss.map(PrintStream::new).accept(ps -> ps.println("Hello world!"));
 * }
* Because sockets are reusable {@code foss} and {@code pss} can be saved for subsequent use, including filtering: * On any use, a new {@code FileOutputStream} and a new {@code PrintStream} gets created. * The file output stream gets automatically closed in all examples, preventing the application from leaking file * descriptors. * Only the last two examples will close the print stream however. * With a print stream this is not a problem, but more complex decorators may buffer data or write some additional bytes * when closing, making it mandatory to close them, too. * Because of this, transforming a socket is generally preferable over decorating the given resource in a consumer or * function. *

* The following example safely filters a file output stream socket for writing {@code "Hello world!"} to the * compressed text file {@code "test.txt.gz"}. * It then safely filters a file input stream socket for reading the message back and printing it to standard output: *

{@code
 * File file = new File("test.txt.gz");
 *
 * Socket foss = () -> new FileOutputStream(file);
 * foss    .map(GZIPOutputStream::new)
 *         .map(PrintStream::new)
 *         .accept(ps -> ps.println("Hello world!"));
 *
 * Socket fiss = () -> new FileInputStream(file);
 * fiss    .map(GZIPInputStream::new)
 *         .map(InputStreamReader::new)
 *         .map(BufferedReader::new)
 *         .accept(br -> System.out.println(br.readLine()));
 * }
* Should any filter fail, e.g. because the file system is full or the file's content is not in GZIP format, * then the sockets will properly close the previously created output or input stream and no resources will be leaked. * * @param the type of the auto-closeable resource. * @author Christian Schlichtherle * @see Filter */ @SuppressWarnings({"DeprecatedIsStillUsed", "deprecation"}) @FunctionalInterface public interface Socket extends XSupplier { /** * Loans a resource to the given consumer. * The resource is obtained from a call to {@link #get()} and will be closed upon return from this method. */ default void accept(final XConsumer consumer) throws Exception { try (T resource = get()) { consumer.accept(resource); } } /** * Loans a resource to the given function and returns its value. * The resource is obtained from a call to {@link #get()} and will be closed upon return from this method. *

* It is an error to return the loaned resource from the given function or any other object which holds on to it. * Use the {@link #map(XFunction)} or {@link #flatMap(XFunction)} methods instead if you need to transform the * resource. */ default U apply(final XFunction function) throws Exception { try (T resource = get()) { return function.apply(resource); } } /** * Returns a socket which applies the given function to the resources loaned by this socket. * If the given function fails then the resource gets closed before this method terminates, which makes the * filter fail-safe. */ default Socket map(final XFunction function) { Objects.requireNonNull(function); return () -> { final T resource = get(); try { return function.apply(resource); } catch (final Throwable t1) { try { resource.close(); } catch (Throwable t2) { t1.addSuppressed(t2); } throw t1; } }; } /** * Returns a socket which applies the given function to the resources loaned by this socket and gets its result. * * @see #map(XFunction) */ default Socket flatMap(XFunction> function) { return map(function.andThen(Socket::get)); } }