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