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

hazendaz / displaytag / 1558

08 Dec 2025 01:09AM UTC coverage: 77.32% (-0.04%) from 77.358%
1558

push

github

web-flow
Merge pull request #1042 from hazendaz/renovate/javax-support-org.apache.commons-commons-text-1.x

Update dependency org.apache.commons:commons-text to v1.15.0 (javax-support)

1435 of 1999 branches covered (71.79%)

Branch coverage included in aggregate %.

4030 of 5069 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
 * Copyright (C) 2002-2025 Fabrizio Giustina, the Displaytag team
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to deal
6
 * in the Software without restriction, including without limitation the rights
7
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
 * copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
 * THE SOFTWARE.
21
 */
22
package org.displaytag.render;
23

24
import java.text.MessageFormat;
25
import java.util.ArrayList;
26
import java.util.HashMap;
27
import java.util.Map;
28
import java.util.Objects;
29

30
import javax.servlet.jsp.JspException;
31

32
import org.apache.commons.lang3.ObjectUtils;
33
import org.displaytag.decorator.TableDecorator;
34
import org.displaytag.model.Column;
35
import org.displaytag.model.ColumnIterator;
36
import org.displaytag.model.HeaderCell;
37
import org.displaytag.model.Row;
38
import org.displaytag.model.RowIterator;
39
import org.displaytag.model.TableModel;
40
import org.displaytag.properties.MediaTypeEnum;
41
import org.displaytag.properties.TableProperties;
42
import org.displaytag.util.TagConstants;
43
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
45

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

58
    /** The Constant GROUP_START. */
59
    public static final short GROUP_START = -2;
60

61
    /** The Constant GROUP_END. */
62
    public static final short GROUP_END = 5;
63

64
    /** The Constant GROUP_START_AND_END. */
65
    public static final short GROUP_START_AND_END = 3;
66

67
    /** The Constant GROUP_NO_CHANGE. */
68
    public static final short GROUP_NO_CHANGE = 0;
69

70
    /** The Constant NO_RESET_GROUP. */
71
    protected static final int NO_RESET_GROUP = 42000;
72

73
    /**
74
     * logger.
75
     */
76
    private static Logger log = LoggerFactory.getLogger(TableWriterTemplate.class);
1✔
77

78
    /**
79
     * Table unique id.
80
     */
81
    private String id;
82

83
    /** The lowest ended group. */
84
    int lowestEndedGroup;
85

86
    /** The lowest started group. */
87
    int lowestStartedGroup;
88

89
    /**
90
     * Given a table model, this method creates a table, sorting and grouping it per its configuration, while delegating
91
     * where and how it writes the table to subclass objects. (Background: This method refactors
92
     * TableTagData.writeHTMLData method. See above.)
93
     *
94
     * @param model
95
     *            The table model used to build the table.
96
     * @param id
97
     *            This table's page id.
98
     *
99
     * @throws JspException
100
     *             if any exception thrown while constructing the tablei, it is caught and rethrown as a JspException.
101
     *             Extension classes may throw all sorts of exceptions, depending on their respective formats and
102
     *             destinations.
103
     */
104
    public void writeTable(final TableModel model, final String id) throws JspException {
105
        try {
106
            // table id used for logging
107
            this.id = id;
1✔
108

109
            final TableProperties properties = model.getProperties();
1✔
110

111
            if (TableWriterTemplate.log.isDebugEnabled()) {
1!
112
                TableWriterTemplate.log.debug("[" + this.id + "] writeTable called for table [" + this.id + "]");
×
113
            }
114

115
            // Handle empty table
116
            final boolean noItems = model.getRowListPage().isEmpty();
1✔
117
            if (noItems && !properties.getEmptyListShowTable()) {
1✔
118
                this.writeEmptyListMessage(properties.getEmptyListMessage());
1✔
119
                return;
1✔
120
            }
121

122
            // search result, navigation bar and export links.
123
            this.writeTopBanner(model);
1✔
124

125
            // open table
126
            this.writeTableOpener(model);
1✔
127

128
            // render caption
129
            if (model.getCaption() != null) {
1✔
130
                this.writeCaption(model);
1✔
131
            }
132

133
            // render headers
134
            if (model.getProperties().getShowHeader()) {
1✔
135
                this.writeTableHeader(model);
1✔
136
            }
137

138
            // render footer prior to body
139
            if (model.getFooter() != null) {
1✔
140
                this.writePreBodyFooter(model);
1✔
141
            }
142

143
            // open table body
144
            this.writeTableBodyOpener(model);
1✔
145

146
            // render table body
147
            this.writeTableBody(model);
1✔
148

149
            // close table body
150
            this.writeTableBodyCloser(model);
1✔
151

152
            // render footer after body
153
            if (model.getFooter() != null) {
1✔
154
                this.writePostBodyFooter(model);
1✔
155
            }
156

157
            // close table
158
            this.writeTableCloser(model);
1✔
159

160
            if (model.getTableDecorator() != null) {
1✔
161
                this.writeDecoratedTableFinish(model);
1✔
162
            }
163

164
            this.writeBottomBanner(model);
1✔
165

166
            if (TableWriterTemplate.log.isDebugEnabled()) {
1!
167
                TableWriterTemplate.log.debug("[" + this.id + "] writeTable end");
×
168
            }
169
        } catch (final Exception e) {
×
170
            throw new JspException(e);
×
171
        }
1✔
172
    }
1✔
173

174
    /**
175
     * Called by writeTable to write a message explaining that the table model contains no data.
176
     *
177
     * @param emptyListMessage
178
     *            A message explaining that the table model contains no data.
179
     *
180
     * @throws Exception
181
     *             if it encounters an error while writing.
182
     */
183
    protected abstract void writeEmptyListMessage(String emptyListMessage) throws Exception;
184

185
    /**
186
     * Called by writeTable to write a summary of the search result this table reports and the table's pagination
187
     * interface.
188
     *
189
     * @param model
190
     *            The table model for which the banner is written.
191
     *
192
     * @throws Exception
193
     *             if it encounters an error while writing.
194
     */
195
    protected abstract void writeTopBanner(TableModel model) throws Exception;
196

197
    /**
198
     * Called by writeTable to write the start of the table structure.
199
     *
200
     * @param model
201
     *            The table model for which the content is written.
202
     *
203
     * @throws Exception
204
     *             if it encounters an error while writing.
205
     */
206
    protected abstract void writeTableOpener(TableModel model) throws Exception;
207

208
    /**
209
     * Called by writeTable to write the table's caption.
210
     *
211
     * @param model
212
     *            The table model for which the content is written.
213
     *
214
     * @throws Exception
215
     *             if it encounters an error while writing.
216
     */
217
    protected abstract void writeCaption(TableModel model) throws Exception;
218

219
    /**
220
     * Called by writeTable to write the table's header columns.
221
     *
222
     * @param model
223
     *            The table model for which the content is written.
224
     *
225
     * @throws Exception
226
     *             if it encounters an error while writing.
227
     */
228
    protected abstract void writeTableHeader(TableModel model) throws Exception;
229

230
    /**
231
     * Called by writeTable to write table footer before table body.
232
     *
233
     * @param model
234
     *            The table model for which the content is written.
235
     *
236
     * @throws Exception
237
     *             if it encounters an error while writing.
238
     */
239
    protected abstract void writePreBodyFooter(TableModel model) throws Exception;
240

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

252
    // protected abstract void writeTableBody(TableModel model);
253

254
    /**
255
     * Called by writeTable to write the end of the table's body.
256
     *
257
     * @param model
258
     *            The table model for which the content is written.
259
     *
260
     * @throws Exception
261
     *             if it encounters an error while writing.
262
     */
263
    protected abstract void writeTableBodyCloser(TableModel model) throws Exception;
264

265
    /**
266
     * Called by writeTable to write table footer after table body.
267
     *
268
     * @param model
269
     *            The table model for which the content is written.
270
     *
271
     * @throws Exception
272
     *             if it encounters an error while writing.
273
     */
274
    protected abstract void writePostBodyFooter(TableModel model) throws Exception;
275

276
    /**
277
     * Called by writeTable to write the end of the table's structure.
278
     *
279
     * @param model
280
     *            The table model for which the content is written.
281
     *
282
     * @throws Exception
283
     *             if it encounters an error while writing.
284
     */
285
    protected abstract void writeTableCloser(TableModel model) throws Exception;
286

287
    /**
288
     * Called by writeTable to decorate the table.
289
     *
290
     * @param model
291
     *            The table model for which the content is written.
292
     *
293
     * @throws Exception
294
     *             if it encounters an error while writing.
295
     */
296
    protected abstract void writeDecoratedTableFinish(TableModel model) throws Exception;
297

298
    /**
299
     * Called by writeTable to write the table's footer.
300
     *
301
     * @param model
302
     *            The table model for which the content is written.
303
     *
304
     * @throws Exception
305
     *             if it encounters an error while writing.
306
     */
307
    protected abstract void writeBottomBanner(TableModel model) throws Exception;
308

309
    /**
310
     * Given a table model, writes the table body content, sorting and grouping it per its configuration, while
311
     * delegating where and how it writes to subclass objects. (Background: This method refactors
312
     * TableTagData.writeTableBody method. See above.)
313
     *
314
     * @param model
315
     *            The table model used to build the table body.
316
     *
317
     * @throws Exception
318
     *             if an error is encountered while writing the table body.
319
     */
320
    protected void writeTableBody(final TableModel model) throws Exception {
321
        // Ok, start bouncing through our list (only the visible part)
322
        boolean fullList = false;
1✔
323
        if (!MediaTypeEnum.HTML.equals(model.getMedia()) && model.getProperties().getExportFullList()) {
1!
324
            fullList = true;
1✔
325
        }
326
        final RowIterator rowIterator = model.getRowIterator(fullList);
1✔
327
        TableTotaler totalsTableDecorator = model.getTotaler();
1✔
328
        if (totalsTableDecorator == null) {
1!
329
            totalsTableDecorator = TableTotaler.NULL;
×
330
        }
331

332
        // iterator on rows
333
        final TableDecorator tableDecorator = model.getTableDecorator();
1✔
334
        Row previousRow = null;
1✔
335
        Row currentRow = null;
1✔
336
        Row nextRow = null;
1✔
337
        final Map<Integer, CellStruct> previousRowValues = new HashMap<>(10);
1✔
338
        final Map<Integer, CellStruct> currentRowValues = new HashMap<>(10);
1✔
339
        final Map<Integer, CellStruct> nextRowValues = new HashMap<>(10);
1✔
340

341
        while (nextRow != null || rowIterator.hasNext()) {
1✔
342
            // The first pass
343
            if (currentRow == null) {
1✔
344
                currentRow = rowIterator.next();
1✔
345
            } else {
346
                previousRow = currentRow;
1✔
347
                currentRow = nextRow;
1✔
348
            }
349

350
            if (previousRow != null) {
1✔
351
                previousRowValues.putAll(currentRowValues);
1✔
352
            }
353
            if (!nextRowValues.isEmpty()) {
1✔
354
                currentRowValues.putAll(nextRowValues);
1✔
355
            }
356
            // handle the first pass
357
            else {
358
                final ColumnIterator columnIterator = currentRow.getColumnIterator(model.getHeaderCellList());
1✔
359
                // iterator on columns
360
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
361
                    TableWriterTemplate.log.debug(" creating ColumnIterator on " + model.getHeaderCellList());
×
362
                }
363
                while (columnIterator.hasNext()) {
1✔
364
                    final Column column = columnIterator.nextColumn();
1✔
365

366
                    // Get the value to be displayed for the column
367
                    column.initialize();
1✔
368

369
                    @SuppressWarnings("deprecation")
370
                    final String cellvalue = MediaTypeEnum.HTML.equals(model.getMedia())
1✔
371
                            ? column.getChoppedAndLinkedValue()
1✔
372
                            : ObjectUtils.toString(column.getValue(true));
1✔
373

374
                    final CellStruct struct = new CellStruct(column, cellvalue);
1✔
375
                    currentRowValues.put(Integer.valueOf(column.getHeaderCell().getColumnNumber()), struct);
1✔
376
                }
1✔
377
            }
378

379
            nextRowValues.clear();
1✔
380
            // Populate the next row values
381
            nextRow = rowIterator.hasNext() ? rowIterator.next() : null;
1✔
382
            if (nextRow != null) {
1✔
383
                final ColumnIterator columnIterator = nextRow.getColumnIterator(model.getHeaderCellList());
1✔
384
                // iterator on columns
385
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
386
                    TableWriterTemplate.log.debug(" creating ColumnIterator on " + model.getHeaderCellList());
×
387
                }
388
                while (columnIterator.hasNext()) {
1✔
389
                    final Column column = columnIterator.nextColumn();
1✔
390
                    column.initialize();
1✔
391
                    // Get the value to be displayed for the column
392

393
                    @SuppressWarnings("deprecation")
394
                    final String cellvalue = MediaTypeEnum.HTML.equals(model.getMedia())
1✔
395
                            ? column.getChoppedAndLinkedValue()
1✔
396
                            : ObjectUtils.toString(column.getValue(true));
1✔
397

398
                    final CellStruct struct = new CellStruct(column, cellvalue);
1✔
399
                    nextRowValues.put(Integer.valueOf(column.getHeaderCell().getColumnNumber()), struct);
1✔
400
                }
1✔
401
            }
402
            // now we are going to create the current row; reset the decorator to the current row
403
            if (tableDecorator != null) {
1✔
404
                tableDecorator.initRow(currentRow.getObject(), currentRow.getRowNumber(),
1✔
405
                        currentRow.getRowNumber() + rowIterator.getPageOffset());
1✔
406
            }
407
            if (totalsTableDecorator != null) {
1!
408
                totalsTableDecorator.initRow(currentRow.getRowNumber(),
1✔
409
                        currentRow.getRowNumber() + rowIterator.getPageOffset());
1✔
410
            }
411

412
            final ArrayList<CellStruct> structsForRow = new ArrayList<>(model.getHeaderCellList().size());
1✔
413
            this.lowestEndedGroup = TableWriterTemplate.NO_RESET_GROUP;
1✔
414
            this.lowestStartedGroup = TableWriterTemplate.NO_RESET_GROUP;
1✔
415

416
            for (final HeaderCell header : model.getHeaderCellList()) {
1✔
417

418
                // Get the value to be displayed for the column
419
                final CellStruct struct = currentRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
420
                struct.decoratedValue = struct.bodyValue;
1✔
421
                // Check and see if there is a grouping transition. If there is, then notify the decorator
422
                if (header.getGroup() != -1) {
1✔
423
                    final CellStruct prior = previousRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
424
                    final CellStruct next = nextRowValues.get(Integer.valueOf(header.getColumnNumber()));
1✔
425
                    // Why npe?
426
                    final String priorBodyValue = prior != null ? prior.bodyValue : null;
1✔
427
                    final String nextBodyValue = next != null ? next.bodyValue : null;
1✔
428
                    final short groupingValue = this.groupColumns(struct.bodyValue, priorBodyValue, nextBodyValue,
1✔
429
                            header.getGroup());
1✔
430

431
                    if (tableDecorator != null || totalsTableDecorator != null) {
1!
432
                        switch (groupingValue) {
1✔
433
                            case GROUP_START:
434
                                totalsTableDecorator.startGroup(struct.bodyValue, header.getGroup());
1✔
435
                                if (tableDecorator != null) {
1✔
436
                                    tableDecorator.startOfGroup(struct.bodyValue, header.getGroup());
1✔
437
                                }
438
                                break;
439
                            case GROUP_END:
440
                                totalsTableDecorator.stopGroup(struct.bodyValue, header.getGroup());
1✔
441
                                if (tableDecorator != null) {
1✔
442
                                    tableDecorator.endOfGroup(struct.bodyValue, header.getGroup());
1✔
443
                                }
444
                                break;
445
                            case GROUP_START_AND_END:
446
                                totalsTableDecorator.startGroup(struct.bodyValue, header.getGroup());
1✔
447
                                if (tableDecorator != null) {
1!
448
                                    tableDecorator.startOfGroup(struct.bodyValue, header.getGroup());
1✔
449
                                }
450
                                totalsTableDecorator.stopGroup(struct.bodyValue, header.getGroup());
1✔
451
                                if (tableDecorator != null) {
1!
452
                                    tableDecorator.endOfGroup(struct.bodyValue, header.getGroup());
1✔
453
                                }
454

455
                                break;
456
                            default:
457
                                break;
458
                        }
459
                    }
460
                    if (tableDecorator != null) {
1✔
461
                        struct.decoratedValue = tableDecorator.displayGroupedValue(struct.bodyValue, groupingValue,
1✔
462
                                header.getColumnNumber());
1✔
463
                    } else if (groupingValue == TableWriterTemplate.GROUP_END
1!
464
                            || groupingValue == TableWriterTemplate.GROUP_NO_CHANGE) {
465
                        struct.decoratedValue = TagConstants.EMPTY_STRING;
1✔
466
                    }
467
                }
468
                structsForRow.add(struct);
1✔
469
            }
1✔
470

471
            if (totalsTableDecorator != null) {
1!
472
                this.writeSubgroupStart(model);
1✔
473
            }
474
            if (tableDecorator != null) {
1✔
475
                this.writeDecoratedRowStart(model);
1✔
476
            }
477
            // open row
478
            this.writeRowOpener(currentRow);
1✔
479

480
            for (final CellStruct struct : structsForRow) {
1✔
481
                this.writeColumnOpener(struct.column);
1✔
482
                this.writeColumnValue(struct.decoratedValue, struct.column);
1✔
483
                this.writeColumnCloser(struct.column);
1✔
484
            }
1✔
485

486
            if (model.isEmpty()) {
1✔
487
                if (TableWriterTemplate.log.isDebugEnabled()) {
1!
488
                    TableWriterTemplate.log.debug("[" + this.id + "] table has no columns");
×
489
                }
490
                // render empty row
491
                this.writeRowWithNoColumns(currentRow.getObject().toString());
1✔
492
            }
493

494
            // close row
495
            this.writeRowCloser(currentRow);
1✔
496
            // decorate row finish
497
            if (model.getTableDecorator() != null) {
1✔
498
                this.writeDecoratedRowFinish(model);
1✔
499
            }
500
            if (model.getTotaler() != null) {
1!
501
                this.writeSubgroupStop(model);
1✔
502
            }
503
        }
1✔
504
        // how is this really going to work?
505
        // the totaler is notified whenever we start or stop a group, and the totaler tracks the current state of the
506
        // the totals; the totaler writes nothing
507
        // when the row is finished, it is the responsibility of the decorator or exporter to ask for the totaler total
508
        // and write it when the row is finished,
509

510
        // render empty list message
511
        if (model.getRowListPage().isEmpty()) {
1✔
512
            this.writeEmptyListRowMessage(
1✔
513
                    new MessageFormat(model.getProperties().getEmptyListRowMessage(), model.getProperties().getLocale())
1✔
514
                            .format(new Object[] { Integer.valueOf(model.getNumberOfColumns()) }));
1✔
515
        }
516
    }
1✔
517

518
    /*
519
     * writeTableBody callback methods
520
     */
521

522
    /**
523
     * Called by writeTableBody to write to decorate the table.
524
     *
525
     * @param model
526
     *            The table model for which the content is written.
527
     *
528
     * @throws Exception
529
     *             if it encounters an error while writing.
530
     */
531
    protected abstract void writeDecoratedRowStart(TableModel model) throws Exception;
532

533
    /**
534
     * Write subgroup start.
535
     *
536
     * @param model
537
     *            the model
538
     *
539
     * @throws Exception
540
     *             the exception
541
     */
542
    protected void writeSubgroupStart(final TableModel model) throws Exception {
543
    }
1✔
544

545
    /**
546
     * Write subgroup stop.
547
     *
548
     * @param model
549
     *            the model
550
     *
551
     * @throws Exception
552
     *             the exception
553
     */
554
    protected void writeSubgroupStop(final TableModel model) throws Exception {
555
    }
1✔
556

557
    /**
558
     * Called by writeTableBody to write the start of the row structure.
559
     *
560
     * @param row
561
     *            The table row for which the content is written.
562
     *
563
     * @throws Exception
564
     *             if it encounters an error while writing.
565
     */
566
    protected abstract void writeRowOpener(Row row) throws Exception;
567

568
    /**
569
     * Called by writeTableBody to write the start of the column structure.
570
     *
571
     * @param column
572
     *            The table column for which the content is written.
573
     *
574
     * @throws Exception
575
     *             if it encounters an error while writing.
576
     */
577
    protected abstract void writeColumnOpener(Column column) throws Exception;
578

579
    /**
580
     * Called by writeTableBody to write a column's value.
581
     *
582
     * @param value
583
     *            The column value.
584
     * @param column
585
     *            The table column for which the content is written.
586
     *
587
     * @throws Exception
588
     *             if it encounters an error while writing.
589
     */
590
    protected abstract void writeColumnValue(Object value, Column column) throws Exception;
591

592
    /**
593
     * Called by writeTableBody to write the end of the column structure.
594
     *
595
     * @param column
596
     *            The table column for which the content is written.
597
     *
598
     * @throws Exception
599
     *             if it encounters an error while writing.
600
     */
601
    protected abstract void writeColumnCloser(Column column) throws Exception;
602

603
    /**
604
     * Called by writeTableBody to write a row that has no columns.
605
     *
606
     * @param value
607
     *            The row value.
608
     *
609
     * @throws Exception
610
     *             if it encounters an error while writing.
611
     */
612
    protected abstract void writeRowWithNoColumns(String value) throws Exception;
613

614
    /**
615
     * Called by writeTableBody to write the end of the row structure.
616
     *
617
     * @param row
618
     *            The table row for which the content is written.
619
     *
620
     * @throws Exception
621
     *             if it encounters an error while writing.
622
     */
623
    protected abstract void writeRowCloser(Row row) throws Exception;
624

625
    /**
626
     * Called by writeTableBody to decorate the table.
627
     *
628
     * @param model
629
     *            The table model for which the content is written.
630
     *
631
     * @throws Exception
632
     *             if it encounters an error while writing.
633
     */
634
    protected abstract void writeDecoratedRowFinish(TableModel model) throws Exception;
635

636
    /**
637
     * Called by writeTableBody to write a message explaining that the row contains no data.
638
     *
639
     * @param message
640
     *            The message explaining that the row contains no data.
641
     *
642
     * @throws Exception
643
     *             if it encounters an error while writing.
644
     */
645
    protected abstract void writeEmptyListRowMessage(String message) throws Exception;
646

647
    /**
648
     * This takes a column value and grouping index as the argument. It then groups the column and returns the
649
     * appropriate string back to the caller.
650
     *
651
     * @param value
652
     *            String current cell value
653
     * @param previous
654
     *            the previous
655
     * @param next
656
     *            the next
657
     * @param currentGroup
658
     *            the current group
659
     *
660
     * @return String
661
     */
662
    @SuppressWarnings("deprecation")
663
    protected short groupColumns(final String value, final String previous, final String next, final int currentGroup) {
664

665
        short groupingKey = TableWriterTemplate.GROUP_NO_CHANGE;
1✔
666
        if (this.lowestEndedGroup < currentGroup) {
1✔
667
            // if a lower group has ended, cascade so that all subgroups end as well
668
            groupingKey += TableWriterTemplate.GROUP_END;
1✔
669
        } else if (next == null || !Objects.equals(value, next)) {
1✔
670
            // at the end of the list
671
            groupingKey += TableWriterTemplate.GROUP_END;
1✔
672
            this.lowestEndedGroup = currentGroup;
1✔
673
        }
674

675
        if (this.lowestStartedGroup < currentGroup) {
1✔
676
            // if a lower group has started, cascade so that all subgroups restart as well
677
            groupingKey += TableWriterTemplate.GROUP_START;
1✔
678
        } else if (previous == null || !Objects.equals(value, previous)) {
1✔
679
            // At the start of the list
680
            groupingKey += TableWriterTemplate.GROUP_START;
1✔
681
            this.lowestStartedGroup = currentGroup;
1✔
682
        }
683
        return groupingKey;
1✔
684
    }
685

686
    /**
687
     * The Class CellStruct.
688
     */
689
    static class CellStruct {
690

691
        /** The column. */
692
        Column column;
693

694
        /** The body value. */
695
        String bodyValue;
696

697
        /** The decorated value. */
698
        String decoratedValue;
699

700
        /**
701
         * Instantiates a new cell struct.
702
         *
703
         * @param theColumn
704
         *            the the column
705
         * @param bodyValueParam
706
         *            the body value param
707
         */
708
        public CellStruct(final Column theColumn, final String bodyValueParam) {
1✔
709
            this.column = theColumn;
1✔
710
            this.bodyValue = bodyValueParam;
1✔
711
        }
1✔
712
    }
713
}
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