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}