ConnectionEvent.java

/*
 * SPDX-FileCopyrightText: 2023-2025 Lucimber UG
 * SPDX-License-Identifier: Apache-2.0
 */
package com.lucimber.dbus.connection;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;

/**
 * Represents a connection lifecycle event.
 *
 * <p>Connection events are fired when the connection state changes, health checks succeed or fail,
 * or other significant connection-related events occur.
 */
public final class ConnectionEvent {

    private final ConnectionEventType type;
    private final ConnectionState oldState;
    private final ConnectionState newState;
    private final Instant timestamp;
    private final String message;
    private final Throwable cause;

    private ConnectionEvent(Builder builder) {
        this.type = Objects.requireNonNull(builder.type, "Event type cannot be null");
        this.oldState = builder.oldState;
        this.newState = builder.newState;
        this.timestamp = builder.timestamp != null ? builder.timestamp : Instant.now();
        this.message = builder.message;
        this.cause = builder.cause;
    }

    /**
     * Creates a new builder for constructing connection events.
     *
     * @param type The type of event
     * @return A new builder instance
     */
    public static Builder builder(ConnectionEventType type) {
        return new Builder(type);
    }

    /**
     * Creates a state change event.
     *
     * @param oldState The previous connection state
     * @param newState The new connection state
     * @return A state change event
     */
    public static ConnectionEvent stateChanged(ConnectionState oldState, ConnectionState newState) {
        return builder(ConnectionEventType.STATE_CHANGED)
                .withOldState(oldState)
                .withNewState(newState)
                .withMessage("Connection state changed from " + oldState + " to " + newState)
                .build();
    }

    /**
     * Creates a health check success event.
     *
     * @return A health check success event
     */
    public static ConnectionEvent healthCheckSuccess() {
        return builder(ConnectionEventType.HEALTH_CHECK_SUCCESS)
                .withMessage("Health check succeeded")
                .build();
    }

    /**
     * Creates a health check failure event.
     *
     * @param cause The cause of the health check failure
     * @return A health check failure event
     */
    public static ConnectionEvent healthCheckFailure(Throwable cause) {
        return builder(ConnectionEventType.HEALTH_CHECK_FAILURE)
                .withMessage("Health check failed: " + cause.getMessage())
                .withCause(cause)
                .build();
    }

    /**
     * Creates a reconnection attempt event.
     *
     * @param attemptNumber The reconnection attempt number
     * @return A reconnection attempt event
     */
    public static ConnectionEvent reconnectionAttempt(int attemptNumber) {
        return builder(ConnectionEventType.RECONNECTION_ATTEMPT)
                .withMessage("Reconnection attempt #" + attemptNumber)
                .build();
    }

    public ConnectionEventType getType() {
        return type;
    }

    public Optional<ConnectionState> getOldState() {
        return Optional.ofNullable(oldState);
    }

    public Optional<ConnectionState> getNewState() {
        return Optional.ofNullable(newState);
    }

    public Instant getTimestamp() {
        return timestamp;
    }

    public Optional<String> getMessage() {
        return Optional.ofNullable(message);
    }

    public Optional<Throwable> getCause() {
        return Optional.ofNullable(cause);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("ConnectionEvent{");
        sb.append("type=").append(type);
        sb.append(", timestamp=").append(timestamp);
        if (oldState != null || newState != null) {
            sb.append(", state=").append(oldState).append("->").append(newState);
        }
        if (message != null) {
            sb.append(", message='").append(message).append('\'');
        }
        if (cause != null) {
            sb.append(", cause=").append(cause.getClass().getSimpleName());
        }
        sb.append('}');
        return sb.toString();
    }

    /** Builder for creating {@link ConnectionEvent} instances. */
    public static final class Builder {
        private final ConnectionEventType type;
        private ConnectionState oldState;
        private ConnectionState newState;
        private Instant timestamp;
        private String message;
        private Throwable cause;

        private Builder(ConnectionEventType type) {
            this.type = type;
        }

        public Builder withOldState(ConnectionState oldState) {
            this.oldState = oldState;
            return this;
        }

        public Builder withNewState(ConnectionState newState) {
            this.newState = newState;
            return this;
        }

        public Builder withTimestamp(Instant timestamp) {
            this.timestamp = timestamp;
            return this;
        }

        public Builder withMessage(String message) {
            this.message = message;
            return this;
        }

        public Builder withCause(Throwable cause) {
            this.cause = cause;
            return this;
        }

        public ConnectionEvent build() {
            return new ConnectionEvent(this);
        }
    }
}