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 com.soklet.internal.ObjectIdentity.sameInstance; 025import static java.util.Objects.requireNonNull; 026 027/** 028 * Indicates that shutdown ended without complete termination proof. 029 * <p> 030 * The message includes the bounded diagnostic from {@link #getShutdownResult()}, 031 * identifying component dispositions and residual-activity categories without 032 * rendering application Throwables or retained objects. Outstanding work alone 033 * does not supply a Throwable cause; inspect the result's component evidence. 034 */ 035@NotThreadSafe 036public final class SokletShutdownIncompleteException 037 extends SokletLifecycleException { 038 @Nullable 039 private final Object retainedScopeEvidence; 040 041 SokletShutdownIncompleteException(@NonNull ShutdownResult shutdownResult) { 042 this(shutdownResult, null, null); 043 } 044 045 SokletShutdownIncompleteException(@NonNull ShutdownResult shutdownResult, 046 @Nullable Object retainedScopeEvidence, @Nullable Throwable cause) { 047 super("Soklet shutdown could not prove complete termination: " + requireNonNull(shutdownResult), 048 requireNonNull(shutdownResult), cause); 049 this.retainedScopeEvidence = retainedScopeEvidence; 050 if (shutdownResult.getShutdownDisposition() != ShutdownDisposition.INCOMPLETE) 051 throw new IllegalArgumentException( 052 "SokletShutdownIncompleteException requires an incomplete result"); 053 } 054 055 SokletShutdownIncompleteException( 056 @NonNull InternalShutdownResult shutdownResult) { 057 this(ShutdownResult.fromInternal(requireNonNull(shutdownResult))); 058 } 059 060 SokletShutdownIncompleteException( 061 @NonNull InternalShutdownResult shutdownResult, 062 @Nullable Object retainedScopeEvidence, @Nullable Throwable cause) { 063 this(ShutdownResult.fromInternal(requireNonNull(shutdownResult)), 064 retainedScopeEvidence, cause); 065 } 066 067 boolean retainsScopeEvidence(@NonNull Object candidate) { 068 Object exactCandidate = requireNonNull(candidate); 069 return sameInstance(this.retainedScopeEvidence, exactCandidate) 070 || (this.retainedScopeEvidence 071 instanceof SimulatorConfigurationScopeIdentity scopeIdentity 072 && exactCandidate instanceof SimulatorConfig simulatorConfig 073 && simulatorConfig.belongsTo( 074 scopeIdentity.simulatorConfigurationIdentity())); 075 } 076}