001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2024-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.common.context.testlib;
019
020import dev.enola.common.context.Singleton;
021
022import org.junit.rules.TestRule;
023import org.junit.runner.Description;
024import org.junit.runners.model.Statement;
025
026/** JUnit <code>@ClassRule</code> (or even just <code>@Rule</code>) for {@link Singleton}. */
027public class SingletonRule implements TestRule {
028
029    /** Intended to be statically imported into *Test classes. */
030    public static SingletonRule $(Singleton<?>... singletons) {
031        return new SingletonRule(singletons, true);
032    }
033
034    public static SingletonRule onlyReset(Singleton<?>... singletons) {
035        return new SingletonRule(singletons, false);
036    }
037
038    private final Singleton<?>[] singletons;
039    private boolean doNotReset = false;
040
041    private SingletonRule(Singleton<?>[] singletons, boolean get) {
042        if (get)
043            for (var singleton : singletons)
044                // This may throw IllegalStateException (which is what we want)
045                singleton.get();
046        this.singletons = singletons;
047    }
048
049    @Override
050    public Statement apply(Statement base, Description description) {
051        return statement(base);
052    }
053
054    private <T> Statement statement(Statement base) {
055        return new Statement() {
056            @Override
057            public void evaluate() throws Throwable {
058                try {
059                    base.evaluate();
060                } finally {
061                    if (!doNotReset)
062                        for (var singleton : singletons)
063                            try {
064                                singleton.reset();
065                            } catch (IllegalStateException ignored) {
066                                // IGNORE!
067                            }
068                    doNotReset = false;
069                }
070            }
071        };
072    }
073
074    public void doNotReset() {
075        this.doNotReset = true;
076    }
077}