001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2018 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
030/**
031 * A loop performing a series of {@link Step steps} in an iterative manner as long as the {@link LoopCondition} is true.
032 * If no step performs any work in a whole iteration, an {@link IdleStrategy} is invoked.
033 */
034public class Loop implements Runnable {
035
036    private final LoopCondition loopCondition;
037    private final IdleStrategy idleStrategy;
038    private final ExceptionHandler exceptionHandler;
039    private final Step[] steps;
040
041    /**
042     * Constructor with loop condition, idle strategy, step exception handler and the steps to perform.
043     *
044     * @param loopCondition     the condition defining when the loop terminates
045     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
046     * @param exceptionHandler  the handler for step exceptions
047     * @param steps             the steps executed in the loop
048     */
049    public Loop(final LoopCondition loopCondition,
050                final IdleStrategy idleStrategy,
051                final ExceptionHandler exceptionHandler,
052                final Step... steps) {
053        this.loopCondition = Objects.requireNonNull(loopCondition);
054        this.idleStrategy = Objects.requireNonNull(idleStrategy);
055        this.exceptionHandler = Objects.requireNonNull(exceptionHandler);
056        this.steps = Objects.requireNonNull(steps);
057    }
058
059    /**
060     * Static factory method creating a loop with {@link StepProvider#normalStep(StepProvider) normal} steps using the
061     * given providers to construct the loop steps.
062     *
063     * @param loopCondition     the condition defining when the loop terminates
064     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
065     * @param exceptionHandler  the handler for step exceptions
066     * @param stepProviders     the providers for the steps executed during the loop
067     * @return new loop with steps to execute in the normal phase of a process
068     */
069    public static Loop mainLoop(final LoopCondition loopCondition,
070                                final IdleStrategy idleStrategy,
071                                final ExceptionHandler exceptionHandler,
072                                final StepProvider... stepProviders) {
073        return new Loop(loopCondition, idleStrategy, exceptionHandler, toSteps(stepProviders, StepProvider::normalStep));
074    }
075
076    /**
077     * Static factory method creating a loop with {@link StepProvider#shutdownStep(StepProvider) shutdown} steps using
078     * the given providers to construct the loop steps.
079     *
080     * @param loopCondition     the condition defining when the loop terminates
081     * @param idleStrategy      the idle strategy defining how to handle situations without work to do
082     * @param exceptionHandler  the handler for step exceptions
083     * @param stepProviders     the providers for the steps executed during the loop
084     * @return new loop with steps to execute in the shutdown phase of a process
085     */
086    public static Loop shutdownLoop(final LoopCondition loopCondition,
087                                    final IdleStrategy idleStrategy,
088                                    final ExceptionHandler exceptionHandler,
089                                    final StepProvider... stepProviders) {
090        return new Loop(loopCondition, idleStrategy, exceptionHandler, toSteps(stepProviders, StepProvider::shutdownStep));
091    }
092
093    /**
094     * Creates, starts and returns a new thread running a loop with the given steps.
095     *
096     * @param idleStrategy      the strategy handling idle loop phases
097     * @param exceptionHandler  the step exception handler
098     * @param threadFactory     the factory to provide the service thread
099     * @param steps             the steps executed during the loop
100     * @return the newly created and started thread running the loop
101     */
102    public static StoppableThread start(final IdleStrategy idleStrategy,
103                                        final ExceptionHandler exceptionHandler,
104                                        final ThreadFactory threadFactory,
105                                        final Step... steps) {
106        Objects.requireNonNull(idleStrategy);
107        Objects.requireNonNull(exceptionHandler);
108        Objects.requireNonNull(steps);
109        return StoppableThread.start(
110                running -> new Loop(workDone -> running.getAsBoolean(), idleStrategy, exceptionHandler, steps),
111                threadFactory);
112    }
113
114    /**
115     * Creates, starts and returns a new thread running first a main loop and then another shutdown loop during the
116     * graceful {@link ShutdownableThread#shutdown shutdown} phase.  The loops are created with steps constructed with
117     * the given providers using {@link StepProvider#normalStep(StepProvider) normal} steps for the main loop and
118     * {@link StepProvider#shutdownStep(StepProvider) shutdown} steps for the shutdown loop.
119     *
120     * @param idleStrategy      the strategy handling idle main loop phases
121     * @param exceptionHandler  the step exception handler
122     * @param threadFactory     the factory to provide the service thread
123     * @param stepProviders     the providers for the steps executed during the loop
124     * @return the newly created and started thread running the loop
125     */
126    public static ShutdownableThread start(final IdleStrategy idleStrategy,
127                                           final ExceptionHandler exceptionHandler,
128                                           final ThreadFactory threadFactory,
129                                           final StepProvider... stepProviders) {
130        Objects.requireNonNull(idleStrategy);
131        Objects.requireNonNull(exceptionHandler);
132        Objects.requireNonNull(stepProviders);
133        return ShutdownableThread.start(
134                runMain -> mainLoop(workDone -> runMain.getAsBoolean(), idleStrategy, exceptionHandler, stepProviders),
135                runShutown -> shutdownLoop(workDone -> workDone && runShutown.getAsBoolean(), IdleStrategy.NO_OP, exceptionHandler, stepProviders),
136                threadFactory);
137    }
138
139    @Override
140    public void run() {
141        boolean workDone;
142        do {
143            workDone = false;
144            for (final Step step : steps) {
145                workDone |= exceptionHandler.performQuietly(this, step);
146            }
147            idleStrategy.idle(workDone);
148        } while (loopCondition.loopAgain(workDone));
149    }
150
151    private static Step[] toSteps(final StepProvider[] providers, final Function<StepProvider, Step> providerInvoker) {
152        //count first to avoid garbage
153        int count = 0;
154        for (final StepProvider provider : providers) {
155            if (providerInvoker.apply(provider) != Step.NO_OP) {
156                count++;
157            }
158        }
159        //now provide the array
160        final Step[] steps = new Step[count];
161        int index = 0;
162        for (final StepProvider provider : providers) {
163            final Step step = providerInvoker.apply(provider);
164            if (step != Step.NO_OP) {
165                steps[index] = step;
166                index++;
167            }
168        }
169        assert count == index;
170        return steps;
171    }
172}