001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.common.stats;
023    
024    import java.util.concurrent.locks.Lock;
025    import java.util.concurrent.locks.ReadWriteLock;
026    import java.util.concurrent.locks.ReentrantReadWriteLock;
027    import net.jcip.annotations.ThreadSafe;
028    import org.jboss.dna.common.math.MathOperations;
029    import org.jboss.dna.common.text.Inflector;
030    import org.jboss.dna.common.util.StringUtil;
031    
032    /**
033     * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
034     * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and
035     * {@link #getMean() mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the
036     * {@link DetailedStatistics#getMedian() median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the
037     * {@link DetailedStatistics#getHistogram() histogram} of the values.
038     * <p>
039     * This class is threadsafe.
040     * </p>
041     * @param <T> the number type used in these statistics
042     * @author Randall Hauch
043     */
044    @ThreadSafe
045    public class SimpleStatistics<T extends Number> {
046    
047        protected final MathOperations<T> math;
048        private int count = 0;
049        private T total;
050        private T maximum;
051        private T minimum;
052        private T mean;
053        private Double meanValue;
054        private final ReadWriteLock lock = new ReentrantReadWriteLock();
055    
056        public SimpleStatistics( MathOperations<T> operations ) {
057            this.math = operations;
058            this.total = this.math.createZeroValue();
059            this.maximum = this.math.createZeroValue();
060            this.minimum = null;
061            this.mean = this.math.createZeroValue();
062            this.meanValue = 0.0d;
063        }
064    
065        /**
066         * Add a new value to these statistics.
067         * @param value the new value
068         */
069        public void add( T value ) {
070            Lock lock = this.lock.writeLock();
071            try {
072                lock.lock();
073                doAddValue(value);
074            } finally {
075                lock.unlock();
076            }
077        }
078    
079        /**
080         * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
081         * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
082         * @param value the value already added
083         */
084        protected void doAddValue( T value ) {
085            if (value == null) return;
086            // Modify the basic statistics ...
087            ++this.count;
088            this.total = math.add(this.total, value);
089            this.maximum = this.math.maximum(this.maximum, value);
090            this.minimum = this.math.minimum(this.minimum, value);
091            // Calculate the mean and standard deviation ...
092            int count = getCount();
093            if (count == 1) {
094                // M(1) = x(1)
095                this.meanValue = value.doubleValue();
096                this.mean = value;
097            } else {
098                double dValue = value.doubleValue();
099                double dCount = count;
100                // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
101                this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
102                this.mean = this.math.create(this.meanValue);
103            }
104        }
105    
106        /**
107         * Get the aggregate sum of the values in the series.
108         * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
109         */
110        public T getTotal() {
111            Lock lock = this.lock.readLock();
112            lock.lock();
113            try {
114                return this.total;
115            } finally {
116                lock.unlock();
117            }
118        }
119    
120        /**
121         * Get the maximum value in the series.
122         * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
123         */
124        public T getMaximum() {
125            Lock lock = this.lock.readLock();
126            lock.lock();
127            try {
128                return this.maximum;
129            } finally {
130                lock.unlock();
131            }
132        }
133    
134        /**
135         * Get the minimum value in the series.
136         * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
137         */
138        public T getMinimum() {
139            Lock lock = this.lock.readLock();
140            lock.lock();
141            try {
142                return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
143            } finally {
144                lock.unlock();
145            }
146        }
147    
148        /**
149         * Get the number of values that have been measured.
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         * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
166         */
167        public T getMean() {
168            Lock lock = this.lock.readLock();
169            lock.lock();
170            try {
171                return this.mean;
172            } finally {
173                lock.unlock();
174            }
175        }
176    
177        /**
178         * Return the mean (average) value.
179         * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
180         * @see #getMean()
181         */
182        public double getMeanValue() {
183            Lock lock = this.lock.readLock();
184            lock.lock();
185            try {
186                return this.meanValue;
187            } finally {
188                lock.unlock();
189            }
190        }
191    
192        /**
193         * Reset the statistics in this object, and clear out any stored information.
194         */
195        public void reset() {
196            Lock lock = this.lock.writeLock();
197            lock.lock();
198            try {
199                doReset();
200            } finally {
201                lock.unlock();
202            }
203        }
204    
205        public MathOperations<T> getMathOperations() {
206            return math;
207        }
208    
209        protected ReadWriteLock getLock() {
210            return this.lock;
211        }
212    
213        /**
214         * Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
215         * locked for write and does work; therefore, the subclass should call this method.
216         */
217        protected void doReset() {
218            this.total = this.math.createZeroValue();
219            this.maximum = this.math.createZeroValue();
220            this.minimum = null;
221            this.mean = this.math.createZeroValue();
222            this.meanValue = 0.0d;
223            this.count = 0;
224        }
225    
226        @Override
227        public String toString() {
228            int count = this.getCount();
229            String samples = Inflector.getInstance().pluralize("sample", count);
230            return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}", count, samples, this.minimum, this.mean, this.maximum);
231        }
232    
233    }