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