KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jimm > datavision > field > AggregateField


1 package jimm.datavision.field;
2 import jimm.datavision.*;
3 import java.util.*;
4
5 interface AggregateFunction {
6 public double aggregate(double[] values, int numValues);
7 }
8
9 /**
10  * An aggregate field represents a field's aggregated values, either {@link
11  * ColumnField} or {@link FormulaField}. It also may be associated with a
12  * group (assigned in {@link ReportReader#field} or when editing a report),
13  * meaning that the aggregate value is reset whenever the group's value
14  * changes. The value of an aggregate field holds the id of some other field
15  * whose value we are aggregating.
16  *
17  * @author Jim Menard, <a HREF="mailto:jimm@io.com">jimm@io.com</a>
18  */

19 public class AggregateField extends Field {
20
21 protected static final int START_VALUES_LENGTH = 100;
22
23 /** Maps function names to {@link AggregateFunction} objects. */
24 protected static HashMap functions;
25 /** A sorted array of the function names. */
26 protected static Object JavaDoc[] functionNames;
27
28 // Initialize map from function names to functions.
29
static {
30     functions = new HashMap();
31     functions.put("sum", new AggregateFunction() {
32     public double aggregate(double[] values, int numValues) {
33         double total = 0;
34         for (int i = 0; i < numValues; ++i) total += values[i];
35         return total;
36     }
37     });
38     functions.put("subtotal", functions.get("sum")); // Old name for "sum"
39
functions.put("min", new AggregateFunction() {
40     public double aggregate(double[] values, int numValues) {
41         double min = Double.MAX_VALUE;
42         for (int i = 0; i < numValues; ++i)
43         if (values[i] < min) min = values[i];
44         return min;
45     }
46     });
47     functions.put("max", new AggregateFunction() {
48     public double aggregate(double[] values, int numValues) {
49         double max = Double.MIN_VALUE;
50         for (int i = 0; i < numValues; ++i)
51         if (values[i] > max) max = values[i];
52         return max;
53     }
54     });
55     functions.put("count", new AggregateFunction() {
56     public double aggregate(double[] values, int numValues) {
57         return numValues;
58     }
59     });
60     functions.put("average", new AggregateFunction() {
61     public double aggregate(double[] values, int numValues) {
62         if (numValues == 0)
63         return 0;
64         double total = 0;
65         for (int i = 0; i < numValues; ++i) total += values[i];
66         return total / numValues;
67     }
68     });
69     functions.put("stddev", new AggregateFunction() {
70     public double aggregate(double[] values, int numValues) {
71         if (numValues < 2)
72         return 0;
73         double average = ((AggregateFunction)functions.get("average"))
74         .aggregate(values, numValues);
75         double sumOfSquares = 0;
76         for (int i = 0; i < numValues; ++i)
77         sumOfSquares += (values[i] - average) * (values[i] - average);
78         return Math.sqrt(sumOfSquares / (numValues - 1));
79     }
80     });
81
82     // Create a sorted list of function names. Don't include "select", which
83
// is the old name for "sum".
84
TreeSet withoutSelect = new TreeSet(functions.keySet());
85     withoutSelect.remove("select");
86     functionNames = withoutSelect.toArray();
87 }
88
89 protected Group group; // Set by report creation; possibly null
90
protected String JavaDoc functionName;
91 protected AggregateFunction function;
92 protected double[] values; // Read-only
93
protected int valuesIndex;
94 protected Field fieldToAggregate;
95
96 /**
97  * Returns <code>true</code> if <var>functionName</var> is a legal aggregate
98  * function name.
99  *
100  * @param functionName an aggregate function name (hopefully)
101  * @return <code>true</code> if it's a function name
102  */

103 public static boolean isAggregateFunctionName(String JavaDoc functionName) {
104     return functions.keySet().contains(functionName);
105 }
106
107 /**
108  * Returns the list of function names as an array of objects.
109  *
110  * @return all possible function names
111  */

112 public static Object JavaDoc[] functionNameArray() {
113     return functionNames;
114 }
115
116 /**
117  * Constructs a field with the specified id in the specified report
118  * section that aggregates the field with id <i>value</i>.
119  *
120  * @param id the new field's id
121  * @param report the report containing this element
122  * @param section the report section in which the field resides
123  * @param value the magic string
124  * @param visible show/hide flag
125  * @param functionName "xxx", where xxx is the aggregate function
126  */

127 public AggregateField(Long JavaDoc id, Report report, Section section, Object JavaDoc value,
128              boolean visible, String JavaDoc functionName)
129 {
130     super(id, report, section, value, visible);
131     values = null;
132
133     setFunction(functionName);
134
135     // The reason I don't grab fieldToAggregate right now is that this
136
// aggregate field may be constructed before the field to which it
137
// refers.
138
}
139
140 protected void finalize() throws Throwable JavaDoc {
141     if (fieldToAggregate != null)
142     fieldToAggregate.deleteObserver(this);
143     super.finalize();
144 }
145
146 public String JavaDoc getFunction() { return functionName; }
147 public void setFunction(String JavaDoc newFunctionName) {
148     newFunctionName = newFunctionName.toLowerCase();
149     if (functionName != newFunctionName &&
150     (functionName == null || !functionName.equals(newFunctionName)))
151     {
152     functionName = newFunctionName;
153     function = (AggregateFunction)functions.get(functionName);
154     setChanged();
155     notifyObservers();
156     }
157 }
158
159 /**
160  * Resets this aggregate. Called by the report once at the beginning of
161  * each run.
162  */

163 public void initialize() {
164     values = null;
165 }
166
167 public String JavaDoc dragString() {
168     return typeString() + ":" + getField().getId();
169 }
170
171 /**
172  * Returns the group over which this field is aggregating. May return
173  * <code>null</code> since not all aggregate fields are associated with
174  * a group.
175  *
176  * @return a group; <code>null</code> if this aggregate field is not
177  * associated with any group
178  */

179 public Group getGroup() { return group; }
180
181 /**
182  * Sets the group this field is aggregate. The group may be
183  * <code>null</code>.
184  *
185  * @param newGroup a group
186  */

187 public void setGroup(Group newGroup) {
188     if (group != newGroup) {
189     group = newGroup;
190     setChanged();
191     notifyObservers();
192     }
193 }
194
195 /**
196  * Returns the field over which we are aggregating. We lazily instantiate
197  * it because when we are constructed that field may not yet exist. We also
198  * start observing the field so we can notify our observers in turn.
199  *
200  * @return the field we are aggregating
201  */

202 public Field getField() {
203     if (fieldToAggregate == null) {
204     fieldToAggregate = getReport().findField(value);
205     fieldToAggregate.addObserver(this);
206     }
207     return fieldToAggregate;
208 }
209
210 /**
211  * Returns the id of the field over which we are aggregating. The field
212  * itself may not yet exist.
213  *
214  * @return the id of the field we are aggregating
215  */

216 public Long JavaDoc getFieldId() {
217     return (value instanceof Long JavaDoc) ? (Long JavaDoc)value : new Long JavaDoc(value.toString());
218 }
219
220 /**
221  * Returns the current aggregate value.
222  *
223  * @return a doubleing point total
224  */

225 public double getAggregateValue() {
226     if (function == null)
227     return 0;
228     return function.aggregate(values, valuesIndex);
229 }
230
231 public String JavaDoc typeString() { return functionName; }
232
233 public String JavaDoc designLabel() {
234     return functionName + "(" + getField().designLabel() + ")";
235 }
236
237 public String JavaDoc formulaString() { return designLabel(); }
238
239 public boolean refersTo(Field f) {
240     return getField() == f;
241 }
242
243 public boolean refersTo(Formula f) {
244     return (getField() instanceof FormulaField)
245     && ((FormulaField)getField()).getFormula() == f;
246 }
247
248 public boolean refersTo(UserColumn uc) {
249     return (getField() instanceof UserColumnField)
250     && ((UserColumnField)getField()).getUserColumn() == uc;
251 }
252
253 public boolean refersTo(Parameter p) {
254     if ((getField() instanceof ParameterField)
255     && ((ParameterField)getField()).getParameter() == p)
256     return true;
257
258     if ((getField() instanceof FormulaField)
259     && ((FormulaField)getField()).refersTo(p))
260     return true;
261
262     return false;
263 }
264
265 public boolean canBeAggregated() {
266     return true;
267 }
268
269 /**
270  * Updates the aggregate value. Called by the report when a new
271  * line of data is retrieved.
272  */

273 public void updateAggregate() {
274     /*
275      * Our value field holds the id of some other field. Get that field's
276      * value, then convert it to a double.
277      */

278     Object JavaDoc obj = getField().getValue();
279     double value = 0;
280     if (obj != null) {
281     if (obj instanceof Number JavaDoc)
282         value = ((Number JavaDoc)obj).doubleValue();
283     else
284         value = Double.parseDouble(obj.toString());
285     }
286
287     // If we are aggregating within a group and this is a new value,
288
// reset the aggregate value. If we have not yet collected any
289
// values, allocate space.
290
if (values == null || (group != null && group.isNewValue())) {
291     values = new double[START_VALUES_LENGTH];
292     valuesIndex = 0;
293     }
294     else if (valuesIndex == values.length) { // Expand the values array
295
double[] newValues = new double[values.length * 2];
296     System.arraycopy(values, 0, newValues, 0, values.length);
297     values = newValues;
298     }
299     values[valuesIndex++] = value;
300 }
301
302 /**
303  * Returns the value of this field: the aggregate as a Double.
304  *
305  * @return a Double
306  */

307 public Object JavaDoc getValue() { return new Double JavaDoc(getAggregateValue()); }
308
309 }
310
Popular Tags