/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration), * All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Created on May 24, 2007 * */ package alma.acs.logging; import java.util.concurrent.TimeUnit; /** * This class can be used to reduce the repeated execution of any kind of code, * by keeping track of the number of intended executions and the time passed since the last actual execution, * and comparing these against configured values (see details below). *
* A reduction of the number of executions can be useful if that code is resource intensive (e.g. DB access, remote calls, logging) * or has other unwanted side effects if executed too often (e.g. annoying the user with too frequent error or confirmation dialogues). * Of course this only makes sense if the nature of the problem allows skipping some of these executions. *
* This class is so general that it does not actually execute any code; it simply keeps track of counters and timer.
* It can be used directly to manage decisions about skipping or executing some code.
* For example, the application can instantiate a RepeatGuard
with the timer set to one second.
* Then whenever the repetitive action would be called, the application first calls {@link #check()};
* for every timer interval, only the first call to check()
will return true
,
* while the subsequent calls will return false
, and the application should skip the repeated block of code.
* Alternatively this class can be extended or wrapped to become easier to use for specific purposes.
* Then typically the code to be executed (e.g. logging a message) becomes part of the specialized repeat guard class,
* as in {@link RepeatGuardLogger}.
*
* The concept of repeat guards in ACS is discussed at http://almasw.hq.eso.org/almasw/bin/view/ACS/LoggingRepetitionControl *
* Repetitions can be reduced using *
OR
combination of the above: either enough attempts were made or enough time has passed, whatever happens first
* AND
combination: enough skipped execution attempts, and enough time passed.
* timeUnit
units).
* @param timeUnit Time unit of interval
parameter.
* @param maxRepetitions Maximum number of repetitions.
* @param logic Evaluation logic for interval
and maxRepetitions
.
* The logic will be "reduced" automatically if interval
or maxRepetitions
* have a value <= 0, so as to be based only on the other positive value.
* @throws IllegalArgumentException if maxRepetitions <= 0 && interval <= 0
*/
public RepeatGuard(long interval, TimeUnit timeUnit, int maxRepetitions, Logic logic) {
reset(interval, timeUnit, maxRepetitions, logic);
}
/**
* Constructor, convenience for the above, using {@link Logic.OR} evaluation method
* if both interval
and maxRepetitions
are positive values,
* otherwise {@Logic.TIMER} or {@Logic.COUNTER} to make sure that only the respective parameter with a positive value gets used.
* @param interval Time interval (in timeUnit
units).
* @param timeUnit Time unit of interval
parameter.
* @param maxRepetitions Maximum number of repetitions.
* @throws IllegalArgumentException if maxRepetitions <= 0 && interval <= 0
*/
public RepeatGuard(long interval, TimeUnit timeUnit, int maxRepetitions) {
this(interval, timeUnit, maxRepetitions, Logic.OR);
}
/**
* This method checks if the guarded activity is due for execution, or if it should be skipped instead.
*
* For the first call, it always returns true.
* Later it returns true
if the last call to check()
was longer ago than the interval
* given in the constructor or in the reset
methods,
* and/or if the internal counter has been incremented more than maxRepetitions
* by calls to {@link #increment()} or {@link #checkAndIncrement()}.
* @return true
if guarded activity should be run, false
if it should be skipped.
*/
public synchronized boolean check() {
long now = System.nanoTime();
// first time check always returns true, regardless of counter and timer. Then resets timer and counter.
if (firstTime) {
firstTime = false;
counterAtLastExecution = counter;
counter = 0;
endTimeNs = now + intervalNs;
return true;
}
switch (evaluationMethod) {
case AND:
if ((now >= endTimeNs) && (counter >= maxRepetitions)) {
counterAtLastExecution = counter;
counter = 0;
endTimeNs = now + intervalNs;
return true;
}
return false;
case OR:
if ((now >= endTimeNs) || (counter >= maxRepetitions)) {
counterAtLastExecution = counter;
counter = 0;
endTimeNs = now + intervalNs;
return true;
}
return false;
case TIMER:
if (now >= endTimeNs) {
counterAtLastExecution = counter;
counter = 0;
while (endTimeNs <= now) { // we may have to "catch up" several timer intervals during which nothing was logged
endTimeNs += intervalNs; //endTime + interval instead of now + interval to prevent drift
}
return true;
}
return false;
case COUNTER:
if (counter >= maxRepetitions) {
counterAtLastExecution = counter;
counter = 0;
endTimeNs = now + intervalNs;
return true;
}
return false;
default:
throw new IllegalStateException("Unexpected value of RepeatGuard#evaluationMethod='" + evaluationMethod.toString() + "' found. Please report this bug to ACS.");
}
}
/**
* Increments the counter and checks (see {@link #check()}).
* @return true
if OK, false
if should be guarded
* @see #check()
*/
public synchronized boolean checkAndIncrement() {
counter++;
return check();
}
/**
* Resets and reconfigures this guard using the given interval, maxRepetitions, and {@link Logic.OR} logic.
* @param interval Time interval (in timeUnit
units).
* @param timeUnit Time unit of interval
parameter.
* @param maxRepetitions Maximum number of skipped repetitions.
* @throws IllegalArgumentException if maxRepetitions <= 0 && interval <= 0
* @see #RepeatGuard(long, TimeUnit, int, Logic)
*/
public void reset(long interval, TimeUnit timeUnit, int maxRepetitions) {
reset(interval, timeUnit, maxRepetitions, Logic.OR);
}
/**
* Resets and reconfigures logic of guard.
* @param interval Time interval (in timeUnit
units).
* @param timeUnit Time unit of interval
parameter.
* @param maxRepetitions Maximum number of skipped repetitions.
* @param logic Evaluation logic for interval
and maxRepetitions
.
* The logic will be "reduced" automatically if interval
or maxRepetitions
* have a value <= 0, so as to be based only on the other positive value.
* @throws IllegalArgumentException if maxRepetitions <= 0 && interval <= 0
or required arg == null
*/
public synchronized void reset(long interval, TimeUnit timeUnit, int maxRepetitions, Logic logic) {
// check parameters
if (maxRepetitions <= 0 && interval <= 0) {
throw new IllegalArgumentException("maxRepetitions <= 0 && interval <= 0 not allowed.");
}
if (timeUnit == null && interval > 0) {
throw new IllegalArgumentException("A timeUnit must be be specified.");
}
if (logic == null) {
throw new IllegalArgumentException("A 'logic' enum value must be specified.");
}
// use only maxRepetitions if interval <= 0
if (interval <= 0) {
evaluationMethod = Logic.COUNTER;
}
// use only interval if maxRepetitions <= 0
else if (maxRepetitions <= 0) {
evaluationMethod = Logic.TIMER;
}
else {
// use interval and/or maxRepetitions as given by the logic parameter.
this.evaluationMethod = logic;
}
this.intervalNs = ( timeUnit != null ? timeUnit.toNanos(interval) : 0 );
this.maxRepetitions = maxRepetitions;
reset();
}
/**
* Resets this guard without changing the configuration for timer, counter and logic.
*/
public synchronized void reset() {
counter = 0;
counterAtLastExecution = 0;
firstTime = true;
}
/**
* Increase counter value.
*/
public synchronized void increment() {
counter++;
}
/**
* Get current counter value.
* @return current counter value.
*/
public synchronized int counter() {
return counter;
}
/**
* Gets the value of the counter that it had when the {@link #check()} or
* {@link #checkAndIncrement()} method returned true
the last time,
* which corresponds to the number of times the activity was skipped before it got executed.
*
* Calling this method does not make sense if only timer logic was used (no counters configured nor incremented) */ public synchronized int counterAtLastExecution() { return counterAtLastExecution; } }