001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.ThreadSafe;
023import java.util.Optional;
024
025import static java.lang.String.format;
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Encapsulates a Server-Sent Event comment payload and its comment type.
030 *
031 * @author <a href="https://www.revetkn.com">Mark Allen</a>
032 */
033@ThreadSafe
034public final class ServerSentEventComment {
035        @NonNull
036        private static final ServerSentEventComment HEARTBEAT_INSTANCE;
037
038        static {
039                HEARTBEAT_INSTANCE = new ServerSentEventComment(null, CommentType.HEARTBEAT);
040        }
041
042        @Nullable
043        private final String comment;
044        @NonNull
045        private final CommentType commentType;
046
047        /**
048         * Types of Server-Sent Event comments.
049         *
050         * @author <a href="https://www.revetkn.com">Mark Allen</a>
051         */
052        public enum CommentType {
053                /**
054                 * Application-provided comment.
055                 */
056                COMMENT,
057                /**
058                 * Keep-alive/heartbeat comment.
059                 */
060                HEARTBEAT
061        }
062
063        /**
064         * Acquires a {@link ServerSentEventComment} instance with a {@code comment} payload.
065         *
066         * @param comment the comment payload for the instance
067         * @return the comment instance
068         */
069        @NonNull
070public static ServerSentEventComment fromComment(@NonNull String comment) {
071                return new ServerSentEventComment(requireNonNull(comment), CommentType.COMMENT);
072        }
073
074        /**
075         * Acquires a shared heartbeat comment instance.
076         * <p>
077         * Heartbeat comments do not carry a payload; {@link #getComment()} will be empty.
078         *
079         * @return a shared heartbeat comment instance
080         */
081        @NonNull
082        public static ServerSentEventComment heartbeatInstance() {
083                return HEARTBEAT_INSTANCE;
084        }
085
086        private ServerSentEventComment(@Nullable String comment,
087                                                                                                                                 @NonNull CommentType commentType) {
088                this.comment = comment;
089                this.commentType = requireNonNull(commentType);
090
091                if (this.commentType == CommentType.COMMENT && this.comment == null)
092                        throw new IllegalArgumentException(format("%s 'comment' values must not be null for %s comments",
093                                        ServerSentEventComment.class.getSimpleName(), CommentType.COMMENT));
094
095                if (this.commentType == CommentType.HEARTBEAT && this.comment != null)
096                        throw new IllegalArgumentException(format("%s 'comment' values must be null for %s comments",
097                                        ServerSentEventComment.class.getSimpleName(), CommentType.HEARTBEAT));
098        }
099
100        /**
101         * The comment payload.
102         * <p>
103         * Heartbeat comments return {@link Optional#empty()}.
104         *
105         * @return the comment payload
106         */
107        @NonNull
108        public Optional<String> getComment() {
109                return Optional.ofNullable(this.comment);
110        }
111
112        /**
113         * The comment type.
114         *
115         * @return the comment type
116         */
117        @NonNull
118        public CommentType getCommentType() {
119                return this.commentType;
120        }
121
122        @Override
123        @NonNull
124        public String toString() {
125                return format("%s{commentType=%s, comment=%s}", getClass().getSimpleName(),
126                                getCommentType(), getComment().orElse(""));
127        }
128}