/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.common.statistic;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.math.MathOperations;
import org.modeshape.common.text.Inflector;
import org.modeshape.common.util.StringUtil;
/**
* Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
* {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and {@link #getMean()
* mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the {@link DetailedStatistics#getMedian()
* median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the {@link DetailedStatistics#getHistogram()
* histogram} of the values.
* <p>
* This class is threadsafe.
* </p>
*
* @param <T> the number type used in these statistics
*/
@ThreadSafe
public class SimpleStatistics<T extends Number> {
protected final MathOperations<T> math;
private int count = 0;
private T total;
private T maximum;
private T minimum;
private T mean;
private Double meanValue;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public SimpleStatistics( MathOperations<T> operations ) {
this.math = operations;
this.total = this.math.createZeroValue();
this.maximum = this.math.createZeroValue();
this.minimum = null;
this.mean = this.math.createZeroValue();
this.meanValue = 0.0d;
}
/**
* Add a new value to these statistics.
*
* @param value the new value
*/
public void add( T value ) {
Lock lock = this.lock.writeLock();
try {
lock.lock();
doAddValue(value);
} finally {
lock.unlock();
}
}
/**
* A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
* write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
*
* @param value the value already added
*/
protected void doAddValue( T value ) {
if (value == null) return;
// Modify the basic statistics ...
++this.count;
this.total = math.add(this.total, value);
this.maximum = this.math.maximum(this.maximum, value);
this.minimum = this.math.minimum(this.minimum, value);
// Calculate the mean and standard deviation ...
int count = getCount();
if (count == 1) {
// M(1) = x(1)
this.meanValue = value.doubleValue();
this.mean = value;
} else {
double dValue = value.doubleValue();
double dCount = count;
// M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
this.mean = this.math.create(this.meanValue);
}
}
/**
* Get the aggregate sum of the values in the series.
*
* @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
*/
public T getTotal() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.total;
} finally {
lock.unlock();
}
}
/**
* Get the maximum value in the series.
*
* @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
*/
public T getMaximum() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.maximum;
} finally {
lock.unlock();
}
}
/**
* Get the minimum value in the series.
*
* @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
*/
public T getMinimum() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
} finally {
lock.unlock();
}
}
/**
* Get the number of values that have been measured.
*
* @return the count
*/
public int getCount() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.count;
} finally {
lock.unlock();
}
}
/**
* Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
* the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
*
* @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
*/
public T getMean() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.mean;
} finally {
lock.unlock();
}
}
/**
* Return the mean (average) value.
*
* @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
* @see #getMean()
*/
public double getMeanValue() {
Lock lock = this.lock.readLock();
lock.lock();
try {
return this.meanValue;
} finally {
lock.unlock();
}
}
/**
* Reset the statistics in this object, and clear out any stored information.
*/
public void reset() {
Lock lock = this.lock.writeLock();
lock.lock();
try {
doReset();
} finally {
lock.unlock();
}
}
public MathOperations<T> getMathOperations() {
return math;
}
protected ReadWriteLock getLock() {
return this.lock;
}
/**
* Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
* locked for write and does work; therefore, the subclass should call this method.
*/
protected void doReset() {
this.total = this.math.createZeroValue();
this.maximum = this.math.createZeroValue();
this.minimum = null;
this.mean = this.math.createZeroValue();
this.meanValue = 0.0d;
this.count = 0;
}
@Override
public String toString() {
int count = this.getCount();
String samples = Inflector.getInstance().pluralize("sample", count);
return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}",
count,
samples,
this.minimum,
this.mean,
this.maximum);
}
}