001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2019 nobark (tools4j), Marco Terzer, Anton Anufriev
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.nobark.loop;
025
026import java.util.Objects;
027import java.util.concurrent.ThreadFactory;
028import java.util.function.Function;
029
030import org.tools4j.nobark.run.ShutdownableThread;
031import org.tools4j.nobark.run.StoppableThread;
032
033/**
034 * A loop performing a series of {@link Step steps} in an iterative manner as long as the {@link LoopCondition} is true.
035 * If no step performs any work in a whole iteration, an {@link IdleStrategy} is invoked.
036 */
037public class Loop implements Runnable {
038
039    private final LoopCondition loopCondition;
040    private final IdleStrategy idleStrategy;
041    private final ExceptionHandler exceptionHandler;
042    private final Step[] steps;
043
044    /**
045     * Constructor with loop condition, idle strategy, step exception handler and the steps to perform.
046     *
047     * @param loopCondition     the condition defining when the loop terminates
048     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
049     * @param exceptionHandler  the handler for step exceptions
050     * @param steps             the steps executed in the loop
051     */
052    public Loop(final LoopCondition loopCondition,
053                final IdleStrategy idleStrategy,
054                final ExceptionHandler exceptionHandler,
055                final Step... steps) {
056        this.loopCondition = Objects.requireNonNull(loopCondition);
057        this.idleStrategy = Objects.requireNonNull(idleStrategy);
058        this.exceptionHandler = Objects.requireNonNull(exceptionHandler);
059        this.steps = Objects.requireNonNull(steps);
060    }
061
062    /**
063     * Static factory method creating a loop with {@link StepProvider#normalStep(StepProvider) normal} steps using the
064     * given providers to construct the loop steps.
065     *
066     * @param loopCondition     the condition defining when the loop terminates
067     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
068     * @param exceptionHandler  the handler for step exceptions
069     * @param stepProviders     the providers for the steps executed during the loop
070     * @return new loop with steps to execute in the normal phase of a process
071     */
072    public static Loop mainLoop(final LoopCondition loopCondition,
073                                final IdleStrategy idleStrategy,
074                                final ExceptionHandler exceptionHandler,
075                                final StepProvider... stepProviders) {
076        return new Loop(loopCondition, idleStrategy, exceptionHandler, toSteps(stepProviders, StepProvider::normalStep));
077    }
078
079    /**
080     * Static factory method creating a loop with {@link StepProvider#shutdownStep(StepProvider) shutdown} steps using
081     * the given providers to construct the loop steps.
082     *
083     * @param loopCondition     the condition defining when the loop terminates
084     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
085     * @param exceptionHandler  the handler for step exceptions
086     * @param stepProviders     the providers for the steps executed during the loop
087     * @return new loop with steps to execute in the shutdown phase of a process
088     */
089    public static Loop shutdownLoop(final LoopCondition loopCondition,
090                                    final IdleStrategy idleStrategy,
091                                    final ExceptionHandler exceptionHandler,
092                                    final StepProvider... stepProviders) {
093        return new Loop(loopCondition, idleStrategy, exceptionHandler, toSteps(stepProviders, StepProvider::shutdownStep));
094    }
095
096    /**
097     * Creates, starts and returns a new thread running a loop with the given steps.
098     *
099     * @param idleStrategy      the strategy handling idle loop phases
100     * @param exceptionHandler  the step exception handler
101     * @param threadFactory     the factory to provide the service thread
102     * @param steps             the steps executed during the loop
103     * @return the newly created and started thread running the loop
104     */
105    public static StoppableThread start(final IdleStrategy idleStrategy,
106                                        final ExceptionHandler exceptionHandler,
107                                        final ThreadFactory threadFactory,
108                                        final Step... steps) {
109        Objects.requireNonNull(idleStrategy);
110        Objects.requireNonNull(exceptionHandler);
111        Objects.requireNonNull(steps);
112        return StoppableThread.start(
113                running -> new Loop(workDone -> running.keepRunning(), idleStrategy, exceptionHandler, steps),
114                threadFactory);
115    }
116
117    /**
118     * Creates, starts and returns a new thread running first a main loop and then another shutdown loop during the
119     * graceful {@link ShutdownableThread#shutdown shutdown} phase.  The loops are created with steps constructed with
120     * the given providers using {@link StepProvider#normalStep(StepProvider) normal} steps for the main loop and
121     * {@link StepProvider#shutdownStep(StepProvider) shutdown} steps for the shutdown loop.
122     *
123     * @param idleStrategy      the strategy handling idle main loop phases
124     * @param exceptionHandler  the step exception handler
125     * @param threadFactory     the factory to provide the service thread
126     * @param stepProviders     the providers for the steps executed during the loop
127     * @return the newly created and started thread running the loop
128     */
129    public static ShutdownableThread start(final IdleStrategy idleStrategy,
130                                           final ExceptionHandler exceptionHandler,
131                                           final ThreadFactory threadFactory,
132                                           final StepProvider... stepProviders) {
133        Objects.requireNonNull(idleStrategy);
134        Objects.requireNonNull(exceptionHandler);
135        Objects.requireNonNull(stepProviders);
136        return ShutdownableThread.start(
137                main -> mainLoop(workDone -> main.keepRunning(), idleStrategy, exceptionHandler, stepProviders),
138                shutdown -> shutdownLoop(workDone -> workDone && shutdown.keepRunning(), IdleStrategy.NO_OP, exceptionHandler, stepProviders),
139                threadFactory);
140    }
141
142    @Override
143    public void run() {
144        boolean workDone;
145        do {
146            workDone = false;
147            for (final Step step : steps) {
148                workDone |= exceptionHandler.performQuietly(this, step);
149            }
150            idleStrategy.idle(workDone);
151        } while (loopCondition.loopAgain(workDone));
152    }
153
154    private static Step[] toSteps(final StepProvider[] providers, final Function<StepProvider, Step> providerInvoker) {
155        //count first to avoid garbage
156        int count = 0;
157        for (final StepProvider provider : providers) {
158            if (providerInvoker.apply(provider) != Step.NO_OP) {
159                count++;
160            }
161        }
162        //now provide the array
163        final Step[] steps = new Step[count];
164        int index = 0;
165        for (final StepProvider provider : providers) {
166            final Step step = providerInvoker.apply(provider);
167            if (step != Step.NO_OP) {
168                steps[index] = step;
169                index++;
170            }
171        }
172        assert count == index;
173        return steps;
174    }
175}