com.swak.license.api.io.Socket Maven / Gradle / Ivy
Show all versions of swak-license-core Show documentation
/*
* 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 super T> 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 super T, ? extends U> 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 super T, ? extends U> 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 super T, ? extends Socket extends U>> function) {
return map(function.andThen(Socket::get));
}
}