View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.common.statistic;
25  
26  import java.util.concurrent.locks.Lock;
27  import java.util.concurrent.locks.ReadWriteLock;
28  import java.util.concurrent.locks.ReentrantReadWriteLock;
29  import net.jcip.annotations.ThreadSafe;
30  import org.modeshape.common.math.MathOperations;
31  import org.modeshape.common.text.Inflector;
32  import org.modeshape.common.util.StringUtil;
33  
34  /**
35   * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
36   * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and {@link #getMean()
37   * mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the {@link DetailedStatistics#getMedian()
38   * median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the {@link DetailedStatistics#getHistogram()
39   * histogram} of the values.
40   * <p>
41   * This class is threadsafe.
42   * </p>
43   * 
44   * @param <T> the number type used in these statistics
45   */
46  @ThreadSafe
47  public class SimpleStatistics<T extends Number> {
48  
49      protected final MathOperations<T> math;
50      private int count = 0;
51      private T total;
52      private T maximum;
53      private T minimum;
54      private T mean;
55      private Double meanValue;
56      private final ReadWriteLock lock = new ReentrantReadWriteLock();
57  
58      public SimpleStatistics( MathOperations<T> operations ) {
59          this.math = operations;
60          this.total = this.math.createZeroValue();
61          this.maximum = this.math.createZeroValue();
62          this.minimum = null;
63          this.mean = this.math.createZeroValue();
64          this.meanValue = 0.0d;
65      }
66  
67      /**
68       * Add a new value to these statistics.
69       * 
70       * @param value the new value
71       */
72      public void add( T value ) {
73          Lock lock = this.lock.writeLock();
74          try {
75              lock.lock();
76              doAddValue(value);
77          } finally {
78              lock.unlock();
79          }
80      }
81  
82      /**
83       * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
84       * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
85       * 
86       * @param value the value already added
87       */
88      protected void doAddValue( T value ) {
89          if (value == null) return;
90          // Modify the basic statistics ...
91          ++this.count;
92          this.total = math.add(this.total, value);
93          this.maximum = this.math.maximum(this.maximum, value);
94          this.minimum = this.math.minimum(this.minimum, value);
95          // Calculate the mean and standard deviation ...
96          int count = getCount();
97          if (count == 1) {
98              // M(1) = x(1)
99              this.meanValue = value.doubleValue();
100             this.mean = value;
101         } else {
102             double dValue = value.doubleValue();
103             double dCount = count;
104             // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
105             this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
106             this.mean = this.math.create(this.meanValue);
107         }
108     }
109 
110     /**
111      * Get the aggregate sum of the values in the series.
112      * 
113      * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
114      */
115     public T getTotal() {
116         Lock lock = this.lock.readLock();
117         lock.lock();
118         try {
119             return this.total;
120         } finally {
121             lock.unlock();
122         }
123     }
124 
125     /**
126      * Get the maximum value in the series.
127      * 
128      * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
129      */
130     public T getMaximum() {
131         Lock lock = this.lock.readLock();
132         lock.lock();
133         try {
134             return this.maximum;
135         } finally {
136             lock.unlock();
137         }
138     }
139 
140     /**
141      * Get the minimum value in the series.
142      * 
143      * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
144      */
145     public T getMinimum() {
146         Lock lock = this.lock.readLock();
147         lock.lock();
148         try {
149             return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
150         } finally {
151             lock.unlock();
152         }
153     }
154 
155     /**
156      * Get the number of values that have been measured.
157      * 
158      * @return the count
159      */
160     public int getCount() {
161         Lock lock = this.lock.readLock();
162         lock.lock();
163         try {
164             return this.count;
165         } finally {
166             lock.unlock();
167         }
168     }
169 
170     /**
171      * Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
172      * the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
173      * 
174      * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
175      */
176     public T getMean() {
177         Lock lock = this.lock.readLock();
178         lock.lock();
179         try {
180             return this.mean;
181         } finally {
182             lock.unlock();
183         }
184     }
185 
186     /**
187      * Return the mean (average) value.
188      * 
189      * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
190      * @see #getMean()
191      */
192     public double getMeanValue() {
193         Lock lock = this.lock.readLock();
194         lock.lock();
195         try {
196             return this.meanValue;
197         } finally {
198             lock.unlock();
199         }
200     }
201 
202     /**
203      * Reset the statistics in this object, and clear out any stored information.
204      */
205     public void reset() {
206         Lock lock = this.lock.writeLock();
207         lock.lock();
208         try {
209             doReset();
210         } finally {
211             lock.unlock();
212         }
213     }
214 
215     public MathOperations<T> getMathOperations() {
216         return math;
217     }
218 
219     protected ReadWriteLock getLock() {
220         return this.lock;
221     }
222 
223     /**
224      * Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
225      * locked for write and does work; therefore, the subclass should call this method.
226      */
227     protected void doReset() {
228         this.total = this.math.createZeroValue();
229         this.maximum = this.math.createZeroValue();
230         this.minimum = null;
231         this.mean = this.math.createZeroValue();
232         this.meanValue = 0.0d;
233         this.count = 0;
234     }
235 
236     @Override
237     public String toString() {
238         int count = this.getCount();
239         String samples = Inflector.getInstance().pluralize("sample", count);
240         return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}",
241                                        count,
242                                        samples,
243                                        this.minimum,
244                                        this.mean,
245                                        this.maximum);
246     }
247 
248 }