001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.statistic;
017
018import java.util.concurrent.locks.Lock;
019import java.util.concurrent.locks.ReadWriteLock;
020import java.util.concurrent.locks.ReentrantReadWriteLock;
021import org.modeshape.common.annotation.ThreadSafe;
022import org.modeshape.common.math.MathOperations;
023import org.modeshape.common.text.Inflector;
024import org.modeshape.common.util.StringUtil;
025
026/**
027 * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
028 * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and {@link #getMean()
029 * mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the {@link DetailedStatistics#getMedian()
030 * median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the {@link DetailedStatistics#getHistogram()
031 * histogram} of the values.
032 * <p>
033 * This class is threadsafe.
034 * </p>
035 * 
036 * @param <T> the number type used in these statistics
037 */
038@ThreadSafe
039public class SimpleStatistics<T extends Number> {
040
041    protected final MathOperations<T> math;
042    private int count = 0;
043    private T total;
044    private T maximum;
045    private T minimum;
046    private T mean;
047    private Double meanValue;
048    private final ReadWriteLock lock = new ReentrantReadWriteLock();
049
050    public SimpleStatistics( MathOperations<T> operations ) {
051        this.math = operations;
052        this.total = this.math.createZeroValue();
053        this.maximum = this.math.createZeroValue();
054        this.minimum = null;
055        this.mean = this.math.createZeroValue();
056        this.meanValue = 0.0d;
057    }
058
059    /**
060     * Add a new value to these statistics.
061     * 
062     * @param value the new value
063     */
064    public void add( T value ) {
065        Lock lock = this.lock.writeLock();
066        try {
067            lock.lock();
068            doAddValue(value);
069        } finally {
070            lock.unlock();
071        }
072    }
073
074    /**
075     * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
076     * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
077     * 
078     * @param value the value already added
079     */
080    protected void doAddValue( T value ) {
081        if (value == null) return;
082        // Modify the basic statistics ...
083        ++this.count;
084        this.total = math.add(this.total, value);
085        this.maximum = this.math.maximum(this.maximum, value);
086        this.minimum = this.math.minimum(this.minimum, value);
087        // Calculate the mean and standard deviation ...
088        int count = getCount();
089        if (count == 1) {
090            // M(1) = x(1)
091            this.meanValue = value.doubleValue();
092            this.mean = value;
093        } else {
094            double dValue = value.doubleValue();
095            double dCount = count;
096            // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
097            this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
098            this.mean = this.math.create(this.meanValue);
099        }
100    }
101
102    /**
103     * Get the aggregate sum of the values in the series.
104     * 
105     * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
106     */
107    public T getTotal() {
108        Lock lock = this.lock.readLock();
109        lock.lock();
110        try {
111            return this.total;
112        } finally {
113            lock.unlock();
114        }
115    }
116
117    /**
118     * Get the maximum value in the series.
119     * 
120     * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
121     */
122    public T getMaximum() {
123        Lock lock = this.lock.readLock();
124        lock.lock();
125        try {
126            return this.maximum;
127        } finally {
128            lock.unlock();
129        }
130    }
131
132    /**
133     * Get the minimum value in the series.
134     * 
135     * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
136     */
137    public T getMinimum() {
138        Lock lock = this.lock.readLock();
139        lock.lock();
140        try {
141            return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
142        } finally {
143            lock.unlock();
144        }
145    }
146
147    /**
148     * Get the number of values that have been measured.
149     * 
150     * @return the count
151     */
152    public int getCount() {
153        Lock lock = this.lock.readLock();
154        lock.lock();
155        try {
156            return this.count;
157        } finally {
158            lock.unlock();
159        }
160    }
161
162    /**
163     * Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
164     * the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
165     * 
166     * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
167     */
168    public T getMean() {
169        Lock lock = this.lock.readLock();
170        lock.lock();
171        try {
172            return this.mean;
173        } finally {
174            lock.unlock();
175        }
176    }
177
178    /**
179     * Return the mean (average) value.
180     * 
181     * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
182     * @see #getMean()
183     */
184    public double getMeanValue() {
185        Lock lock = this.lock.readLock();
186        lock.lock();
187        try {
188            return this.meanValue;
189        } finally {
190            lock.unlock();
191        }
192    }
193
194    /**
195     * Reset the statistics in this object, and clear out any stored information.
196     */
197    public void reset() {
198        Lock lock = this.lock.writeLock();
199        lock.lock();
200        try {
201            doReset();
202        } finally {
203            lock.unlock();
204        }
205    }
206
207    public MathOperations<T> getMathOperations() {
208        return math;
209    }
210
211    protected ReadWriteLock getLock() {
212        return this.lock;
213    }
214
215    /**
216     * Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
217     * locked for write and does work; therefore, the subclass should call this method.
218     */
219    protected void doReset() {
220        this.total = this.math.createZeroValue();
221        this.maximum = this.math.createZeroValue();
222        this.minimum = null;
223        this.mean = this.math.createZeroValue();
224        this.meanValue = 0.0d;
225        this.count = 0;
226    }
227
228    @Override
229    public String toString() {
230        int count = this.getCount();
231        String samples = Inflector.getInstance().pluralize("sample", count);
232        return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}",
233                                       count,
234                                       samples,
235                                       this.minimum,
236                                       this.mean,
237                                       this.maximum);
238    }
239
240}