View Javadoc

1   // Copyright (C) 2000 - 2012 Philip Aston
2   // All rights reserved.
3   //
4   // This file is part of The Grinder software distribution. Refer to
5   // the file LICENSE which is part of The Grinder distribution for
6   // licensing details. The Grinder distribution is available on the
7   // Internet at http://grinder.sourceforge.net/
8   //
9   // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
13  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
20  // OF THE POSSIBILITY OF SUCH DAMAGE.
21  
22  package net.grinder.statistics;
23  
24  import java.io.IOException;
25  import java.io.ObjectInput;
26  import java.io.ObjectOutput;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.TreeMap;
30  import java.util.Map.Entry;
31  
32  import net.grinder.common.AbstractTestSemantics;
33  import net.grinder.common.Test;
34  
35  
36  /**
37   * A map of test numbers to {@link StatisticsSet}s.
38   *
39   * <p>Test statistics synchronisation occurs at the granularity of the
40   * contained {@link StatisticsSet} instances. The map is synchronised
41   * on the <code>TestStatisticsMap</code> itself.</p>
42   *
43   * @author Philip Aston
44   */
45  public final class TestStatisticsMap implements java.io.Externalizable {
46  
47    // The serialVersionUID should be incremented whenever the default
48    // statistic indices are changed in StatisticsIndexMap, or
49    // when the StatisticsSet externalisation methods are changed.
50    private static final long serialVersionUID = 5L;
51  
52    private final transient StatisticsSetFactory m_statisticsSetFactory;
53  
54    /**
55     * Use a TreeMap so we store in test number order. Synchronise on
56     * this TestStatisticsMap before accessing.
57     */
58    private final Map<Test, StatisticsSet> m_data =
59      new TreeMap<Test, StatisticsSet>();
60  
61    /**
62     * Creates a new <code>TestStatisticsMap</code> instance.
63     *
64     * @param statisticsSetFactory A factory used to build {@link StatisticsSet}s.
65     */
66    public TestStatisticsMap(StatisticsSetFactory statisticsSetFactory) {
67      m_statisticsSetFactory = statisticsSetFactory;
68    }
69  
70    /**
71     * Externalizable classes need a public default constructor.
72     */
73    public TestStatisticsMap() {
74      // No choice but to initialise the StatisticsSetFactory from a singleton.
75      // I hate Externalizable.
76      this(
77        StatisticsServicesImplementation.getInstance().getStatisticsSetFactory());
78    }
79  
80    /**
81     * Put a new {test, statistics} pair in the map.
82     *
83     * @param test A test.
84     * @param statistics The test's statistics.
85     */
86    public void put(Test test, StatisticsSet statistics) {
87      if (!(statistics instanceof StatisticsSetImplementation)) {
88        throw new AssertionError(
89          "StatisticsSet implementation not supported");
90      }
91  
92      synchronized (this) {
93        m_data.put(test, statistics);
94      }
95    }
96  
97    /**
98     * Return the number of entries in the
99     * <code>TestStatisticsMap</code>.
100    *
101    * @return an <code>int</code> value
102    */
103   public int size() {
104     synchronized (this) {
105       return m_data.size();
106     }
107   }
108 
109   /**
110    * Add the values in another <code>TestStatisticsMap</code> to this
111    * <code>TestStatisticsMap</code>.
112    *
113    * @param other The other <code>TestStatisticsMap</code>.
114    */
115   public void add(TestStatisticsMap other) {
116     synchronized (other) {
117       for (Entry<Test, StatisticsSet> othersEntry : other.m_data.entrySet()) {
118         final StatisticsSet statistics;
119 
120         synchronized (this) {
121           final StatisticsSet existingStatistics =
122             m_data.get(othersEntry.getKey());
123 
124           if (existingStatistics == null) {
125             statistics = m_statisticsSetFactory.create();
126             put(othersEntry.getKey(), statistics);
127           }
128           else {
129             statistics = existingStatistics;
130           }
131         }
132 
133         statistics.add(othersEntry.getValue());
134       }
135     }
136   }
137 
138   /**
139    * Reset all our statistics and return a snapshot.
140    *
141    * @return The snapshot. Only Tests with non-zero statistics are included.
142    */
143   public TestStatisticsMap reset() {
144     final TestStatisticsMap result =
145       new TestStatisticsMap(m_statisticsSetFactory);
146 
147     new ForEach() {
148       public void next(Test test, StatisticsSet statistics) {
149         final StatisticsSet snapshot;
150 
151         synchronized (statistics) {
152           snapshot = statistics.snapshot();
153           statistics.reset();
154         }
155 
156         if (!snapshot.isZero()) {
157           result.put(test, snapshot);
158         }
159       }
160     }
161     .iterate();
162 
163     return result;
164   }
165 
166   /**
167    * Add up all the non-composite statistics.
168    *
169    * @return The sum of all the non-composite statistics.
170    */
171   public StatisticsSet nonCompositeStatisticsTotals() {
172     final StatisticsSet result = m_statisticsSetFactory.create();
173 
174     new ForEach() {
175       public void next(Test test, StatisticsSet statistics) {
176         if (!statistics.isComposite()) {
177           result.add(statistics);
178         }
179       }
180     }
181     .iterate();
182 
183     return result;
184   }
185 
186   /**
187    * Add up all the composite statistics.
188    *
189    * @return The sum of all the composite statistics.
190    */
191   public StatisticsSet compositeStatisticsTotals() {
192     final StatisticsSet result = m_statisticsSetFactory.create();
193 
194     new ForEach() {
195       public void next(Test test, StatisticsSet statistics) {
196         if (statistics.isComposite()) {
197           result.add(statistics);
198         }
199       }
200     }
201     .iterate();
202 
203     return result;
204   }
205 
206   /**
207    * Implement value based equality. Used by unit tests, so we don't
208    * bother with synchronisation.
209    *
210    * @param o <code>Object</code> to compare to.
211    * @return <code>true</code> if and only if the two objects are equal.
212    */
213   public boolean equals(Object o) {
214     if (o == this) {
215       return true;
216     }
217 
218     if (o == null || o.getClass() != TestStatisticsMap.class) {
219       return false;
220     }
221 
222     final TestStatisticsMap otherMap = (TestStatisticsMap)o;
223 
224     if (m_data.size() != otherMap.m_data.size()) {
225       return false;
226     }
227 
228     final Iterator<Entry<Test, StatisticsSet>> iterator =
229       m_data.entrySet().iterator();
230     final Iterator<Entry<Test, StatisticsSet>> otherIterator =
231       otherMap.m_data.entrySet().iterator();
232 
233     while (iterator.hasNext()) {
234       final Entry<Test, StatisticsSet> entry = iterator.next();
235       final Entry<Test, StatisticsSet> otherEntry = otherIterator.next();
236 
237       if (!entry.getKey().equals(otherEntry.getKey()) ||
238           !entry.getValue().equals(otherEntry.getValue())) {
239         return false;
240       }
241     }
242 
243     return true;
244   }
245 
246   /**
247    * Defer to <code>Object.hashCode()</code>.
248    *
249    * <p>We define <code>hashCode</code> to keep Checkstyle happy, but
250    * we don't use it.
251    *
252    * @return The hash code.
253    */
254   public int hashCode() {
255     return super.hashCode();
256   }
257 
258   /**
259    * Return a <code>String</code> representation of this
260    * <code>TestStatisticsMap</code>.
261    *
262    * @return The <code>String</code>
263    */
264   public String toString() {
265     final StringBuilder result = new StringBuilder();
266 
267     result.append("TestStatisticsMap = {");
268 
269     new ForEach() {
270       public void next(Test test, StatisticsSet statisticsSet) {
271         result.append("(");
272         result.append(test);
273         result.append(", ");
274         result.append(statisticsSet);
275         result.append(")");
276       }
277     }
278     .iterate();
279 
280     result.append("}");
281 
282     return result.toString();
283   }
284 
285   /**
286    * Efficient externalisation method.
287    *
288    * @param out Handle to the output stream.
289    * @exception IOException If an I/O error occurs.
290    */
291   public void writeExternal(ObjectOutput out) throws IOException {
292 
293     synchronized (this) {
294       out.writeInt(m_data.size());
295 
296       for (Entry<Test, StatisticsSet> entry : m_data.entrySet()) {
297         out.writeInt(entry.getKey().getNumber());
298 
299         // Its a class invariant that our StatisticsSets are all
300         // StatisticsSetImplementations.
301         m_statisticsSetFactory.writeStatisticsExternal(
302           out, (StatisticsSetImplementation)entry.getValue());
303       }
304     }
305   }
306 
307   /**
308    * Efficient externalisation method. No synchronisation, assume that
309    * we're being read into a new instance.
310    *
311    * @param in Handle to the input stream.
312    * @exception IOException If an I/O error occurs.
313    */
314   public void readExternal(ObjectInput in) throws IOException {
315 
316     final int n = in.readInt();
317 
318     m_data.clear();
319 
320     for (int i = 0; i < n; i++) {
321       m_data.put(new LightweightTest(in.readInt()),
322                  m_statisticsSetFactory.readStatisticsExternal(in));
323     }
324   }
325 
326   /**
327    * Light weight test implementation that the console uses.
328    */
329   private static final class LightweightTest extends AbstractTestSemantics {
330     private final int m_number;
331 
332     public LightweightTest(int number) {
333       m_number = number;
334     }
335 
336     public int getNumber() {
337       return m_number;
338     }
339 
340     public String getDescription() {
341       return "";
342     }
343   }
344 
345   /**
346    * Convenient visitor-like iteration.
347    */
348   public abstract class ForEach {
349     /**
350      * Runs the iteration.
351      */
352     public void iterate() {
353       synchronized (TestStatisticsMap.this) {
354         for (Entry<Test, StatisticsSet> entry : m_data.entrySet()) {
355           next(entry.getKey(), entry.getValue());
356         }
357       }
358     }
359 
360     /**
361      * Receives a call for each item in the iteration.
362      *
363      * @param test The item's Test.
364      * @param statistics The item's statistics.
365      */
366     protected abstract void next(Test test, StatisticsSet statistics);
367   }
368 }