1 /******************************************************************************* 2 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors 3 * This program and the accompanying materials are made available under 4 * the terms of the Eclipse Public License 2.0 which is available at 5 * http://www.eclipse.org/legal/epl-2.0 6 * 7 * SPDX-License-Identifier: EPL-2.0 8 * 9 * Contributors: 10 * Marc R. Hoffmann - initial API and implementation 11 * 12 *******************************************************************************/ 13 package org.jacoco.report.internal.html.table; 14 15 import java.io.IOException; 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.Comparator; 19 import java.util.List; 20 21 import org.jacoco.core.analysis.ICoverageNode; 22 import org.jacoco.report.internal.ReportOutputFolder; 23 import org.jacoco.report.internal.html.HTMLElement; 24 import org.jacoco.report.internal.html.resources.Resources; 25 import org.jacoco.report.internal.html.resources.Styles; 26 27 /** 28 * Renderer for a table of {@link ITableItem}s. 29 */ 30 public class Table { 31 32 private final List<Column> columns; 33 34 private Comparator<ITableItem> defaultComparator; 35 36 /** 37 * Create a new table without any columns yet. 38 */ Table()39 public Table() { 40 this.columns = new ArrayList<Table.Column>(); 41 } 42 43 /** 44 * Adds a new column with the given properties to the table. 45 * 46 * @param header 47 * column header caption 48 * @param style 49 * optional CSS style class name for the td-Elements of this 50 * column 51 * @param renderer 52 * callback for column rendering 53 * @param defaultSorting 54 * If <code>true</code>, this column is the default sorting 55 * column. Only one column can be selected for default sorting. 56 * 57 */ add(final String header, final String style, final IColumnRenderer renderer, final boolean defaultSorting)58 public void add(final String header, final String style, 59 final IColumnRenderer renderer, final boolean defaultSorting) { 60 columns.add(new Column(columns.size(), header, style, renderer, 61 defaultSorting)); 62 if (defaultSorting) { 63 if (defaultComparator != null) { 64 throw new IllegalStateException( 65 "Default sorting only allowed for one column."); 66 } 67 this.defaultComparator = renderer.getComparator(); 68 } 69 } 70 71 /** 72 * Renders a table for the given icon 73 * 74 * @param parent 75 * parent element in which the table is created 76 * @param items 77 * items that will make the table rows 78 * @param total 79 * the summary of all coverage data items in the table static 80 * resources that might be referenced 81 * @param resources 82 * static resources that might be referenced 83 * @param base 84 * base folder of the table 85 * @throws IOException 86 * in case of IO problems with the element output 87 */ render(final HTMLElement parent, final List<? extends ITableItem> items, final ICoverageNode total, final Resources resources, final ReportOutputFolder base)88 public void render(final HTMLElement parent, 89 final List<? extends ITableItem> items, final ICoverageNode total, 90 final Resources resources, final ReportOutputFolder base) 91 throws IOException { 92 final List<? extends ITableItem> sortedItems = sort(items); 93 final HTMLElement table = parent.table(Styles.COVERAGETABLE); 94 table.attr("id", "coveragetable"); 95 header(table, sortedItems, total); 96 footer(table, total, resources, base); 97 body(table, sortedItems, resources, base); 98 } 99 header(final HTMLElement table, final List<? extends ITableItem> items, final ICoverageNode total)100 private void header(final HTMLElement table, 101 final List<? extends ITableItem> items, final ICoverageNode total) 102 throws IOException { 103 final HTMLElement tr = table.thead().tr(); 104 for (final Column c : columns) { 105 c.init(tr, items, total); 106 } 107 } 108 footer(final HTMLElement table, final ICoverageNode total, final Resources resources, final ReportOutputFolder base)109 private void footer(final HTMLElement table, final ICoverageNode total, 110 final Resources resources, final ReportOutputFolder base) 111 throws IOException { 112 final HTMLElement tr = table.tfoot().tr(); 113 for (final Column c : columns) { 114 c.footer(tr, total, resources, base); 115 } 116 } 117 body(final HTMLElement table, final List<? extends ITableItem> items, final Resources resources, final ReportOutputFolder base)118 private void body(final HTMLElement table, 119 final List<? extends ITableItem> items, final Resources resources, 120 final ReportOutputFolder base) throws IOException { 121 final HTMLElement tbody = table.tbody(); 122 int idx = 0; 123 for (final ITableItem item : items) { 124 final HTMLElement tr = tbody.tr(); 125 for (final Column c : columns) { 126 c.body(tr, idx, item, resources, base); 127 } 128 idx++; 129 } 130 } 131 sort( final List<? extends ITableItem> items)132 private List<? extends ITableItem> sort( 133 final List<? extends ITableItem> items) { 134 if (defaultComparator != null) { 135 final List<ITableItem> result = new ArrayList<ITableItem>(items); 136 Collections.sort(result, defaultComparator); 137 return result; 138 } 139 return items; 140 } 141 142 private static class Column { 143 144 private final char idprefix; 145 private final String header; 146 private final IColumnRenderer renderer; 147 private final SortIndex<ITableItem> index; 148 private final String style, headerStyle; 149 150 private boolean visible; 151 Column(final int idx, final String header, final String style, final IColumnRenderer renderer, final boolean defaultSorting)152 Column(final int idx, final String header, final String style, 153 final IColumnRenderer renderer, final boolean defaultSorting) { 154 this.idprefix = (char) ('a' + idx); 155 this.header = header; 156 this.renderer = renderer; 157 index = new SortIndex<ITableItem>(renderer.getComparator()); 158 this.style = style; 159 this.headerStyle = Styles.combine( 160 defaultSorting ? Styles.DOWN : null, Styles.SORTABLE, 161 style); 162 } 163 init(final HTMLElement tr, final List<? extends ITableItem> items, final ICoverageNode total)164 void init(final HTMLElement tr, final List<? extends ITableItem> items, 165 final ICoverageNode total) throws IOException { 166 visible = renderer.init(items, total); 167 if (visible) { 168 index.init(items); 169 final HTMLElement td = tr.td(headerStyle); 170 td.attr("id", String.valueOf(idprefix)); 171 td.attr("onclick", "toggleSort(this)"); 172 td.text(header); 173 } 174 } 175 footer(final HTMLElement tr, final ICoverageNode total, final Resources resources, final ReportOutputFolder base)176 void footer(final HTMLElement tr, final ICoverageNode total, 177 final Resources resources, final ReportOutputFolder base) 178 throws IOException { 179 if (visible) { 180 renderer.footer(tr.td(style), total, resources, base); 181 } 182 } 183 body(final HTMLElement tr, final int idx, final ITableItem item, final Resources resources, final ReportOutputFolder base)184 void body(final HTMLElement tr, final int idx, final ITableItem item, 185 final Resources resources, final ReportOutputFolder base) 186 throws IOException { 187 if (visible) { 188 final HTMLElement td = tr.td(style); 189 td.attr("id", 190 idprefix + String.valueOf(index.getPosition(idx))); 191 renderer.item(td, item, resources, base); 192 } 193 } 194 195 } 196 197 } 198