• 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

92.09
/displaytag/src/main/java/org/displaytag/render/TableWriterTemplate.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.render;
8

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

15
import javax.servlet.jsp.JspException;
16

17
import org.apache.commons.lang3.ObjectUtils;
18
import org.displaytag.decorator.TableDecorator;
19
import org.displaytag.model.Column;
20
import org.displaytag.model.ColumnIterator;
21
import org.displaytag.model.HeaderCell;
22
import org.displaytag.model.Row;
23
import org.displaytag.model.RowIterator;
24
import org.displaytag.model.TableModel;
25
import org.displaytag.properties.MediaTypeEnum;
26
import org.displaytag.properties.TableProperties;
27
import org.displaytag.util.TagConstants;
28
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30

31
/**
32
 * A template that encapsulates and drives the construction of a table based on a given table model and configuration.
33
 * This class is meant to be extended by classes that build tables sharing the same structure, sorting, and grouping,
34
 * but that write them in different formats and to various destinations. Subclasses must provide the format- and
35
 * destination-specific implementations of the abstract methods this class calls to build a table. (Background: This
36
 * class came about because our users wanted to export tables to Excel and PDF just as they were presented in HTML. It
37
 * originates with the TableTagData.writeHTMLData method, factoring its logic so that it can be re-used by classes that
38
 * write the tables as PDF, Excel, RTF and other formats. TableTagData.writeHTMLData now calls an HTML extension of this
39
 * class to write tables in HTML format to a JSP page.)
40
 */
41
public abstract class TableWriterTemplate {
1✔
42

43
    /** The Constant GROUP_START. */
44
    public static final short GROUP_START = -2;
45

46
    /** The Constant GROUP_END. */
47
    public static final short GROUP_END = 5;
48

49
    /** The Constant GROUP_START_AND_END. */
50
    public static final short GROUP_START_AND_END = 3;
51

52
    /** The Constant GROUP_NO_CHANGE. */
53
    public static final short GROUP_NO_CHANGE = 0;
54

55
    /** The Constant NO_RESET_GROUP. */
56
    protected static final int NO_RESET_GROUP = 42000;
57

58
    /**
59
     * logger.
60
     */
61
    private static Logger log = LoggerFactory.getLogger(TableWriterTemplate.class);
1✔
62

63
    /**
64
     * Table unique id.
65
     */
66
    private String id;
67

68
    /** The lowest ended group. */
69
    int lowestEndedGroup;
70

71
    /** The lowest started group. */
72
    int lowestStartedGroup;
73

74
    /**
75
     * Given a table model, this method creates a table, sorting and grouping it per its configuration, while delegating
76
     * where and how it writes the table to subclass objects. (Background: This method refactors
77
     * TableTagData.writeHTMLData method. See above.)
78
     *
79
     * @param model
80
     *            The table model used to build the table.
81
     * @param id
82
     *            This table's page id.
83
     *
84
     * @throws JspException
85
     *             if any exception thrown while constructing the tablei, it is caught and rethrown as a JspException.
86
     *             Extension classes may throw all sorts of exceptions, depending on their respective formats and
87
     *             destinations.
88
     */
89
    public void writeTable(final TableModel model, final String id) throws JspException {
90
        try {
91
            // table id used for logging
92
            this.id = id;
1✔
93

94
            final TableProperties properties = model.getProperties();
1✔
95

96
            if (TableWriterTemplate.log.isDebugEnabled()) {
1!
97
                TableWriterTemplate.log.debug("[" + this.id + "] writeTable called for table [" + this.id + "]");
×
98
            }
99

100
            // Handle empty table
101
            final boolean noItems = model.getRowListPage().isEmpty();
1✔
102
            if (noItems && !properties.getEmptyListShowTable()) {
1✔
103
                this.writeEmptyListMessage(properties.getEmptyListMessage());
1✔
104
                return;
1✔
105
            }
106

107
            // search result, navigation bar and export links.
108
            this.writeTopBanner(model);
1✔
109

110
            // open table
111
            this.writeTableOpener(model);
1✔
112

113
            // render caption
114
            if (model.getCaption() != null) {
1✔
115
                this.writeCaption(model);
1✔
116
            }
117

118
            // render headers
119
            if (model.getProperties().getShowHeader()) {
1✔
120
                this.writeTableHeader(model);
1✔
121
            }
122

123
            // render footer prior to body
124
            if (model.getFooter() != null) {
1✔
125
                this.writePreBodyFooter(model);
1✔
126
            }
127

128
            // open table body
129
            this.writeTableBodyOpener(model);
1✔
130

131
            // render table body
132
            this.writeTableBody(model);
1✔
133

134
            // close table body
135
            this.writeTableBodyCloser(model);
1✔
136

137
            // render footer after body
138
            if (model.getFooter() != null) {
1✔
139
                this.writePostBodyFooter(model);
1✔
140
            }
141

142
            // close table
143
            this.writeTableCloser(model);
1✔
144

145
            if (model.getTableDecorator() != null) {
1✔
146
                this.writeDecoratedTableFinish(model);
1✔
147
            }
148

149
            this.writeBottomBanner(model);
1✔
150

151
            if (TableWriterTemplate.log.isDebugEnabled()) {
1!
152
                TableWriterTemplate.log.debug("[" + this.id + "] writeTable end");
×
153
            }
154
        } catch (final Exception e) {
×
155
            throw new JspException(e);
×
156
        }
1✔
157
    }
1✔
158

159
    /**
160
     * Called by writeTable to write a message explaining that the table model contains no data.
161
     *
162
     * @param emptyListMessage
163
     *            A message explaining that the table model contains no data.
164
     *
165
     * @throws Exception
166
     *             if it encounters an error while writing.
167
     */
168
    protected abstract void writeEmptyListMessage(String emptyListMessage) throws Exception;
169

170
    /**
171
     * Called by writeTable to write a summary of the search result this table reports and the table's pagination
172
     * interface.
173
     *
174
     * @param model
175
     *            The table model for which the banner is written.
176
     *
177
     * @throws Exception
178
     *             if it encounters an error while writing.
179
     */
180
    protected abstract void writeTopBanner(TableModel model) throws Exception;
181

182
    /**
183
     * Called by writeTable to write the start of the table structure.
184
     *
185
     * @param model
186
     *            The table model for which the content is written.
187
     *
188
     * @throws Exception
189
     *             if it encounters an error while writing.
190
     */
191
    protected abstract void writeTableOpener(TableModel model) throws Exception;
192

193
    /**
194
     * Called by writeTable to write the table's caption.
195
     *
196
     * @param model
197
     *            The table model for which the content is written.
198
     *
199
     * @throws Exception
200
     *             if it encounters an error while writing.
201
     */
202
    protected abstract void writeCaption(TableModel model) throws Exception;
203

204
    /**
205
     * Called by writeTable to write the table's header columns.
206
     *
207
     * @param model
208
     *            The table model for which the content is written.
209
     *
210
     * @throws Exception
211
     *             if it encounters an error while writing.
212
     */
213
    protected abstract void writeTableHeader(TableModel model) throws Exception;
214

215
    /**
216
     * Called by writeTable to write table footer before table body.
217
     *
218
     * @param model
219
     *            The table model for which the content is written.
220
     *
221
     * @throws Exception
222
     *             if it encounters an error while writing.
223
     */
224
    protected abstract void writePreBodyFooter(TableModel model) throws Exception;
225

226
    /**
227
     * Called by writeTable to write the start of the table's body.
228
     *
229
     * @param model
230
     *            The table model for which the content is written.
231
     *
232
     * @throws Exception
233
     *             if it encounters an error while writing.
234
     */
235
    protected abstract void writeTableBodyOpener(TableModel model) throws Exception;
236

237
    // protected abstract void writeTableBody(TableModel model);
238

239
    /**
240
     * Called by writeTable to write the end of the table's body.
241
     *
242
     * @param model
243
     *            The table model for which the content is written.
244
     *
245
     * @throws Exception
246
     *             if it encounters an error while writing.
247
     */
248
    protected abstract void writeTableBodyCloser(TableModel model) throws Exception;
249

250
    /**
251
     * Called by writeTable to write table footer after table body.
252
     *
253
     * @param model
254
     *            The table model for which the content is written.
255
     *
256
     * @throws Exception
257
     *             if it encounters an error while writing.
258
     */
259
    protected abstract void writePostBodyFooter(TableModel model) throws Exception;
260

261
    /**
262
     * Called by writeTable to write the end of the table's structure.
263
     *
264
     * @param model
265
     *            The table model for which the content is written.
266
     *
267
     * @throws Exception
268
     *             if it encounters an error while writing.
269
     */
270
    protected abstract void writeTableCloser(TableModel model) throws Exception;
271

272
    /**
273
     * Called by writeTable to decorate the table.
274
     *
275
     * @param model
276
     *            The table model for which the content is written.
277
     *
278
     * @throws Exception
279
     *             if it encounters an error while writing.
280
     */
281
    protected abstract void writeDecoratedTableFinish(TableModel model) throws Exception;
282

283
    /**
284
     * Called by writeTable to write the table's footer.
285
     *
286
     * @param model
287
     *            The table model for which the content is written.
288
     *
289
     * @throws Exception
290
     *             if it encounters an error while writing.
291
     */
292
    protected abstract void writeBottomBanner(TableModel model) throws Exception;
293

294
    /**
295
     * Given a table model, writes the table body content, sorting and grouping it per its configuration, while
296
     * delegating where and how it writes to subclass objects. (Background: This method refactors
297
     * TableTagData.writeTableBody method. See above.)
298
     *
299
     * @param model
300
     *            The table model used to build the table body.
301
     *
302
     * @throws Exception
303
     *             if an error is encountered while writing the table body.
304
     */
305
    protected void writeTableBody(final TableModel model) throws Exception {
306
        // Ok, start bouncing through our list (only the visible part)
307
        boolean fullList = false;
1✔
308
        if (!MediaTypeEnum.HTML.equals(model.getMedia()) && model.getProperties().getExportFullList()) {
1!
309
            fullList = true;
1✔
310
        }
311
        final RowIterator rowIterator = model.getRowIterator(fullList);
1✔
312
        TableTotaler totalsTableDecorator = model.getTotaler();
1✔
313
        if (totalsTableDecorator == null) {
1!
314
            totalsTableDecorator = TableTotaler.NULL;
×
315
        }
316

317
        // iterator on rows
318
        final TableDecorator tableDecorator = model.getTableDecorator();
1✔
319
        Row previousRow = null;
1✔
320
        Row currentRow = null;
1✔
321
        Row nextRow = null;
1✔
322
        final Map<Integer, CellStruct> previousRowValues = new HashMap<>(10);
1✔
323
        final Map<Integer, CellStruct> currentRowValues = new HashMap<>(10);
1✔
324
        final Map<Integer, CellStruct> nextRowValues = new HashMap<>(10);
1✔
325

326
        while (nextRow != null || rowIterator.hasNext()) {
1✔
327
            // The first pass
328
            if (currentRow == null) {
1✔
329
                currentRow = rowIterator.next();
1✔
330
            } else {
331
                previousRow = currentRow;
1✔
332
                currentRow = nextRow;
1✔
333
            }
334

335
            if (previousRow != null) {
1✔
336
                previousRowValues.putAll(currentRowValues);
1✔
337
            }
338
            if (!nextRowValues.isEmpty()) {
1✔
339
                currentRowValues.putAll(nextRowValues);
1✔
340
            }
341
            // handle the first pass
342
            else {
343
                final ColumnIterator columnIterator = currentRow.getColumnIterator(model.getHeaderCellList());
1✔
344
                // iterator on columns
345
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
346
                    TableWriterTemplate.log.debug(" creating ColumnIterator on " + model.getHeaderCellList());
×
347
                }
348
                while (columnIterator.hasNext()) {
1✔
349
                    final Column column = columnIterator.nextColumn();
1✔
350

351
                    // Get the value to be displayed for the column
352
                    column.initialize();
1✔
353

354
                    @SuppressWarnings("deprecation")
355
                    final String cellvalue = MediaTypeEnum.HTML.equals(model.getMedia())
1✔
356
                            ? column.getChoppedAndLinkedValue()
1✔
357
                            : ObjectUtils.toString(column.getValue(true));
1✔
358

359
                    final CellStruct struct = new CellStruct(column, cellvalue);
1✔
360
                    currentRowValues.put(Integer.valueOf(column.getHeaderCell().getColumnNumber()), struct);
1✔
361
                }
1✔
362
            }
363

364
            nextRowValues.clear();
1✔
365
            // Populate the next row values
366
            nextRow = rowIterator.hasNext() ? rowIterator.next() : null;
1✔
367
            if (nextRow != null) {
1✔
368
                final ColumnIterator columnIterator = nextRow.getColumnIterator(model.getHeaderCellList());
1✔
369
                // iterator on columns
370
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
371
                    TableWriterTemplate.log.debug(" creating ColumnIterator on " + model.getHeaderCellList());
×
372
                }
373
                while (columnIterator.hasNext()) {
1✔
374
                    final Column column = columnIterator.nextColumn();
1✔
375
                    column.initialize();
1✔
376
                    // Get the value to be displayed for the column
377

378
                    @SuppressWarnings("deprecation")
379
                    final String cellvalue = MediaTypeEnum.HTML.equals(model.getMedia())
1✔
380
                            ? column.getChoppedAndLinkedValue()
1✔
381
                            : ObjectUtils.toString(column.getValue(true));
1✔
382

383
                    final CellStruct struct = new CellStruct(column, cellvalue);
1✔
384
                    nextRowValues.put(Integer.valueOf(column.getHeaderCell().getColumnNumber()), struct);
1✔
385
                }
1✔
386
            }
387
            // now we are going to create the current row; reset the decorator to the current row
388
            if (tableDecorator != null) {
1✔
389
                tableDecorator.initRow(currentRow.getObject(), currentRow.getRowNumber(),
1✔
390
                        currentRow.getRowNumber() + rowIterator.getPageOffset());
1✔
391
            }
392
            if (totalsTableDecorator != null) {
1!
393
                totalsTableDecorator.initRow(currentRow.getRowNumber(),
1✔
394
                        currentRow.getRowNumber() + rowIterator.getPageOffset());
1✔
395
            }
396

397
            final ArrayList<CellStruct> structsForRow = new ArrayList<>(model.getHeaderCellList().size());
1✔
398
            this.lowestEndedGroup = TableWriterTemplate.NO_RESET_GROUP;
1✔
399
            this.lowestStartedGroup = TableWriterTemplate.NO_RESET_GROUP;
1✔
400

401
            for (final HeaderCell header : model.getHeaderCellList()) {
1✔
402

403
                // Get the value to be displayed for the column
404
                final CellStruct struct = currentRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
405
                struct.decoratedValue = struct.bodyValue;
1✔
406
                // Check and see if there is a grouping transition. If there is, then notify the decorator
407
                if (header.getGroup() != -1) {
1✔
408
                    final CellStruct prior = previousRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
409
                    final CellStruct next = nextRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
410
                    // Why npe?
411
                    final String priorBodyValue = prior != null ? prior.bodyValue : null;
1✔
412
                    final String nextBodyValue = next != null ? next.bodyValue : null;
1✔
413
                    final short groupingValue = this.groupColumns(struct.bodyValue, priorBodyValue, nextBodyValue,
1✔
414
                            header.getGroup());
1✔
415

416
                    if (tableDecorator != null || totalsTableDecorator != null) {
1!
417
                        switch (groupingValue) {
1✔
418
                            case GROUP_START:
419
                                totalsTableDecorator.startGroup(struct.bodyValue, header.getGroup());
1✔
420
                                if (tableDecorator != null) {
1✔
421
                                    tableDecorator.startOfGroup(struct.bodyValue, header.getGroup());
1✔
422
                                }
423
                                break;
424
                            case GROUP_END:
425
                                totalsTableDecorator.stopGroup(struct.bodyValue, header.getGroup());
1✔
426
                                if (tableDecorator != null) {
1✔
427
                                    tableDecorator.endOfGroup(struct.bodyValue, header.getGroup());
1✔
428
                                }
429
                                break;
430
                            case GROUP_START_AND_END:
431
                                totalsTableDecorator.startGroup(struct.bodyValue, header.getGroup());
1✔
432
                                if (tableDecorator != null) {
1!
433
                                    tableDecorator.startOfGroup(struct.bodyValue, header.getGroup());
1✔
434
                                }
435
                                totalsTableDecorator.stopGroup(struct.bodyValue, header.getGroup());
1✔
436
                                if (tableDecorator != null) {
1!
437
                                    tableDecorator.endOfGroup(struct.bodyValue, header.getGroup());
1✔
438
                                }
439

440
                                break;
441
                            default:
442
                                break;
443
                        }
444
                    }
445
                    if (tableDecorator != null) {
1✔
446
                        struct.decoratedValue = tableDecorator.displayGroupedValue(struct.bodyValue, groupingValue,
1✔
447
                                header.getColumnNumber());
1✔
448
                    } else if (groupingValue == TableWriterTemplate.GROUP_END
1!
449
                            || groupingValue == TableWriterTemplate.GROUP_NO_CHANGE) {
450
                        struct.decoratedValue = TagConstants.EMPTY_STRING;
1✔
451
                    }
452
                }
453
                structsForRow.add(struct);
1✔
454
            }
1✔
455

456
            if (totalsTableDecorator != null) {
1!
457
                this.writeSubgroupStart(model);
1✔
458
            }
459
            if (tableDecorator != null) {
1✔
460
                this.writeDecoratedRowStart(model);
1✔
461
            }
462
            // open row
463
            this.writeRowOpener(currentRow);
1✔
464

465
            for (final CellStruct struct : structsForRow) {
1✔
466
                this.writeColumnOpener(struct.column);
1✔
467
                this.writeColumnValue(struct.decoratedValue, struct.column);
1✔
468
                this.writeColumnCloser(struct.column);
1✔
469
            }
1✔
470

471
            if (model.isEmpty()) {
1✔
472
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
473
                    TableWriterTemplate.log.debug("[" + this.id + "] table has no columns");
×
474
                }
475
                // render empty row
476
                this.writeRowWithNoColumns(currentRow.getObject().toString());
1✔
477
            }
478

479
            // close row
480
            this.writeRowCloser(currentRow);
1✔
481
            // decorate row finish
482
            if (model.getTableDecorator() != null) {
1✔
483
                this.writeDecoratedRowFinish(model);
1✔
484
            }
485
            if (model.getTotaler() != null) {
1!
486
                this.writeSubgroupStop(model);
1✔
487
            }
488
        }
1✔
489
        // how is this really going to work?
490
        // the totaler is notified whenever we start or stop a group, and the totaler tracks the current state of the
491
        // the totals; the totaler writes nothing
492
        // when the row is finished, it is the responsibility of the decorator or exporter to ask for the totaler total
493
        // and write it when the row is finished,
494

495
        // render empty list message
496
        if (model.getRowListPage().isEmpty()) {
1✔
497
            this.writeEmptyListRowMessage(
1✔
498
                    new MessageFormat(model.getProperties().getEmptyListRowMessage(), model.getProperties().getLocale())
1✔
499
                            .format(new Object[] { Integer.valueOf(model.getNumberOfColumns()) }));
1✔
500
        }
501
    }
1✔
502

503
    /*
504
     * writeTableBody callback methods
505
     */
506

507
    /**
508
     * Called by writeTableBody to write to decorate the table.
509
     *
510
     * @param model
511
     *            The table model for which the content is written.
512
     *
513
     * @throws Exception
514
     *             if it encounters an error while writing.
515
     */
516
    protected abstract void writeDecoratedRowStart(TableModel model) throws Exception;
517

518
    /**
519
     * Write subgroup start.
520
     *
521
     * @param model
522
     *            the model
523
     *
524
     * @throws Exception
525
     *             the exception
526
     */
527
    protected void writeSubgroupStart(final TableModel model) throws Exception {
528
    }
1✔
529

530
    /**
531
     * Write subgroup stop.
532
     *
533
     * @param model
534
     *            the model
535
     *
536
     * @throws Exception
537
     *             the exception
538
     */
539
    protected void writeSubgroupStop(final TableModel model) throws Exception {
540
    }
1✔
541

542
    /**
543
     * Called by writeTableBody to write the start of the row structure.
544
     *
545
     * @param row
546
     *            The table row for which the content is written.
547
     *
548
     * @throws Exception
549
     *             if it encounters an error while writing.
550
     */
551
    protected abstract void writeRowOpener(Row row) throws Exception;
552

553
    /**
554
     * Called by writeTableBody to write the start of the column structure.
555
     *
556
     * @param column
557
     *            The table column for which the content is written.
558
     *
559
     * @throws Exception
560
     *             if it encounters an error while writing.
561
     */
562
    protected abstract void writeColumnOpener(Column column) throws Exception;
563

564
    /**
565
     * Called by writeTableBody to write a column's value.
566
     *
567
     * @param value
568
     *            The column value.
569
     * @param column
570
     *            The table column for which the content is written.
571
     *
572
     * @throws Exception
573
     *             if it encounters an error while writing.
574
     */
575
    protected abstract void writeColumnValue(Object value, Column column) throws Exception;
576

577
    /**
578
     * Called by writeTableBody to write the end of the column structure.
579
     *
580
     * @param column
581
     *            The table column for which the content is written.
582
     *
583
     * @throws Exception
584
     *             if it encounters an error while writing.
585
     */
586
    protected abstract void writeColumnCloser(Column column) throws Exception;
587

588
    /**
589
     * Called by writeTableBody to write a row that has no columns.
590
     *
591
     * @param value
592
     *            The row value.
593
     *
594
     * @throws Exception
595
     *             if it encounters an error while writing.
596
     */
597
    protected abstract void writeRowWithNoColumns(String value) throws Exception;
598

599
    /**
600
     * Called by writeTableBody to write the end of the row structure.
601
     *
602
     * @param row
603
     *            The table row for which the content is written.
604
     *
605
     * @throws Exception
606
     *             if it encounters an error while writing.
607
     */
608
    protected abstract void writeRowCloser(Row row) throws Exception;
609

610
    /**
611
     * Called by writeTableBody to decorate the table.
612
     *
613
     * @param model
614
     *            The table model for which the content is written.
615
     *
616
     * @throws Exception
617
     *             if it encounters an error while writing.
618
     */
619
    protected abstract void writeDecoratedRowFinish(TableModel model) throws Exception;
620

621
    /**
622
     * Called by writeTableBody to write a message explaining that the row contains no data.
623
     *
624
     * @param message
625
     *            The message explaining that the row contains no data.
626
     *
627
     * @throws Exception
628
     *             if it encounters an error while writing.
629
     */
630
    protected abstract void writeEmptyListRowMessage(String message) throws Exception;
631

632
    /**
633
     * This takes a column value and grouping index as the argument. It then groups the column and returns the
634
     * appropriate string back to the caller.
635
     *
636
     * @param value
637
     *            String current cell value
638
     * @param previous
639
     *            the previous
640
     * @param next
641
     *            the next
642
     * @param currentGroup
643
     *            the current group
644
     *
645
     * @return String
646
     */
647
    @SuppressWarnings("deprecation")
648
    protected short groupColumns(final String value, final String previous, final String next, final int currentGroup) {
649

650
        short groupingKey = TableWriterTemplate.GROUP_NO_CHANGE;
1✔
651
        if (this.lowestEndedGroup < currentGroup) {
1✔
652
            // if a lower group has ended, cascade so that all subgroups end as well
653
            groupingKey += TableWriterTemplate.GROUP_END;
1✔
654
        } else if (next == null || !Objects.equals(value, next)) {
1✔
655
            // at the end of the list
656
            groupingKey += TableWriterTemplate.GROUP_END;
1✔
657
            this.lowestEndedGroup = currentGroup;
1✔
658
        }
659

660
        if (this.lowestStartedGroup < currentGroup) {
1✔
661
            // if a lower group has started, cascade so that all subgroups restart as well
662
            groupingKey += TableWriterTemplate.GROUP_START;
1✔
663
        } else if (previous == null || !Objects.equals(value, previous)) {
1✔
664
            // At the start of the list
665
            groupingKey += TableWriterTemplate.GROUP_START;
1✔
666
            this.lowestStartedGroup = currentGroup;
1✔
667
        }
668
        return groupingKey;
1✔
669
    }
670

671
    /**
672
     * The Class CellStruct.
673
     */
674
    static class CellStruct {
675

676
        /** The column. */
677
        Column column;
678

679
        /** The body value. */
680
        String bodyValue;
681

682
        /** The decorated value. */
683
        String decoratedValue;
684

685
        /**
686
         * Instantiates a new cell struct.
687
         *
688
         * @param theColumn
689
         *            the the column
690
         * @param bodyValueParam
691
         *            the body value param
692
         */
693
        public CellStruct(final Column theColumn, final String bodyValueParam) {
1✔
694
            this.column = theColumn;
1✔
695
            this.bodyValue = bodyValueParam;
1✔
696
        }
1✔
697
    }
698
}
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