1 /**
2 * Licensed under the Artistic License; you may not use this file
3 * except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://displaytag.sourceforge.net/license.html
7 *
8 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12 package org.displaytag.decorator;
13
14 import java.text.MessageFormat;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19
20 import javax.servlet.jsp.PageContext;
21
22 import org.apache.commons.lang.ObjectUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.displaytag.exception.DecoratorException;
27 import org.displaytag.model.HeaderCell;
28 import org.displaytag.model.TableModel;
29
30
31 /**
32 * A table decorator which adds rows with totals (for column with the "total" attribute set) and subtotals (grouping by
33 * the column with a group="1" attribute).
34 * @author Fabrizio Giustina
35 * @version $Id$
36 */
37 public class TotalTableDecorator extends TableDecorator
38 {
39
40 /**
41 * Logger.
42 */
43 private static Log log = LogFactory.getLog(TotalTableDecorator.class);
44
45 /**
46 * total amount.
47 */
48 private Map grandTotals = new HashMap();
49
50 /**
51 * total amount for current group.
52 */
53 private Map subTotals = new HashMap();
54
55 /**
56 * Previous values needed for grouping.
57 */
58 private Map previousValues = new HashMap();
59
60 /**
61 * Name of the property used for grouping.
62 */
63 private String groupPropertyName;
64
65 /**
66 * Label used for subtotals. Default: "{0} total".
67 */
68 private String subtotalLabel = "{0} subtotal";
69
70 /**
71 * Label used for totals. Default: "Total".
72 */
73 private String totalLabel = "Total";
74
75 /**
76 * Setter for <code>subtotalLabel</code>.
77 * @param subtotalLabel The subtotalLabel to set.
78 */
79 public void setSubtotalLabel(String subtotalLabel)
80 {
81 this.subtotalLabel = subtotalLabel;
82 }
83
84 /**
85 * Setter for <code>totalLabel</code>.
86 * @param totalLabel The totalLabel to set.
87 */
88 public void setTotalLabel(String totalLabel)
89 {
90 this.totalLabel = totalLabel;
91 }
92
93 /**
94 * @see org.displaytag.decorator.Decorator#init(PageContext, Object, TableModel)
95 */
96 public void init(PageContext context, Object decorated, TableModel tableModel)
97 {
98 super.init(context, decorated, tableModel);
99
100 // reset
101 groupPropertyName = null;
102 grandTotals.clear();
103 subTotals.clear();
104 previousValues.clear();
105
106 for (Iterator it = tableModel.getHeaderCellList().iterator(); it.hasNext();)
107 {
108 HeaderCell cell = (HeaderCell) it.next();
109 if (cell.getGroup() == 1)
110 {
111 groupPropertyName = cell.getBeanPropertyName();
112 }
113 }
114 }
115
116 public String startRow()
117 {
118 String subtotalRow = null;
119
120 if (groupPropertyName != null)
121 {
122 Object groupedPropertyValue = evaluate(groupPropertyName);
123 Object previousGroupedPropertyValue = previousValues.get(groupPropertyName);
124 // subtotals
125 if (previousGroupedPropertyValue != null
126 && !ObjectUtils.equals(previousGroupedPropertyValue, groupedPropertyValue))
127 {
128 subtotalRow = createTotalRow(false);
129 }
130 previousValues.put(groupPropertyName, groupedPropertyValue);
131 }
132
133 for (Iterator it = tableModel.getHeaderCellList().iterator(); it.hasNext();)
134 {
135 HeaderCell cell = (HeaderCell) it.next();
136 if (cell.isTotaled())
137 {
138 String totalPropertyName = cell.getBeanPropertyName();
139 Number amount = (Number) evaluate(totalPropertyName);
140
141 Number previousSubTotal = (Number) subTotals.get(totalPropertyName);
142 Number previousGrandTotals = (Number) grandTotals.get(totalPropertyName);
143
144 subTotals.put(totalPropertyName, new Double((previousSubTotal != null
145 ? previousSubTotal.doubleValue()
146 : 0)
147 + (amount != null ? amount.doubleValue() : 0)));
148
149 grandTotals.put(totalPropertyName, new Double((previousGrandTotals != null ? previousGrandTotals
150 .doubleValue() : 0)
151 + (amount != null ? amount.doubleValue() : 0)));
152 }
153 }
154
155 return subtotalRow;
156 }
157
158 /**
159 * After every row completes we evaluate to see if we should be drawing a new total line and summing the results
160 * from the previous group.
161 * @return String
162 */
163 public final String finishRow()
164 {
165 StringBuffer buffer = new StringBuffer(1000);
166
167 // Grand totals...
168 if (getViewIndex() == ((List) getDecoratedObject()).size() - 1)
169 {
170 if (groupPropertyName != null)
171 {
172 buffer.append(createTotalRow(false));
173 }
174 buffer.append(createTotalRow(true));
175 }
176 return buffer.toString();
177
178 }
179
180 protected String createTotalRow(boolean grandTotal)
181 {
182 StringBuffer buffer = new StringBuffer(1000);
183 buffer.append("\n<tr class=\"total\">"); //$NON-NLS-1$
184
185 List headerCells = tableModel.getHeaderCellList();
186
187 for (Iterator it = headerCells.iterator(); it.hasNext();)
188 {
189 HeaderCell cell = (HeaderCell) it.next();
190 String cssClass = ObjectUtils.toString(cell.getHtmlAttributes().get("class"));
191
192 buffer.append("<td"); //$NON-NLS-1$
193 if (StringUtils.isNotEmpty(cssClass))
194 {
195 buffer.append(" class=\""); //$NON-NLS-1$
196 buffer.append(cssClass);
197 buffer.append("\""); //$NON-NLS-1$
198 }
199 buffer.append(">"); //$NON-NLS-1$
200
201 if (cell.isTotaled())
202 {
203 String totalPropertyName = cell.getBeanPropertyName();
204 Object total = grandTotal ? grandTotals.get(totalPropertyName) : subTotals.get(totalPropertyName);
205
206 DisplaytagColumnDecorator[] decorators = cell.getColumnDecorators();
207 for (int j = 0; j < decorators.length; j++)
208 {
209 try
210 {
211 total = decorators[j].decorate(total, this.getPageContext(), tableModel.getMedia());
212 }
213 catch (DecoratorException e)
214 {
215 log.warn(e.getMessage(), e);
216 // ignore, use undecorated value for totals
217 }
218 }
219 buffer.append(total);
220 }
221 else if (groupPropertyName != null && groupPropertyName.equals(cell.getBeanPropertyName()))
222 {
223 buffer.append(grandTotal ? totalLabel : MessageFormat.format(subtotalLabel, new Object[]{previousValues
224 .get(groupPropertyName)}));
225 }
226
227 buffer.append("</td>"); //$NON-NLS-1$
228
229 }
230
231 buffer.append("</tr>"); //$NON-NLS-1$
232
233 // reset subtotal
234 this.subTotals.clear();
235
236 return buffer.toString();
237 }
238
239 }