// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.ide.util.executor; import org.eclipse.che.ide.runtime.Assert; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; /** * Executor of a cancellable repeating command. * <p/> * <p>Execution can be cancelled with {@link #cancel()} and rescheduled * with consequent {@link #schedule(int)} invocations. * <p/> * <p>Generally method {@link #execute()} is invoked after * (<i>{@link #tickDurationMs} * {@link #tickCount}</i>) ms. * If this method return {@code true} then it is called again * after {@link #tickDurationMs} ms. * <p/> * <p>If method {@link #schedule(int)} is called before {@link #execute()} had * finally returned {@code false} then {@link #execute()} will be called * only after (<i>{@link #tickDurationMs} * {@link #tickCount}</i>) ms. * <p/> * <p>At any time there is at most one scheduled {@link RepeatingCommand}. * Thus duration to the next {@link #execute()} invocation is not exact. * Actually duration can be less than expected by, at most, * {@link #tickDurationMs}. */ public abstract class DeferredCommandExecutor { /** * A time granule size. * <p/> * <p>Bigger values make scheduling less accurate. * Lesser values lead to more frequent idle cycles. */ private final int tickDurationMs; /** * A number of the idle cycles before action is executed. * <p/> * <p>{@code -1} means that action is not going to be executed. */ private int tickCount; /** Indicator that shows if {@link #repeatingCommand} is still scheduled. */ private boolean repeatingCommandAlive; /** * Synchronisation / anti-recursion safeguard. * <p/> * <p>{@code true} when action is executed from timer callback. */ private boolean isExecuting; /** Command that is regularly executed to check if it is time to run action. */ private final RepeatingCommand repeatingCommand = new RepeatingCommand() { @Override public boolean execute() { isExecuting = true; try { repeatingCommandAlive = DeferredCommandExecutor.this.onTick(); } finally { isExecuting = false; } if (!repeatingCommandAlive) { tickCount = -1; } return repeatingCommandAlive; } }; private boolean onTick() { // If disarmed if (tickCount < 0) { return false; } // It is not time yet if (tickCount > 0) { tickCount--; } if (tickCount > 0) { return true; } return execute(); } /** * Method that is invoked on schedule. * <p/> * <p>After scheduled invocation, method will be re-invoked each * {@link #tickDurationMs}ms until it return {@code false}, or * {@link #cancel()} / {@link #schedule(int)} is called. */ protected abstract boolean execute(); /** Schedule / reschedule {@link #execute()} invocation. */ public void schedule(int tickCount) { Assert.isTrue(!isExecuting); Assert.isLegal(tickCount > 0); this.tickCount = tickCount; if (!repeatingCommandAlive) { repeatingCommandAlive = true; Scheduler.get().scheduleFixedDelay(repeatingCommand, tickDurationMs); } } /** Cancel scheduled {@link #execute()} invocation. */ public void cancel() { Assert.isTrue(!isExecuting); tickCount = -1; } /** Check if executor is going to invoke {@link #execute()} again. */ public boolean isScheduled() { Assert.isTrue(!isExecuting); return tickCount >= 0; } protected DeferredCommandExecutor(int tickDurationMs) { this.tickDurationMs = tickDurationMs; } }