com.yelstream.topp.standard.net.Sockets Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of topp-standard-core Show documentation
Show all versions of topp-standard-core Show documentation
Topp Standard Core addressing basics of Java SE.
The newest version!
/*
* Project: Topp Standard
* GitHub: https://github.com/sabroe/Topp-Standard
*
* Copyright 2024 Morten Sabroe Mortensen
*
* 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
*
* http://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.yelstream.topp.standard.net;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
* Utility addressing instances of {@link Socket}.
*
* @author Morten Sabroe Mortensen
* @version 1.0
* @since 2024-07-02
*/
@Slf4j
@UtilityClass
@SuppressWarnings("java:S1192")
public class Sockets {
/**
* Abstract operation to be applied upon a socket.
*/
@FunctionalInterface
public interface SocketOperation {
/**
* Applies the operation upon a socket.
* @param socket Socket.
* @throws IOException Thrown in case of I/O error.
*/
void apply(Socket socket) throws IOException;
}
/**
* Connect-specific operation.
*/
@FunctionalInterface
public interface ConnectOperation extends SocketOperation {
/**
* Connects a socket.
* @param socket Socket.
* @throws IOException Thrown in case of I/O error.
*/
void connect(Socket socket) throws IOException;
@Override
default void apply(Socket socket) throws IOException {
connect(socket);
}
}
/**
* Probe for connectivity of a socket.
*/
@FunctionalInterface
public interface ConnectProbe {
/**
* Tests connectivity of a socket.
* @param operation Connect-operation.
* @throws IOException Thrown in case of I/O error.
*/
void test(ConnectOperation operation) throws IOException;
}
/**
* Tests if a socket can be connected.
*
* This is intended for probing an endpoint.
*
* @param operation Connect-operation.
* @throws IOException Thrown in case of I/O error.
* This is thrown if the socket cannot be connected.
*/
public static void testConnect(ConnectOperation operation) throws IOException {
try (Socket socket=new Socket()) {
operation.connect(socket);
}
}
/**
* Parameters to connect a socket.
*/
@Getter
@ToString
@AllArgsConstructor(staticName="of")
@lombok.Builder(builderClassName="Builder",toBuilder=true)
public static class ConnectParameter {
/**
* Endpoint to connect to.
*/
private final SocketAddress endpoint;
/**
* Connect timeout.
*/
@lombok.Builder.Default
private final Duration connectTimeout=null;
}
/**
* Utility handling instances of {@link ConnectOperation}.
*/
@UtilityClass
public static class ConnectOperations {
/**
* Creates a socket connect-operation.
* @param endpoint Endpoint to connect to.
* @param connectTimeout Connect timeout.
*/
public static ConnectOperation create(SocketAddress endpoint,
Duration connectTimeout) {
return socket -> socket.connect(endpoint,(int)connectTimeout.toMillis());
}
/**
* Creates a socket connect-operation.
* @param endpoint Endpoint to connect to.
*/
public static ConnectOperation create(SocketAddress endpoint) {
return socket -> socket.connect(endpoint);
}
/**
* Creates a socket connect-operation.
* @param parameter Connect parameters.
*/
public static ConnectOperation create(ConnectParameter parameter) {
SocketAddress endpoint=parameter.endpoint;
Duration connectTimeout=parameter.connectTimeout;
if (connectTimeout==null) {
return create(endpoint);
} else {
return create(endpoint,connectTimeout);
}
}
}
/**
* Utility handling instances of {@link ConnectProbe}.
*/
@UtilityClass
public static class ConnectProbes {
/**
* Creates a default connect-probe.
*/
public static ConnectProbe create() {
return Sockets::testConnect;
}
}
/**
* Initiates a test connect.
* @param resultSupplier Result supplier.
* @param executor Executor.
* @return Result.
* @param Type of result.
*/
private static CompletableFuture initiateTestConnect(Supplier resultSupplier,
Executor executor) {
return CompletableFuture.supplyAsync(resultSupplier,executor);
}
/**
* Decoration of a test connect.
* @param Type of result.
*/
public interface ConnectDecoration {
/**
* Performs a test connect.
* @param probe Connect probe.
* @param parameter Connect parameters.
* @return Result.
*/
R apply(ConnectProbe probe,
ConnectParameter parameter);
}
/**
* Creates a result supplier for a connect test.
* @param decoration Connect decoration.
* @param probe Connect probe.
* @param parameter Connect parameters.
* @return Created supplier.
* @param Type of result.
*/
private static Supplier createResultSupplier(ConnectDecoration decoration,
ConnectProbe probe,
ConnectParameter parameter) {
return ()->decoration.apply(probe,parameter);
}
/**
* Initiates a test connect.
* @param decoration Connect decoration.
* @param probe Connect probe.
* @param parameter Connect parameters.
* @param executor Executor.
* @param Type of result.
*/
public static CompletableFuture initiateTestConnect(ConnectDecoration decoration,
ConnectProbe probe,
ConnectParameter parameter,
Executor executor) {
Supplier resultSupplier=createResultSupplier(decoration,probe,parameter);
return initiateTestConnect(resultSupplier,executor);
}
/**
* Initiates a test connect.
* @param decoration Connect decoration.
* @param parameter Connect parameters.
* @param executor Executor.
* @param Type of result.
*/
public static CompletableFuture initiateTestConnect(ConnectDecoration decoration,
ConnectParameter parameter,
Executor executor) {
ConnectProbe probe=ConnectProbes.create();
return initiateTestConnect(decoration,probe,parameter,executor);
}
/**
* Decorates a connect probe to return an indication of success.
* @param probe Connect probe.
* @param parameter Connect parameter.
* @return Result.
* Indicates, if socket was connected.
*/
private static Boolean decorateWithBoolean(ConnectProbe probe,
ConnectParameter parameter) {
ConnectOperation operation=ConnectOperations.create(parameter);
AtomicReference connected=new AtomicReference<>(null);
try {
probe.test(socket->{
operation.connect(socket);
connected.set(Boolean.TRUE);
});
} catch (IOException ex) {
log.atTrace().setMessage("Failure to connect socket; parameters are {}!").addArgument(parameter).setCause(ex).log();
connected.set(Boolean.FALSE);
}
return connected.get();
}
/**
* Result of executing a test connect with simple, plain information.
*/
@Getter
@ToString
@lombok.Builder(builderClassName="Builder",toBuilder=true)
@AllArgsConstructor
public static class SimpleConnectResult {
/**
* Connect parameters.
*/
private final ConnectParameter connectParameter;
/**
* Indicates, if the test connect has succeeded.
*/
private final Boolean connected;
/**
* Indicates, if the test connect has failed.
* @return Indicates, if connect has failed.
*/
public boolean failure() {
return !success();
}
/**
* Indicates, if the test connect has succeeded.
* @return Indicates, if connect has succeeded.
*/
public boolean success() {
return connected==Boolean.TRUE;
}
}
/**
* Decorates a connect probe to return an indication of success.
* @param probe Connect probe.
* @param parameter Connect parameter.
* @return Result.
*/
private static SimpleConnectResult decorateWithSimpleConnectResult(ConnectProbe probe,
ConnectParameter parameter) {
ConnectOperation operation=ConnectOperations.create(parameter);
SimpleConnectResult.Builder builder=SimpleConnectResult.builder().connectParameter(parameter);
try {
probe.test(socket->{
operation.connect(socket);
builder.connected(Boolean.TRUE);
});
} catch (IOException ex) {
log.atTrace().setMessage("Failure to connect socket; parameters are {}!").addArgument(parameter).setCause(ex).log();
builder.connected(Boolean.FALSE);
}
return builder.build();
}
/**
* Result of executing a test connect with somewhat detailed information.
*/
@Getter
@ToString
@lombok.Builder(builderClassName="Builder",toBuilder=true)
@AllArgsConstructor
public static class DetailedConnectResult {
/**
* Connect parameters.
*/
private final ConnectParameter connectParameter;
/**
* Remote socket address connected to.
*/
private final SocketAddress remoteAddress;
/**
* Local socket address connected to.
*/
private final SocketAddress localAddress;
/**
* Indicates, that an exception has occurred.
*/
private final Exception exception;
/**
* Indicates, if the test connect has failed.
* @return Indicates, if connect has failed.
*/
public boolean failure() {
return !success();
}
/**
* Indicates, if the test connect has succeeded.
* @return Indicates, if connect has succeeded.
*/
public boolean success() {
return exception==null;
}
}
/**
* Decorates a connect probe to return an indication of success.
* @param probe Connect probe.
* @param parameter Connect parameter.
* @return Result.
*/
private static DetailedConnectResult decorateWithDetailedConnectResult(ConnectProbe probe,
ConnectParameter parameter) {
ConnectOperation operation=ConnectOperations.create(parameter);
DetailedConnectResult.Builder builder= DetailedConnectResult.builder().connectParameter(parameter);
try {
probe.test(socket->{
operation.connect(socket);
builder.remoteAddress(socket.getRemoteSocketAddress());
builder.localAddress(socket.getLocalSocketAddress());
});
} catch (IOException ex) {
log.atTrace().setMessage("Failure to connect socket; parameters are {}!").addArgument(parameter).setCause(ex).log();
builder.exception(ex);
}
return builder.build();
}
/**
* Utilities addressing instances of {@link ConnectDecoration}.
*/
@UtilityClass
public static class ConnectDecorations {
/**
* Creates a decoration for a test connect with a basic result.
* @return Created decoration.
*/
public static ConnectDecoration createWithBoolean() {
return Sockets::decorateWithBoolean;
}
/**
* Creates a decoration for a test connect with a simple result.
* @return Created decoration.
*/
public static ConnectDecoration createWithSimpleConnectResult() {
return Sockets::decorateWithSimpleConnectResult;
}
/**
* Creates a decoration for a test connect with a detailed result.
* @return Created decoration.
*/
public static ConnectDecoration createWithDetailedConnectResult() {
return Sockets::decorateWithDetailedConnectResult;
}
}
/**
* Utility addressing the initiation of test connects.
*/
@UtilityClass
public static class TestConnects {
/**
* Initiates a test connect with a basic result.
* @param parameter Connect parameter.
* @param executor Executor.
* @return Result.
*/
public static CompletableFuture withBoolean(ConnectParameter parameter,
Executor executor) {
return initiateTestConnect(ConnectDecorations.createWithBoolean(),parameter,executor);
}
/**
* Initiates a test connect with a simple result.
* @param parameter Connect parameter.
* @param executor Executor.
* @return Result.
*/
public static CompletableFuture withWithSimpleConnectResult(ConnectParameter parameter,
Executor executor) {
return initiateTestConnect(ConnectDecorations.createWithSimpleConnectResult(),parameter,executor);
}
/**
* Initiates a test connect with a detailed result.
* @param parameter Connect parameter.
* @param executor Executor.
* @return Result.
*/
public static CompletableFuture withDetailedConnectResult(ConnectParameter parameter,
Executor executor) {
return initiateTestConnect(ConnectDecorations.createWithDetailedConnectResult(),parameter,executor);
}
}
}