• 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

77.01
/displaytag/src/main/java/org/displaytag/render/HssfTableWriter.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.Calendar;
11
import java.util.Collections;
12
import java.util.Date;
13
import java.util.List;
14

15
import org.apache.commons.lang3.StringUtils;
16
import org.apache.commons.lang3.Strings;
17
import org.apache.commons.lang3.math.NumberUtils;
18
import org.apache.poi.hssf.usermodel.HSSFCell;
19
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
20
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
21
import org.apache.poi.hssf.usermodel.HSSFFont;
22
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
23
import org.apache.poi.hssf.usermodel.HSSFRow;
24
import org.apache.poi.hssf.usermodel.HSSFSheet;
25
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
26
import org.apache.poi.ss.usermodel.BorderStyle;
27
import org.apache.poi.ss.usermodel.CellStyle;
28
import org.apache.poi.ss.usermodel.HorizontalAlignment;
29
import org.apache.poi.ss.usermodel.IndexedColors;
30
import org.apache.poi.ss.util.CellRangeAddress;
31
import org.displaytag.decorator.TableDecorator;
32
import org.displaytag.decorator.hssf.DecoratesHssf;
33
import org.displaytag.export.XmlTotalsWriter;
34
import org.displaytag.export.excel.ExcelUtils;
35
import org.displaytag.model.Column;
36
import org.displaytag.model.HeaderCell;
37
import org.displaytag.model.Row;
38
import org.displaytag.model.TableModel;
39

40
/**
41
 * A table writer that formats a table in Excel's spreadsheet format, and writes it to an HSSF workbook.
42
 *
43
 * @see org.displaytag.render.TableWriterTemplate
44
 */
45
public class HssfTableWriter extends TableWriterAdapter {
46

47
    /** The Constant EMPTY_TEXT. */
48
    public static final HSSFRichTextString EMPTY_TEXT = new HSSFRichTextString("");
1✔
49

50
    /** The total label. */
51
    protected MessageFormat totalLabel = new MessageFormat("{0} Total");
1✔
52

53
    /** The decorated. */
54
    protected boolean decorated = false;
1✔
55

56
    /**
57
     * The workbook to which the table is written.
58
     */
59
    private final HSSFWorkbook wb;
60

61
    /**
62
     * Generated sheet.
63
     */
64
    protected HSSFSheet sheet;
65

66
    /**
67
     * Current row number.
68
     */
69
    protected int sheetRowNum;
70

71
    /**
72
     * Current row.
73
     */
74
    private HSSFRow currentRow;
75

76
    /**
77
     * Current column number.
78
     */
79
    protected int colNum;
80

81
    /**
82
     * Current cell.
83
     */
84
    protected HSSFCell currentCell;
85

86
    /** The current grouping. */
87
    protected int currentGrouping = 0;
1✔
88

89
    /**
90
     * Percent Excel format.
91
     */
92

93
    protected short intFormat = HSSFDataFormat.getBuiltinFormat("0");
1✔
94

95
    /**
96
     * Some operations require the model.
97
     */
98
    protected TableModel model;
99

100
    /** The sheet name. */
101
    protected String sheetName = "-";
1✔
102

103
    /** The utils. */
104
    protected ExcelUtils utils;
105

106
    /**
107
     * This table writer uses an HSSF workbook to write the table.
108
     *
109
     * @param wb
110
     *            The HSSF workbook to write the table.
111
     */
112
    public HssfTableWriter(final HSSFWorkbook wb) {
1✔
113
        this.wb = wb;
1✔
114
        this.utils = new ExcelUtils(wb);
1✔
115
    }
1✔
116

117
    /**
118
     * Write table opener.
119
     *
120
     * @param model
121
     *            the model
122
     *
123
     * @throws Exception
124
     *             the exception
125
     *
126
     * @see org.displaytag.render.TableWriterTemplate#writeTableOpener(org.displaytag.model.TableModel)
127
     */
128
    @Override
129
    protected void writeTableOpener(final TableModel model) throws Exception {
130
        this.sheet = this.wb.createSheet(this.sheetName);
1✔
131
        this.setModel(model);
1✔
132
        this.init(model);
1✔
133
        this.sheetRowNum = 0;
1✔
134

135
    }
1✔
136

137
    /**
138
     * Override this to do local config, but you should call super() first so that this can set up the ExcelUtils.
139
     *
140
     * @param model
141
     *            the model
142
     */
143
    protected void init(final TableModel model) {
144
        this.utils.initCellStyles(model.getProperties());
1✔
145
    }
1✔
146

147
    /**
148
     * Write caption.
149
     *
150
     * @param model
151
     *            the model
152
     *
153
     * @throws Exception
154
     *             the exception
155
     *
156
     * @see org.displaytag.render.TableWriterTemplate#writeCaption(org.displaytag.model.TableModel)
157
     */
158
    @Override
159
    protected void writeCaption(final TableModel model) throws Exception {
160
        final HSSFCellStyle style = this.wb.createCellStyle();
×
161
        final HSSFFont bold = this.wb.createFont();
×
162
        bold.setBold(true);
×
163
        bold.setFontHeightInPoints((short) 14);
×
164
        style.setFont(bold);
×
165
        style.setAlignment(HorizontalAlignment.CENTER);
×
166

167
        this.colNum = 0;
×
168
        this.currentRow = this.sheet.createRow(this.sheetRowNum++);
×
169
        this.currentCell = this.currentRow.createCell(this.colNum);
×
170
        this.currentCell.setCellStyle(style);
×
171
        final String caption = model.getCaption();
×
172
        this.currentCell.setCellValue(new HSSFRichTextString(caption));
×
173
        this.rowSpanTable(model);
×
174
    }
×
175

176
    /**
177
     * Obtain the region over which to merge a cell.
178
     *
179
     * @param first
180
     *            Column number of first cell from which to merge.
181
     * @param last
182
     *            Column number of last cell over which to merge.
183
     *
184
     * @return The region over which to merge a cell.
185
     */
186
    private CellRangeAddress getMergeCellsRegion(final int first, final int last) {
187
        return new CellRangeAddress(this.currentRow.getRowNum(), this.currentRow.getRowNum(), first, last);
×
188
    }
189

190
    /**
191
     * Write table header.
192
     *
193
     * @param model
194
     *            the model
195
     *
196
     * @throws Exception
197
     *             the exception
198
     *
199
     * @see org.displaytag.render.TableWriterTemplate#writeTableHeader(org.displaytag.model.TableModel)
200
     */
201
    @Override
202
    protected void writeTableHeader(final TableModel model) throws Exception {
203
        this.currentRow = this.sheet.createRow(this.sheetRowNum++);
1✔
204
        this.colNum = 0;
1✔
205
        final HSSFCellStyle headerStyle = this.getHeaderFooterStyle();
1✔
206
        for (final HeaderCell headerCell : model.getHeaderCellList()) {
1✔
207
            String columnHeader = headerCell.getTitle();
1✔
208
            if (columnHeader == null) {
1!
209
                columnHeader = StringUtils.capitalize(headerCell.getBeanPropertyName());
×
210
            }
211

212
            this.writeHeaderFooter(columnHeader, this.currentRow, headerStyle);
1✔
213
        }
1✔
214
    }
1✔
215

216
    /**
217
     * Write decorated row start.
218
     *
219
     * @param model
220
     *            the model
221
     *
222
     * @see org.displaytag.render.TableWriterTemplate#writeDecoratedRowStart(org.displaytag.model.TableModel)
223
     */
224
    @Override
225
    protected void writeDecoratedRowStart(final TableModel model) {
226
        model.getTableDecorator().startRow();
1✔
227
    }
1✔
228

229
    /**
230
     * Write row opener.
231
     *
232
     * @param row
233
     *            the row
234
     *
235
     * @throws Exception
236
     *             the exception
237
     */
238
    @Override
239
    protected void writeRowOpener(final Row row) throws Exception {
240
        this.currentRow = this.sheet.createRow(this.sheetRowNum++);
1✔
241
        this.colNum = 0;
1✔
242
    }
1✔
243

244
    /**
245
     * Write a column's opening structure to a HSSF document.
246
     *
247
     * @param column
248
     *            the column
249
     *
250
     * @throws Exception
251
     *             the exception
252
     *
253
     * @see org.displaytag.render.TableWriterTemplate#writeColumnOpener(org.displaytag.model.Column)
254
     */
255
    @Override
256
    protected void writeColumnOpener(final Column column) throws Exception {
257
        if (column != null) {
1✔
258
            column.getOpenTag(); // has side effect, setting its stringValue, which affects grouping logic.
1✔
259
        }
260
        this.currentCell = this.currentRow.createCell(this.colNum++);
1✔
261
    }
1✔
262

263
    /**
264
     * Write column value.
265
     *
266
     * @param value
267
     *            the value
268
     * @param column
269
     *            the column
270
     *
271
     * @throws Exception
272
     *             the exception
273
     *
274
     * @see org.displaytag.render.TableWriterTemplate#writeColumnValue(Object,org.displaytag.model.Column)
275
     */
276
    @Override
277
    protected void writeColumnValue(final Object value, final Column column) throws Exception {
278
        // is this a detail row for a column that is currently grouped?
279
        final int myGroup = column.getHeaderCell().getGroup();
1✔
280
        Object cellValue = column.getValue(this.decorated);
1✔
281
        if (myGroup > 0) {
1✔
282
            cellValue = "";
1✔
283
        }
284
        this.writeCellValue(cellValue);
1✔
285
    }
1✔
286

287
    /**
288
     * Override in subclasses to handle local data types.
289
     *
290
     * @param value
291
     *            the value object to write
292
     */
293
    protected void writeCellValue(final Object value) {
294
        if (value instanceof Number) {
1✔
295
            final Number num = (Number) value;
1✔
296
            // Percentage
297
            if (value.toString().indexOf('%') > -1) {
1!
298
                this.currentCell.setCellValue(num.doubleValue() / 100);
×
299
                this.currentCell.setCellStyle(this.utils.getStyle(ExcelUtils.STYLE_PCT));
×
300
            } else if (value instanceof Integer) {
1✔
301
                this.currentCell.setCellStyle(this.utils.getStyle(ExcelUtils.STYLE_INTEGER));
1✔
302
                this.currentCell.setCellValue(num.intValue());
1✔
303
            } else {
304
                this.currentCell.setCellValue(num.doubleValue());
1✔
305
            }
306

307
        } else if (value instanceof Date) {
1✔
308
            this.currentCell.setCellValue((Date) value);
1✔
309
            this.currentCell.setCellStyle(this.utils.getStyle(ExcelUtils.STYLE_DATE));
1✔
310
        } else if (value instanceof Calendar) {
1!
311
            final Calendar c = (Calendar) value;
×
312
            this.currentCell.setCellValue(c);
×
313
            this.currentCell.setCellStyle(this.utils.getStyle(ExcelUtils.STYLE_DATE));
×
314
        } else if (value == null) {
1✔
315
            this.currentCell.setCellValue(HssfTableWriter.EMPTY_TEXT);
1✔
316
        } else {
317
            final String v = value.toString();
1✔
318
            if (v.length() > this.utils.getWrapAtLength()) {
1✔
319
                this.currentCell.getCellStyle().setWrapText(true);
1✔
320
            }
321
            this.currentCell.setCellValue(new HSSFRichTextString(ExcelUtils.escapeColumnValue(value)));
1✔
322
        }
323

324
    }
1✔
325

326
    /**
327
     * Decorators that help render the table to an HSSF table must implement DecoratesHssf.
328
     *
329
     * @param model
330
     *            the model
331
     *
332
     * @throws Exception
333
     *             the exception
334
     *
335
     * @see org.displaytag.render.TableWriterTemplate#writeDecoratedRowFinish(org.displaytag.model.TableModel)
336
     */
337
    @Override
338
    protected void writeDecoratedRowFinish(final TableModel model) throws Exception {
339
        final TableDecorator decorator = model.getTableDecorator();
1✔
340
        if (decorator instanceof DecoratesHssf) {
1!
341
            final DecoratesHssf hdecorator = (DecoratesHssf) decorator;
×
342
            hdecorator.setSheet(this.sheet);
×
343
        }
344
        decorator.finishRow();
1✔
345
        this.sheetRowNum = this.sheet.getLastRowNum();
1✔
346
        this.sheetRowNum++;
1✔
347
    }
1✔
348

349
    /**
350
     * Write post body footer.
351
     *
352
     * @param model
353
     *            the model
354
     *
355
     * @throws Exception
356
     *             the exception
357
     *
358
     * @see org.displaytag.render.TableWriterTemplate#writePostBodyFooter(org.displaytag.model.TableModel)
359
     */
360
    @Override
361
    protected void writePostBodyFooter(final TableModel model) throws Exception {
362
        this.colNum = 0;
×
363
        this.currentRow = this.sheet.createRow(this.sheetRowNum++);
×
364
        this.writeHeaderFooter(model.getFooter(), this.currentRow, this.getHeaderFooterStyle());
×
365
        this.rowSpanTable(model);
×
366
    }
×
367

368
    /**
369
     * Make a row span the width of the table.
370
     *
371
     * @param model
372
     *            The table model representing the rendered table.
373
     */
374
    private void rowSpanTable(final TableModel model) {
375
        this.sheet.addMergedRegion(
×
376
                this.getMergeCellsRegion(this.currentCell.getColumnIndex(), model.getNumberOfColumns() - 1));
×
377
    }
×
378

379
    /**
380
     * Write decorated table finish.
381
     *
382
     * @param model
383
     *            the model
384
     *
385
     * @see org.displaytag.render.TableWriterTemplate#writeDecoratedTableFinish(org.displaytag.model.TableModel)
386
     */
387
    @Override
388
    protected void writeDecoratedTableFinish(final TableModel model) {
389
        model.getTableDecorator().finish();
1✔
390
    }
1✔
391

392
    /**
393
     * Is this value numeric? You should probably override this method to handle your locale.
394
     *
395
     * @param rawValue
396
     *            the object value
397
     *
398
     * @return true if numeric
399
     */
400
    protected boolean isNumber(final String rawValue) {
401
        if (rawValue == null) {
×
402
            return false;
×
403
        }
404
        String rawV = rawValue;
×
405
        if (rawV.indexOf('%') > -1) {
×
406
            rawV = rawV.replace('%', ' ').trim();
×
407
        }
408
        if (rawV.indexOf('$') > -1) {
×
409
            rawV = rawV.replace('$', ' ').trim();
×
410
        }
411
        if (rawV.indexOf(',') > -1) {
×
412
            rawV = Strings.CS.replace(rawV, ",", "");
×
413
        }
414
        return NumberUtils.isCreatable(rawV.trim());
×
415
    }
416

417
    /**
418
     * Writes a table header or a footer.
419
     *
420
     * @param value
421
     *            Header or footer value to be rendered.
422
     * @param row
423
     *            The row in which to write the header or footer.
424
     * @param style
425
     *            Style used to render the header or footer.
426
     */
427
    private void writeHeaderFooter(final String value, final HSSFRow row, final HSSFCellStyle style) {
428
        this.currentCell = row.createCell(this.colNum++);
1✔
429
        this.currentCell.setCellValue(new HSSFRichTextString(value));
1✔
430
        this.currentCell.setCellStyle(style);
1✔
431
    }
1✔
432

433
    /**
434
     * Obtain the style used to render a header or footer.
435
     *
436
     * @return The style used to render a header or footer.
437
     */
438
    private HSSFCellStyle getHeaderFooterStyle() {
439
        final HSSFCellStyle style = this.wb.createCellStyle();
1✔
440
        final HSSFFont bold = this.wb.createFont();
1✔
441
        bold.setBold(true);
1✔
442
        style.setBorderBottom(BorderStyle.THIN);
1✔
443
        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
1✔
444

445
        style.setFont(bold);
1✔
446
        return style;
1✔
447
    }
448

449
    /**
450
     * Write bottom banner.
451
     *
452
     * @param model
453
     *            the model
454
     *
455
     * @throws Exception
456
     *             the exception
457
     *
458
     * @see org.displaytag.render.TableWriterAdapter#writeBottomBanner(org.displaytag.model.TableModel)
459
     */
460
    @Override
461
    protected void writeBottomBanner(final TableModel model) throws Exception {
462
        // adjust the column widths
463
        int colCount = 0;
1✔
464
        while (colCount <= this.colNum) {
1✔
465
            this.sheet.autoSizeColumn((short) colCount);
1✔
466
            colCount++;
1✔
467
        }
468
    }
1✔
469

470
    /**
471
     * Write subgroup start.
472
     *
473
     * @param model
474
     *            the model
475
     *
476
     * @throws Exception
477
     *             the exception
478
     */
479
    @Override
480
    protected void writeSubgroupStart(final TableModel model) throws Exception {
481
        final TableTotaler tt = model.getTotaler();
1✔
482
        if (tt.howManyGroups == 0) {
1✔
483
            return;
1✔
484
        }
485

486
        // for each newly opened subgroup we need to output the opener, in order;
487
        // so we need to know somehow which groups are new since we last wrote out openers; how about we track a list of
488
        // the
489
        // already opened groups, and ask the tt for a list of all known groups?
490

491
        for (final int dtColumnNumber : tt.getOpenedColumns()) {
1✔
492
            this.currentGrouping++;
1✔
493
            this.writeRowOpener(null);
1✔
494
            // for each subgroup
495

496
            for (final HeaderCell cell : model.getHeaderCellList()) {
1✔
497
                this.writeColumnOpener(null);
1✔
498
                final int thisCellAsDtNumber = this.asDtColNumber(cell.getColumnNumber());
1✔
499
                final String columnValue = thisCellAsDtNumber != dtColumnNumber ? ""
1✔
500
                        : tt.getGroupingValue(dtColumnNumber);
1✔
501
                this.writeCellValue(columnValue);
1✔
502
                this.writeColumnCloser(null);
1✔
503
            }
1✔
504

505
            this.writeRowCloser(null);
1✔
506
            // Have to handle a case where this is a nested subgroup start;
507
            // put out the blanks for any column that has already exists
508
            // now write the label for the group that is opening
509
        }
1✔
510
    }
1✔
511

512
    /**
513
     * DT columns are 1 based, excel columns are 0 based.
514
     *
515
     * @param cellColumnNumber
516
     *            the cell column number
517
     *
518
     * @return the int
519
     */
520
    protected int asDtColNumber(final int cellColumnNumber) {
521
        return cellColumnNumber + 1;
1✔
522
    }
523

524
    /**
525
     * Gets the total label.
526
     *
527
     * @param groupingValue
528
     *            the grouping value
529
     *
530
     * @return the total label
531
     */
532
    public String getTotalLabel(final String groupingValue) {
533
        final String gv = StringUtils.defaultString(groupingValue);
1✔
534
        return MessageFormat.format("{0} Total", gv);
1✔
535
    }
536

537
    /**
538
     * Write subgroup stop.
539
     *
540
     * @param model
541
     *            the model
542
     *
543
     * @throws Exception
544
     *             the exception
545
     */
546
    @Override
547
    protected void writeSubgroupStop(final TableModel model) throws Exception {
548
        final TableTotaler tt = model.getTotaler();
1✔
549

550
        // for each newly opened subgroup we need to output the opener, in order;
551
        // so we need to know somehow which groups are new since we last wrote out openers; how about we track a list of
552
        // the
553
        // already opened groups, and ask the tt for a list of all known groups?
554

555
        if (tt.howManyGroups == 0) {
1✔
556
            return;
1✔
557
        }
558
        final List<Integer> closedColumns = tt.getClosedColumns();
1✔
559
        Collections.reverse(closedColumns);
1✔
560
        for (final int columnNumber : closedColumns) {
1✔
561
            this.writeRowOpener(null);
1✔
562
            // for each subgroup
563

564
            for (final HeaderCell cell : model.getHeaderCellList()) {
1✔
565
                this.writeColumnOpener(null);
1✔
566
                Object columnValue;
567
                final int cellColumnNumberAsDt = this.asDtColNumber(cell.getColumnNumber());
1✔
568
                if (cellColumnNumberAsDt > columnNumber && cell.isTotaled()) {
1✔
569
                    columnValue = tt.getTotalForColumn(cell.getColumnNumber(), this.currentGrouping);
1✔
570
                } else if (cellColumnNumberAsDt == columnNumber) {
1✔
571
                    columnValue = this.getTotalLabel(tt.getGroupingValue(columnNumber));
1✔
572
                } else {
573
                    columnValue = null;
1✔
574
                }
575
                this.writeCellValue(columnValue);
1✔
576
                this.writeColumnCloser(null);
1✔
577
            }
1✔
578

579
            this.writeRowCloser(null);
1✔
580
            this.writeGroupExtraInfo(model);
1✔
581
            this.currentGrouping--;
1✔
582
        }
1✔
583

584
        assert this.currentGrouping > -1;
1!
585
        super.writeSubgroupStop(model);
1✔
586
    }
1✔
587

588
    /**
589
     * Sets the model.
590
     *
591
     * @param m
592
     *            the new model
593
     */
594
    public void setModel(final TableModel m) {
595
        m.setTableDecorator(XmlTotalsWriter.NOOP);
1✔
596
        if (m.getTotaler() == null || m.getTotaler() == TableTotaler.NULL) {
1!
597
            final TableTotaler tt = new TableTotaler();
×
598
            tt.init(m);
×
599
            m.setTotaler(tt);
×
600
        }
601
        this.model = m;
1✔
602
    }
1✔
603

604
    /**
605
     * Gets the sheet name.
606
     *
607
     * @return the sheet name
608
     */
609
    public String getSheetName() {
610
        return this.sheetName;
×
611
    }
612

613
    /**
614
     * Sets the sets the sheet name.
615
     *
616
     * @param name
617
     *            the new sets the sheet name
618
     */
619
    public void setSetSheetName(final String name) {
620
        this.sheetName = name;
1✔
621
    }
1✔
622

623
    /**
624
     * Gets the sheet.
625
     *
626
     * @return the sheet
627
     */
628
    public HSSFSheet getSheet() {
629
        return this.sheet;
×
630
    }
631

632
    /**
633
     * Write table body closer.
634
     *
635
     * @param model
636
     *            the model
637
     *
638
     * @throws Exception
639
     *             the exception
640
     */
641
    @Override
642
    protected void writeTableBodyCloser(final TableModel model) throws Exception {
643
        // write totals, if there are any
644
        boolean hasTotals = false;
1✔
645
        for (final HeaderCell cell : model.getHeaderCellList()) {
1✔
646
            hasTotals = hasTotals || cell.isTotaled();
1✔
647
        }
1✔
648
        if (!hasTotals) {
1!
649
            return;
×
650
        }
651
        final TableTotaler tt = model.getTotaler();
1✔
652
        this.writeRowOpener(null);
1✔
653
        for (final HeaderCell cell : model.getHeaderCellList()) {
1✔
654
            this.writeColumnOpener(null);
1✔
655
            final Object columnValue = cell.isTotaled() ? tt.getTotalForColumn(cell.getColumnNumber(), 0) : null;
1✔
656
            this.writeCellValue(columnValue);
1✔
657
            final CellStyle st = this.utils.getNewCellStyle();
1✔
658
            st.cloneStyleFrom(this.currentCell.getCellStyle());
1✔
659
            st.setBorderTop(BorderStyle.THIN);
1✔
660
            st.setTopBorderColor(IndexedColors.BLACK.getIndex());
1✔
661
            this.currentCell.setCellStyle(st);
1✔
662
            this.writeColumnCloser(null);
1✔
663
        }
1✔
664
        this.writeRowCloser(null);
1✔
665
    }
1✔
666

667
    /**
668
     * Write group extra info.
669
     *
670
     * @param model
671
     *            the model
672
     *
673
     * @throws Exception
674
     *             the exception
675
     */
676
    protected void writeGroupExtraInfo(final TableModel model) throws Exception {
677
    }
1✔
678
}
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