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.NotThreadSafe;
023
024import static java.util.Objects.requireNonNull;
025
026/** Indicates that the sole startup attempt ended before readiness. */
027@NotThreadSafe
028public final class SokletStartupException extends SokletLifecycleException {
029        @NonNull
030        private final StartupDisposition startupDisposition;
031
032        SokletStartupException(@NonNull ShutdownResult shutdownResult) {
033                this(shutdownResult, null);
034        }
035
036        SokletStartupException(@NonNull ShutdownResult shutdownResult,
037                        @Nullable Throwable cause) {
038                super(message(requireNonNull(shutdownResult).getStartupDisposition()),
039                                shutdownResult, cause);
040                this.startupDisposition = shutdownResult.getStartupDisposition();
041                if (this.startupDisposition == StartupDisposition.READY)
042                        throw new IllegalArgumentException(
043                                        "SokletStartupException requires a non-ready result");
044        }
045
046        SokletStartupException(
047                        @NonNull InternalStartupDisposition startupDisposition,
048                        @NonNull InternalShutdownResult shutdownResult) {
049                this(ShutdownResult.fromInternal(requireNonNull(shutdownResult)));
050                verifyInternalDisposition(startupDisposition, shutdownResult);
051        }
052
053        SokletStartupException(
054                        @NonNull InternalStartupDisposition startupDisposition,
055                        @NonNull InternalShutdownResult shutdownResult,
056                        @NonNull Throwable cause) {
057                this(ShutdownResult.fromInternal(requireNonNull(shutdownResult),
058                                requireNonNull(cause), null, null), cause);
059                verifyInternalDisposition(startupDisposition, shutdownResult);
060        }
061
062        /** @return disposition that prevented readiness */
063        @NonNull
064        public StartupDisposition getStartupDisposition() {
065                return this.startupDisposition;
066        }
067
068        @NonNull
069        InternalStartupDisposition getInternalStartupDisposition() {
070                return InternalStartupDisposition.valueOf(
071                                this.startupDisposition.name());
072        }
073
074        private static void verifyInternalDisposition(
075                        @NonNull InternalStartupDisposition startupDisposition,
076                        @NonNull InternalShutdownResult shutdownResult) {
077                if (requireNonNull(shutdownResult).startupDisposition()
078                                != requireNonNull(startupDisposition))
079                        throw new IllegalArgumentException(
080                                        "Startup exception disposition must match its shutdown result");
081        }
082
083        @NonNull
084        private static String message(@NonNull StartupDisposition disposition) {
085                return "Soklet startup did not reach readiness: "
086                                + requireNonNull(disposition);
087        }
088}