• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / displaytag / 1753

12 Feb 2026 03:17AM UTC coverage: 77.321% (-0.01%) from 77.334%
1753

push

github

web-flow
Merge pull request #1102 from hazendaz/renovate/javax-support-logback-monorepo

Update dependency ch.qos.logback:logback-classic to v1.5.29 (javax-support)

1438 of 2003 branches covered (71.79%)

Branch coverage included in aggregate %.

4034 of 5074 relevant lines covered (79.5%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.6
/displaytag/src/main/java/org/displaytag/decorator/TotalTableDecorator.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2002-2026 Fabrizio Giustina, the Displaytag team
6
 */
7
package org.displaytag.decorator;
8

9
import java.text.MessageFormat;
10
import java.util.HashMap;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.Objects;
14

15
import javax.servlet.jsp.PageContext;
16

17
import org.apache.commons.lang3.StringUtils;
18
import org.displaytag.exception.DecoratorException;
19
import org.displaytag.model.HeaderCell;
20
import org.displaytag.model.TableModel;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23

24
/**
25
 * A table decorator which adds rows with totals (for column with the "total" attribute set) and subtotals (grouping by
26
 * the column with a group="1" attribute).
27
 */
28
public class TotalTableDecorator extends TableDecorator {
1✔
29

30
    /**
31
     * Logger.
32
     */
33
    private static Logger log = LoggerFactory.getLogger(TotalTableDecorator.class);
1✔
34

35
    /**
36
     * total amount.
37
     */
38
    private final Map<String, Double> grandTotals = new HashMap<>();
1✔
39

40
    /**
41
     * total amount for current group.
42
     */
43
    private final Map<String, Double> subTotals = new HashMap<>();
1✔
44

45
    /**
46
     * Previous values needed for grouping.
47
     */
48
    private final Map<String, Object> previousValues = new HashMap<>();
1✔
49

50
    /**
51
     * Name of the property used for grouping.
52
     */
53
    private String groupPropertyName;
54

55
    /**
56
     * Label used for subtotals. Default: "{0} total".
57
     */
58
    private String subtotalLabel = "{0} subtotal";
1✔
59

60
    /**
61
     * Label used for totals. Default: "Total".
62
     */
63
    private String totalLabel = "Total";
1✔
64

65
    /**
66
     * Setter for <code>subtotalLabel</code>.
67
     *
68
     * @param subtotalLabel
69
     *            The subtotalLabel to set.
70
     */
71
    public void setSubtotalLabel(final String subtotalLabel) {
72
        this.subtotalLabel = subtotalLabel;
×
73
    }
×
74

75
    /**
76
     * Setter for <code>totalLabel</code>.
77
     *
78
     * @param totalLabel
79
     *            The totalLabel to set.
80
     */
81
    public void setTotalLabel(final String totalLabel) {
82
        this.totalLabel = totalLabel;
×
83
    }
×
84

85
    /**
86
     * Inits the.
87
     *
88
     * @param context
89
     *            the context
90
     * @param decorated
91
     *            the decorated
92
     * @param tableModel
93
     *            the table model
94
     *
95
     * @see org.displaytag.decorator.Decorator#init(PageContext, Object, TableModel)
96
     */
97
    @Override
98
    public void init(final PageContext context, final Object decorated, final TableModel tableModel) {
99
        super.init(context, decorated, tableModel);
1✔
100

101
        // reset
102
        this.groupPropertyName = null;
1✔
103
        this.grandTotals.clear();
1✔
104
        this.subTotals.clear();
1✔
105
        this.previousValues.clear();
1✔
106

107
        for (final HeaderCell cell : tableModel.getHeaderCellList()) {
1✔
108
            if (cell.getGroup() == 1) {
1!
109
                this.groupPropertyName = cell.getBeanPropertyName();
×
110
            }
111
        }
1✔
112
    }
1✔
113

114
    /**
115
     * Start row.
116
     *
117
     * @return the string
118
     */
119
    @Override
120
    public String startRow() {
121
        String subtotalRow = null;
1✔
122

123
        if (this.groupPropertyName != null) {
1!
124
            final Object groupedPropertyValue = this.evaluate(this.groupPropertyName);
×
125
            final Object previousGroupedPropertyValue = this.previousValues.get(this.groupPropertyName);
×
126
            // subtotals
127
            if (previousGroupedPropertyValue != null
×
128
                    && !Objects.equals(previousGroupedPropertyValue, groupedPropertyValue)) {
×
129
                subtotalRow = this.createTotalRow(false);
×
130
            }
131
            this.previousValues.put(this.groupPropertyName, groupedPropertyValue);
×
132
        }
133

134
        for (final HeaderCell cell : this.tableModel.getHeaderCellList()) {
1✔
135
            if (cell.isTotaled()) {
1!
136
                final String totalPropertyName = cell.getBeanPropertyName();
1✔
137
                final Number amount = (Number) this.evaluate(totalPropertyName);
1✔
138

139
                final Number previousSubTotal = this.subTotals.get(totalPropertyName);
1✔
140
                final Number previousGrandTotals = this.grandTotals.get(totalPropertyName);
1✔
141

142
                this.subTotals.put(totalPropertyName,
1✔
143
                        Double.valueOf((previousSubTotal != null ? previousSubTotal.doubleValue() : 0)
1✔
144
                                + (amount != null ? amount.doubleValue() : 0)));
1!
145

146
                this.grandTotals.put(totalPropertyName,
1✔
147
                        Double.valueOf((previousGrandTotals != null ? previousGrandTotals.doubleValue() : 0)
1✔
148
                                + (amount != null ? amount.doubleValue() : 0)));
1!
149
            }
150
        }
1✔
151

152
        return subtotalRow;
1✔
153
    }
154

155
    /**
156
     * After every row completes we evaluate to see if we should be drawing a new total line and summing the results
157
     * from the previous group.
158
     *
159
     * @return String
160
     */
161
    @Override
162
    public final String finishRow() {
163
        final StringBuilder buffer = new StringBuilder(1000);
1✔
164

165
        // Grand totals...
166
        if (this.getViewIndex() == ((List<Object>) this.getDecoratedObject()).size() - 1) {
1✔
167
            if (this.groupPropertyName != null) {
1!
168
                buffer.append(this.createTotalRow(false));
×
169
            }
170
            buffer.append(this.createTotalRow(true));
1✔
171
        }
172
        return buffer.toString();
1✔
173

174
    }
175

176
    /**
177
     * Creates the total row.
178
     *
179
     * @param grandTotal
180
     *            the grand total
181
     *
182
     * @return the string
183
     */
184
    protected String createTotalRow(final boolean grandTotal) {
185
        final StringBuilder buffer = new StringBuilder(1000);
1✔
186
        buffer.append("\n<tr class=\"total\">"); //$NON-NLS-1$
1✔
187

188
        final List<HeaderCell> headerCells = this.tableModel.getHeaderCellList();
1✔
189

190
        for (final HeaderCell cell : headerCells) {
1✔
191
            final Object cssClassObj = cell.getHtmlAttributes().get("class");
1✔
192
            final String cssClass = cssClassObj != null ? cssClassObj.toString() : StringUtils.EMPTY;
1!
193

194
            buffer.append("<td"); //$NON-NLS-1$
1✔
195
            if (StringUtils.isNotEmpty(cssClass)) {
1!
196
                buffer.append(" class=\""); //$NON-NLS-1$
×
197
                buffer.append(cssClass);
×
198
                buffer.append("\""); //$NON-NLS-1$
×
199
            }
200
            buffer.append(">"); //$NON-NLS-1$
1✔
201

202
            if (cell.isTotaled()) {
1!
203
                final String totalPropertyName = cell.getBeanPropertyName();
1✔
204
                Object total = grandTotal ? this.grandTotals.get(totalPropertyName)
1!
205
                        : this.subTotals.get(totalPropertyName);
1✔
206

207
                final DisplaytagColumnDecorator[] decorators = cell.getColumnDecorators();
1✔
208
                for (final DisplaytagColumnDecorator decorator : decorators) {
1!
209
                    try {
210
                        total = decorator.decorate(total, this.getPageContext(), this.tableModel.getMedia());
×
211
                    } catch (final DecoratorException e) {
×
212
                        TotalTableDecorator.log.warn(e.getMessage(), e);
×
213
                        // ignore, use undecorated value for totals
214
                    }
×
215
                }
216
                buffer.append(total);
1✔
217
            } else if (this.groupPropertyName != null && this.groupPropertyName.equals(cell.getBeanPropertyName())) {
1!
218
                buffer.append(grandTotal ? this.totalLabel
×
219
                        : new MessageFormat(this.subtotalLabel, this.tableModel.getProperties().getLocale())
×
220
                                .format(new Object[] { this.previousValues.get(this.groupPropertyName) }));
×
221
            }
222

223
            buffer.append("</td>"); //$NON-NLS-1$
1✔
224

225
        }
1✔
226

227
        buffer.append("</tr>"); //$NON-NLS-1$
1✔
228

229
        // reset subtotal
230
        this.subTotals.clear();
1✔
231

232
        return buffer.toString();
1✔
233
    }
234

235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc