/*
 * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.javadoc.internal.doclets.formats.html.markup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;

import javax.lang.model.element.Element;

import jdk.javadoc.internal.doclets.formats.html.Contents;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;

A builder for HTML tables, such as the summary tables for various types of element.

The table should be used in three phases:

  1. Configuration: the overall characteristics of the table should be specified
  2. Population: the content for the cells in each row should be added
  3. Generation: the HTML content and any associated JavaScript can be accessed
Many methods return the current object, to facilitate fluent builder-style usage.

This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice.

/** * A builder for HTML tables, such as the summary tables for various * types of element. * * <p>The table should be used in three phases: * <ol> * <li>Configuration: the overall characteristics of the table should be specified * <li>Population: the content for the cells in each row should be added * <li>Generation: the HTML content and any associated JavaScript can be accessed * </ol> * * Many methods return the current object, to facilitate fluent builder-style usage. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */
public class Table { private final HtmlVersion version; private final HtmlStyle tableStyle; private String summary; private Content caption; private Map<String, Predicate<Element>> tabMap; private String defaultTab; private Set<String> tabs; private HtmlStyle activeTabStyle = HtmlStyle.activeTableTab; private HtmlStyle tabStyle = HtmlStyle.tableTab; private HtmlStyle tabEnd = HtmlStyle.tabEnd; private IntFunction<String> tabScript; private Function<Integer, String> tabId = (i -> "t" + i); private TableHeader header; private List<HtmlStyle> columnStyles; private int rowScopeColumnIndex; private List<HtmlStyle> stripedStyles = Arrays.asList(HtmlStyle.altColor, HtmlStyle.rowColor); private final List<Content> bodyRows; private final List<Integer> bodyRowMasks; private String rowIdPrefix = "i";
Creates a builder for an HTML table.
Params:
  • version – the version of HTML, used to determine is a summary attribute is needed
  • style – the style class for the <table> tag
/** * Creates a builder for an HTML table. * * @param version the version of HTML, used to determine is a {@code summary} * attribute is needed * @param style the style class for the {@code <table>} tag */
public Table(HtmlVersion version, HtmlStyle style) { this.version = version; this.tableStyle = style; bodyRows = new ArrayList<>(); bodyRowMasks = new ArrayList<>(); }
Sets the summary for the table. This is ignored if the HTML version for the table is not HtmlVersion.HTML4.
Params:
  • summary – the summary
Returns:this object
/** * Sets the summary for the table. * This is ignored if the HTML version for the table is not {@link HtmlVersion#HTML4}. * * @param summary the summary * @return this object */
public Table setSummary(String summary) { if (version == HtmlVersion.HTML4) { this.summary = summary; } return this; }
Sets the caption for the table. This is ignored if the table is configured to provide tabs to select different subsets of rows within the table. The caption should be suitable for use as the content of a <caption> element. For compatibility, the code currently accepts a <caption> element as well. This should be removed when all clients rely on using the <caption> element being generated by this class.
Params:
  • captionContent – the caption
Returns:this object
/** * Sets the caption for the table. * This is ignored if the table is configured to provide tabs to select * different subsets of rows within the table. * The caption should be suitable for use as the content of a {@code <caption>} * element. * * <b>For compatibility, the code currently accepts a {@code <caption>} element * as well. This should be removed when all clients rely on using the {@code <caption>} * element being generated by this class.</b> * * @param captionContent the caption * @return this object */
public Table setCaption(Content captionContent) { if (captionContent instanceof HtmlTree && ((HtmlTree) captionContent).htmlTag == HtmlTag.CAPTION) { caption = captionContent; } else { caption = getCaption(captionContent); } return this; }
Adds a tab to the table. Tabs provide a way to display subsets of rows, as determined by a predicate for the tab, and an element associated with each row. Tabs will appear left-to-right in the order they are added.
Params:
  • name – the name of the tab
  • predicate – the predicate
Returns:this object
/** * Adds a tab to the table. * Tabs provide a way to display subsets of rows, as determined by a * predicate for the tab, and an element associated with each row. * Tabs will appear left-to-right in the order they are added. * * @param name the name of the tab * @param predicate the predicate * @return this object */
public Table addTab(String name, Predicate<Element> predicate) { if (tabMap == null) { tabMap = new LinkedHashMap<>(); // preserves order that tabs are added tabs = new HashSet<>(); // order not significant } tabMap.put(name, predicate); return this; }
Sets the name for the default tab, which displays all the rows in the table. This tab will appear first in the left-to-right list of displayed tabs.
Params:
  • name – the name
Returns:this object
/** * Sets the name for the default tab, which displays all the rows in the table. * This tab will appear first in the left-to-right list of displayed tabs. * * @param name the name * @return this object */
public Table setDefaultTab(String name) { defaultTab = name; return this; }
Sets the function used to generate the JavaScript to be used when a tab is selected. When the function is invoked, the argument will be an integer value containing the bit mask identifying the rows to be selected.
Params:
  • f – the function
Returns:this object
/** * Sets the function used to generate the JavaScript to be used when a tab is selected. * When the function is invoked, the argument will be an integer value containing * the bit mask identifying the rows to be selected. * * @param f the function * @return this object */
public Table setTabScript(IntFunction<String> f) { tabScript = f; return this; }
Sets the name of the styles used to display the tabs.
Params:
  • activeTabStyle – the style for the active tab
  • tabStyle – the style for other tabs
  • tabEnd – the style for the padding that appears within each tab
Returns: this object
/** * Sets the name of the styles used to display the tabs. * * @param activeTabStyle the style for the active tab * @param tabStyle the style for other tabs * @param tabEnd the style for the padding that appears within each tab * @return this object */
public Table setTabStyles(HtmlStyle activeTabStyle, HtmlStyle tabStyle, HtmlStyle tabEnd) { this.activeTabStyle = activeTabStyle; this.tabStyle = tabStyle; this.tabEnd = tabEnd; return this; }
Sets the JavaScript function used to generate the id attribute for each tag. The default is to use tN where N is the index of the tab, counting from 0 (for the default tab), and then from 1 upwards for additional tabs.
Params:
  • f – the function
Returns:this object
/** * Sets the JavaScript function used to generate the {@code id} attribute for each tag. * The default is to use <code>t</code><i>N</i> where <i>N</i> is the index of the tab, * counting from 0 (for the default tab), and then from 1 upwards for additional tabs. * * @param f the function * @return this object */
public Table setTabId(Function<Integer,String> f) { tabId = f; return this; }
Sets the header for the table.

Notes:

  • This currently does not use a <thead> tag, but probably should, eventually
  • The column styles are not currently applied to the header, but probably should, eventually
Params:
  • header – the header
Returns:this object
/** * Sets the header for the table. * * <p>Notes: * <ul> * <li>This currently does not use a {@code <thead>} tag, but probably should, eventually * <li>The column styles are not currently applied to the header, but probably should, eventually * </ul> * * @param header the header * @return this object */
public Table setHeader(TableHeader header) { this.header = header; return this; }
Sets the styles used for <tr> tags, to give a "striped" appearance. The defaults are currently rowColor and altColor.
Params:
  • evenRowStyle – the style to use for even-numbered rows
  • oddRowStyle – the style to use for odd-numbered rows
Returns:
/** * Sets the styles used for {@code <tr>} tags, to give a "striped" appearance. * The defaults are currently {@code rowColor} and {@code altColor}. * * @param evenRowStyle the style to use for even-numbered rows * @param oddRowStyle the style to use for odd-numbered rows * @return */
public Table setStripedStyles(HtmlStyle evenRowStyle, HtmlStyle oddRowStyle) { stripedStyles = Arrays.asList(evenRowStyle, oddRowStyle); return this; }
Sets the column used to indicate which cell in a row should be declared as a header cell with the scope attribute set to row.
Params:
  • columnIndex – the column index
Returns:this object
/** * Sets the column used to indicate which cell in a row should be declared * as a header cell with the {@code scope} attribute set to {@code row}. * * @param columnIndex the column index * @return this object */
public Table setRowScopeColumn(int columnIndex) { rowScopeColumnIndex = columnIndex; return this; }
Sets the styles for be used for the cells in each row.

Note:

  • The column styles are not currently applied to the header, but probably should, eventually
Params:
  • styles – the styles
Returns:this object
/** * Sets the styles for be used for the cells in each row. * * <p>Note: * <ul> * <li>The column styles are not currently applied to the header, but probably should, eventually * </ul> * * @param styles the styles * @return this object */
public Table setColumnStyles(HtmlStyle... styles) { return setColumnStyles(Arrays.asList(styles)); }
Sets the styles for be used for the cells in each row.

Note:

  • The column styles are not currently applied to the header, but probably should, eventually
Params:
  • styles – the styles
Returns:this object
/** * Sets the styles for be used for the cells in each row. * * <p>Note: * <ul> * <li>The column styles are not currently applied to the header, but probably should, eventually * </ul> * * @param styles the styles * @return this object */
public Table setColumnStyles(List<HtmlStyle> styles) { columnStyles = styles; return this; }
Sets the prefix used for the id attribute for each row in the table. The default is "i".

Note:

  • The prefix should probably be a value such that the generated ids cannot clash with any other id, such as those that might be created for fields within a class.
Params:
  • prefix – the prefix
Returns: this object
/** * Sets the prefix used for the {@code id} attribute for each row in the table. * The default is "i". * * <p>Note: * <ul> * <li>The prefix should probably be a value such that the generated ids cannot * clash with any other id, such as those that might be created for fields within * a class. * </ul> * * @param prefix the prefix * @return this object */
public Table setRowIdPrefix(String prefix) { rowIdPrefix = prefix; return this; }
Add a row of data to the table. Each item of content should be suitable for use as the content of a <th> or <td> cell. This method should not be used when the table has tabs: use a method that takes an Element parameter instead.
Params:
  • contents – the contents for the row
/** * Add a row of data to the table. * Each item of content should be suitable for use as the content of a * {@code <th>} or {@code <td>} cell. * This method should not be used when the table has tabs: use a method * that takes an {@code Element} parameter instead. * * @param contents the contents for the row */
public void addRow(Content... contents) { addRow(null, Arrays.asList(contents)); }
Add a row of data to the table. Each item of content should be suitable for use as the content of a <th> or <td> cell. This method should not be used when the table has tabs: use a method that takes an element parameter instead.
Params:
  • contents – the contents for the row
/** * Add a row of data to the table. * Each item of content should be suitable for use as the content of a * {@code <th>} or {@code <td> cell}. * This method should not be used when the table has tabs: use a method * that takes an {@code element} parameter instead. * * @param contents the contents for the row */
public void addRow(List<Content> contents) { addRow(null, contents); }
Add a row of data to the table. Each item of content should be suitable for use as the content of a <th> or <td> cell. If tabs have been added to the table, the specified element will be used to determine whether the row should be displayed when any particular tab is selected, using the predicate specified when the tab was added.
Params:
  • element – the element
  • contents – the contents for the row
Throws:
/** * Add a row of data to the table. * Each item of content should be suitable for use as the content of a * {@code <th>} or {@code <td>} cell. * * If tabs have been added to the table, the specified element will be used * to determine whether the row should be displayed when any particular tab * is selected, using the predicate specified when the tab was * {@link #add(String,Predicate) added}. * * @param element the element * @param contents the contents for the row * @throws NullPointerException if tabs have previously been added to the table * and {@code element} is null */
public void addRow(Element element, Content... contents) { addRow(element, Arrays.asList(contents)); }
Add a row of data to the table. Each item of content should be suitable for use as the content of a <th> or <td> cell. If tabs have been added to the table, the specified element will be used to determine whether the row should be displayed when any particular tab is selected, using the predicate specified when the tab was added.
Params:
  • element – the element
  • contents – the contents for the row
Throws:
/** * Add a row of data to the table. * Each item of content should be suitable for use as the content of a * {@code <th>} or {@code <td>} cell. * * If tabs have been added to the table, the specified element will be used * to determine whether the row should be displayed when any particular tab * is selected, using the predicate specified when the tab was * {@link #add(String,Predicate) added}. * * @param element the element * @param contents the contents for the row * @throws NullPointerException if tabs have previously been added to the table * and {@code element} is null */
public void addRow(Element element, List<Content> contents) { if (tabMap != null && element == null) { throw new NullPointerException(); } HtmlTree row = new HtmlTree(HtmlTag.TR); if (stripedStyles != null) { int rowIndex = bodyRows.size(); row.addAttr(HtmlAttr.CLASS, stripedStyles.get(rowIndex % 2).name()); } int colIndex = 0; for (Content c : contents) { HtmlStyle cellStyle = (columnStyles == null || colIndex > columnStyles.size()) ? null : columnStyles.get(colIndex); HtmlTree cell = (colIndex == rowScopeColumnIndex) ? HtmlTree.TH(cellStyle, "row", c) : HtmlTree.TD(cellStyle, c); row.addContent(cell); colIndex++; } bodyRows.add(row); if (tabMap != null) { int index = bodyRows.size() - 1; row.addAttr(HtmlAttr.ID, (rowIdPrefix + index)); int mask = 0; int maskBit = 1; for (Map.Entry<String, Predicate<Element>> e : tabMap.entrySet()) { String name = e.getKey(); Predicate<Element> predicate = e.getValue(); if (predicate.test(element)) { tabs.add(name); mask |= maskBit; } maskBit = (maskBit << 1); } bodyRowMasks.add(mask); } }
Returns whether or not the table is empty. The table is empty if it has no (body) rows.
Returns:true if the table has no rows
/** * Returns whether or not the table is empty. * The table is empty if it has no (body) rows. * * @return true if the table has no rows */
public boolean isEmpty() { return bodyRows.isEmpty(); }
Returns the HTML for the table.
Returns:the HTML
/** * Returns the HTML for the table. * * @return the HTML */
public Content toContent() { HtmlTree mainDiv = new HtmlTree(HtmlTag.DIV); mainDiv.setStyle(tableStyle); HtmlTree table = new HtmlTree(HtmlTag.TABLE); if (summary != null) { table.addAttr(HtmlAttr.SUMMARY, summary); } if (tabMap == null || tabs.size() == 1) { if (tabMap == null) { table.addContent(caption); } else if (tabs.size() == 1) { String tabName = tabs.iterator().next(); table.addContent(getCaption(new StringContent(tabName))); } table.addContent(getTableBody()); mainDiv.addContent(table); } else { HtmlTree tablist = new HtmlTree(HtmlTag.DIV) .addAttr(HtmlAttr.ROLE, "tablist") .addAttr(HtmlAttr.ARIA_ORIENTATION, "horizontal"); int tabIndex = 0; tablist.addContent(createTab(tabId.apply(tabIndex), activeTabStyle, true, defaultTab)); table.addAttr(HtmlAttr.ARIA_LABELLEDBY, tabId.apply(tabIndex)); for (String tabName : tabMap.keySet()) { tabIndex++; if (tabs.contains(tabName)) { String script = tabScript.apply(1 << (tabIndex - 1)); HtmlTree tab = createTab(tabId.apply(tabIndex), tabStyle, false, tabName); tab.addAttr(HtmlAttr.ONCLICK, script); tablist.addContent(tab); } } HtmlTree tabpanel = new HtmlTree(HtmlTag.DIV) .addAttr(HtmlAttr.ID, tableStyle + "_tabpanel") .addAttr(HtmlAttr.ROLE, "tabpanel"); table.addContent(getTableBody()); tabpanel.addContent(table); mainDiv.addContent(tablist); mainDiv.addContent(tabpanel); } return mainDiv; } private HtmlTree createTab(String tabId, HtmlStyle style, boolean defaultTab, String tabName) { HtmlTree tab = new HtmlTree(HtmlTag.BUTTON) .addAttr(HtmlAttr.ROLE, "tab") .addAttr(HtmlAttr.ARIA_SELECTED, defaultTab ? "true" : "false") .addAttr(HtmlAttr.ARIA_CONTROLS, tableStyle + "_tabpanel") .addAttr(HtmlAttr.TABINDEX, defaultTab ? "0" : "-1") .addAttr(HtmlAttr.ONKEYDOWN, "switchTab(event)") .addAttr(HtmlAttr.ID, tabId) .setStyle(style); tab.addContent(tabName); return tab; } private Content getTableBody() { ContentBuilder tableContent = new ContentBuilder(); tableContent.addContent(header.toContent()); Content tbody = new HtmlTree(HtmlTag.TBODY); bodyRows.forEach(row -> tbody.addContent(row)); tableContent.addContent(tbody); return tableContent; }
Returns whether or not the table needs JavaScript support. It requires such support if tabs have been added.
Returns:true if JavaScript is required
/** * Returns whether or not the table needs JavaScript support. * It requires such support if tabs have been added. * * @return true if JavaScript is required */
public boolean needsScript() { return (tabs != null) && (tabs.size() > 1); }
Returns the script to be used in conjunction with the table.
Returns:the script
/** * Returns the script to be used in conjunction with the table. * * @return the script */
public String getScript() { if (tabMap == null) throw new IllegalStateException(); StringBuilder sb = new StringBuilder(); // Add the variable defining the bitmask for each row sb.append("var data").append(" = {"); int rowIndex = 0; for (int mask : bodyRowMasks) { if (rowIndex > 0) { sb.append(","); } sb.append("\"").append(rowIdPrefix).append(rowIndex).append("\":").append(mask); rowIndex++; } sb.append("};\n"); // Add the variable defining the tabs sb.append("var tabs = {"); appendTabInfo(sb, 65535, tabId.apply(0), defaultTab); int tabIndex = 1; int maskBit = 1; for (String tabName: tabMap.keySet()) { if (tabs.contains(tabName)) { sb.append(","); appendTabInfo(sb, maskBit, tabId.apply(tabIndex), tabName); } tabIndex++; maskBit = (maskBit << 1); } sb.append("};\n"); // Add the variables defining the stylenames appendStyleInfo(sb, stripedStyles.get(0), stripedStyles.get(1), tabStyle, activeTabStyle); return sb.toString(); } private void appendTabInfo(StringBuilder sb, int value, String id, String name) { sb.append(value) .append(":[") .append(Script.stringLiteral(id)) .append(",") .append(Script.stringLiteral(name)) .append("]"); } private void appendStyleInfo(StringBuilder sb, HtmlStyle... styles) { for (HtmlStyle style : styles) { sb.append("var ").append(style).append(" = \"").append(style).append("\";\n"); } } private HtmlTree getCaption(Content title) { return new HtmlTree(HtmlTag.CAPTION, HtmlTree.SPAN(title), HtmlTree.SPAN(tabEnd, Contents.SPACE)); } }