001 package org.hd.d.pg2k.svrCore.vars;
002
003 import java.text.DecimalFormat;
004 import java.text.DecimalFormatSymbols;
005 import java.text.FieldPosition;
006 import java.util.Iterator;
007 import java.util.Map;
008
009 import org.hd.d.pg2k.svrCore.I18NTools;
010
011 /**Immutable simple stats derived from a NUMBER globalMap variable.
012 * Has a toString() method directly usable on a JSP page.
013 * <p>
014 * Captures a reference to the value it was constructed from.
015 * <p>
016 * For most combination operators such as getSum(),
017 * values are manipulated as double (as risk of precision loss) to:
018 * <ul>
019 * <li>Have the maximum-possible dynamic range.
020 * <li>Represent integer and floating values.
021 * <li>Preserve special IEEE floating-point behaviour, such as for NaN.
022 * </ul>
023 */
024 public final class SimpleNumberStats extends SimpleVarStats
025 {
026 /**One-off initialisation of symbols for gallery-default locale; not null. */
027 private static final DecimalFormatSymbols symbols = new DecimalFormatSymbols(I18NTools.DEFAULT_SYSTEM_LOCALE);
028
029 /**Construct a new instance, filtering variable globalMap members by age.
030 * The Gallery-default locale is used.
031 *
032 * @param originalVar the variable to compute; must be non-null
033 * TYPE_NUMBER (and getValue() must return non-null value)
034 * @param oldestAllowed any globalMap entry than this will
035 * be ignored, which allows only up-to-date information
036 * to be included
037 * @param decimalFormatPattern numeric formatting string to
038 * be used for display of most quantities;
039 * null if toString() is not going to be used
040 * or String.valueOf() formatting is acceptable
041 * @param sysID identifies this system so we can select the local
042 * value of a global variable if available; can be null
043 */
044 public SimpleNumberStats(final SimpleVariableValue originalVar,
045 final long oldestAllowed,
046 final InstanceID sysID,
047 final String decimalFormatPattern)
048 {
049 super(originalVar, oldestAllowed, sysID);
050
051 // Post-hoc veto non-numeric or null values.
052 if((originalVar == null) ||
053 (originalVar.getDef().getType() != SimpleVariableDefinition.TYPE_NUMBER) ||
054 (originalVar.getValue() == null))
055 { throw new IllegalArgumentException("bad NUMBER argument"); }
056
057 pattern = decimalFormatPattern;
058
059 // Create the formatting object (at most) once to save time.
060 format = (null == pattern) ? null : new DecimalFormat(decimalFormatPattern, symbols);
061 }
062
063 /**The pattern String; null if toString() is not going to be used. */
064 private final String pattern;
065
066 /**Get the pattern String; null if toString() is not going to be used. */
067 public String getPattern()
068 {
069 return(pattern);
070 }
071
072 /**Our computed formatting object; never null.
073 * Not immutable, nor immutable, so must not be passed outside the class.
074 * <p>
075 * All access to this value is synchronized.
076 */
077 private final transient DecimalFormat format;
078
079 /**Gets the total of the non-null values.
080 * If there are no usable globalMap values,
081 * then this returns the main value, which may be null.
082 * <p>
083 * Will attempt to use integer computations for efficiency
084 * if all component values are Integer/Short/Byte,
085 * and the result fits in an int,
086 * else will promote sum/result to Long or Double.
087 */
088 public Number getSum()
089 {
090 assert(filteredVar != null);
091 final Map<InstanceID,SimpleVariableValue> globalMap = filteredVar.getGlobalMap();
092 if((globalMap == null) || (getNonNullValueCount() < 1))
093 { return((Number) filteredVar.getValue()); }
094
095 long lResult = 0;
096 final Iterator<SimpleVariableValue> it = globalMap.values().iterator();
097 while(it.hasNext())
098 {
099 final SimpleVariableValue svv = it.next();
100 final Number value = (Number) svv.getValue();
101 if(null == value) { continue; }
102 // We expect Integer to be the most common value type in use.
103 final Class<? extends Number> valueClass = value.getClass();
104 if((Integer.class != valueClass) && (Short.class != valueClass) && (Byte.class != valueClass))
105 {
106 // Convert sum so far to, and sum remainder as, double...
107 double dResult = ((double) lResult) + value.doubleValue();
108 while(it.hasNext())
109 {
110 final SimpleVariableValue svvD = it.next();
111 final Number valueD = (Number) svvD.getValue();
112 if(null == valueD) { continue; }
113 dResult += valueD.doubleValue();
114 }
115 return(Double.valueOf(dResult));
116 }
117
118 lResult += value.intValue();
119 }
120 if((lResult < Integer.MIN_VALUE) || (lResult > Integer.MAX_VALUE))
121 { return(Long.valueOf(lResult)); }
122 return(Integer.valueOf((int) lResult));
123 }
124
125 /**Gets the maximum of the non-null values.
126 * If there are no usable globalMap values,
127 * then this returns the main value, which may be null.
128 */
129 public Number getMax()
130 {
131 final Map<InstanceID,SimpleVariableValue> globalMap = filteredVar.getGlobalMap();
132 if((globalMap == null) || (getNonNullValueCount() < 1))
133 { return((Number) filteredVar.getValue()); }
134
135 Number best = null;
136 for(final SimpleVariableValue svv : globalMap.values())
137 {
138 final Number value = (Number) svv.getValue();
139 if(value != null)
140 {
141 if((best == null) ||
142 (value.doubleValue() > best.doubleValue()))
143 { best = value; }
144 }
145 }
146 return(best);
147 }
148
149 /**Gets the minimum of the non-null values.
150 * If there are no usable globalMap values,
151 * then this returns the main value, which may be null.
152 */
153 public Number getMin()
154 {
155 final Map<InstanceID,SimpleVariableValue> globalMap = filteredVar.getGlobalMap();
156 if((globalMap == null) || (getNonNullValueCount() < 1))
157 { return((Number) filteredVar.getValue()); }
158
159 Number best = null;
160 for(final SimpleVariableValue svv : globalMap.values())
161 {
162 final Number value = (Number) svv.getValue();
163 if(value != null)
164 {
165 if((best == null) ||
166 (value.doubleValue() < best.doubleValue()))
167 { best = value; }
168 }
169 }
170 return(best);
171 }
172
173 /**Format value and append to buffer.
174 * If using the format then thread-safety is ensured by synchronising on it.
175 */
176 private void fmt(final Object o, final StringBuffer sb)
177 {
178 if(null != format) { synchronized(format) { format.format(o, sb, new FieldPosition(0)); } }
179 else { sb.append(String.valueOf(o)); }
180 }
181
182 /**Returns HTML- and human- friendly summary of numerical stats.
183 * This does <em>not</em> include the variable name.
184 * <p>
185 * If the count is zero
186 * then this just returns a formatted version of the main value.
187 */
188 @Override
189 public String toString()
190 {
191 final int n = getNonNullValueCount();
192 final StringBuffer sb = new StringBuffer((n > 1) ? 64 : 32);
193
194 fmt(getMainValue(), sb);
195
196 if(n > 0)
197 {
198 sb.append(", n=").append(n);
199 if(n > 1)
200 {
201 // Order stats values from smallest to largest...
202 sb.append(", min="); fmt(getMin(), sb);
203 sb.append(", max="); fmt(getMax(), sb);
204 sb.append(", sum="); fmt(getSum(), sb);
205 }
206 }
207
208 return(sb.toString());
209 }
210 }