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}