001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.common.statistic;
025    
026    import java.util.concurrent.locks.Lock;
027    import java.util.concurrent.locks.ReadWriteLock;
028    import java.util.concurrent.locks.ReentrantReadWriteLock;
029    import net.jcip.annotations.ThreadSafe;
030    import org.jboss.dna.common.math.MathOperations;
031    import org.jboss.dna.common.text.Inflector;
032    import org.jboss.dna.common.util.StringUtil;
033    
034    /**
035     * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
036     * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and
037     * {@link #getMean() mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the
038     * {@link DetailedStatistics#getMedian() median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the
039     * {@link DetailedStatistics#getHistogram() histogram} of the values.
040     * <p>
041     * This class is threadsafe.
042     * </p>
043     * @param <T> the number type used in these statistics
044     * @author Randall Hauch
045     */
046    @ThreadSafe
047    public class SimpleStatistics<T extends Number> {
048    
049        protected final MathOperations<T> math;
050        private int count = 0;
051        private T total;
052        private T maximum;
053        private T minimum;
054        private T mean;
055        private Double meanValue;
056        private final ReadWriteLock lock = new ReentrantReadWriteLock();
057    
058        public SimpleStatistics( MathOperations<T> operations ) {
059            this.math = operations;
060            this.total = this.math.createZeroValue();
061            this.maximum = this.math.createZeroValue();
062            this.minimum = null;
063            this.mean = this.math.createZeroValue();
064            this.meanValue = 0.0d;
065        }
066    
067        /**
068         * Add a new value to these statistics.
069         * @param value the new value
070         */
071        public void add( T value ) {
072            Lock lock = this.lock.writeLock();
073            try {
074                lock.lock();
075                doAddValue(value);
076            } finally {
077                lock.unlock();
078            }
079        }
080    
081        /**
082         * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
083         * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
084         * @param value the value already added
085         */
086        protected void doAddValue( T value ) {
087            if (value == null) return;
088            // Modify the basic statistics ...
089            ++this.count;
090            this.total = math.add(this.total, value);
091            this.maximum = this.math.maximum(this.maximum, value);
092            this.minimum = this.math.minimum(this.minimum, value);
093            // Calculate the mean and standard deviation ...
094            int count = getCount();
095            if (count == 1) {
096                // M(1) = x(1)
097                this.meanValue = value.doubleValue();
098                this.mean = value;
099            } else {
100                double dValue = value.doubleValue();
101                double dCount = count;
102                // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
103                this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
104                this.mean = this.math.create(this.meanValue);
105            }
106        }
107    
108        /**
109         * Get the aggregate sum of the values in the series.
110         * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
111         */
112        public T getTotal() {
113            Lock lock = this.lock.readLock();
114            lock.lock();
115            try {
116                return this.total;
117            } finally {
118                lock.unlock();
119            }
120        }
121    
122        /**
123         * Get the maximum value in the series.
124         * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
125         */
126        public T getMaximum() {
127            Lock lock = this.lock.readLock();
128            lock.lock();
129            try {
130                return this.maximum;
131            } finally {
132                lock.unlock();
133            }
134        }
135    
136        /**
137         * Get the minimum value in the series.
138         * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
139         */
140        public T getMinimum() {
141            Lock lock = this.lock.readLock();
142            lock.lock();
143            try {
144                return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
145            } finally {
146                lock.unlock();
147            }
148        }
149    
150        /**
151         * Get the number of values that have been measured.
152         * @return the count
153         */
154        public int getCount() {
155            Lock lock = this.lock.readLock();
156            lock.lock();
157            try {
158                return this.count;
159            } finally {
160                lock.unlock();
161            }
162        }
163    
164        /**
165         * Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
166         * the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
167         * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
168         */
169        public T getMean() {
170            Lock lock = this.lock.readLock();
171            lock.lock();
172            try {
173                return this.mean;
174            } finally {
175                lock.unlock();
176            }
177        }
178    
179        /**
180         * Return the mean (average) value.
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}", count, samples, this.minimum, this.mean, this.maximum);
233        }
234    
235    }