/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.renderkit.html_basic;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.component.UIComponent;
import javax.faces.component.UIColumn;
import javax.faces.component.UIData;
import com.sun.faces.renderkit.Attribute;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.util.Util;
/**
* Base class for concrete Grid and Table renderers.
*/
public abstract class BaseTableRenderer extends HtmlBasicRenderer {
// ------------------------------------------------------- Protected Methods
/**
* Called to render the opening/closing <code>thead</code> elements
* and any content nested between.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
protected abstract void renderHeader(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException;
/**
* Called to render the opening/closing <code>tfoot</code> elements
* and any content nested between.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
protected abstract void renderFooter(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException;
/**
* Call to render the content that should be included between opening
* and closing <code>tr</code> elements.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param row the current row (if any - an implmenetation may not need this)
* @param writer the current writer
* @throws IOException if content cannot be written
*/
protected abstract void renderRow(FacesContext context,
UIComponent table,
UIComponent row,
ResponseWriter writer)
throws IOException;
/**
* Renders the start of a table and applies the value of
* <code>styleClass</code> if available and renders any
* pass through attributes that may be specified.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @param attributes pass-through attributes that the component
* supports
* @throws IOException if content cannot be written
*/
protected void renderTableStart(FacesContext context,
UIComponent table,
ResponseWriter writer,
Attribute[] attributes)
throws IOException {
writer.startElement("table", table);
writeIdAttributeIfNecessary(context, writer, table);
String styleClass = (String) table.getAttributes().get("styleClass");
if (styleClass != null) {
writer.writeAttribute("class", styleClass, "styleClass");
}
RenderKitUtils.renderPassThruAttributes(context,
writer,
table,
attributes);
writer.writeText("\n", table, null);
}
/**
* Renders the closing <code>table</code> element.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void renderTableEnd(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException {
writer.endElement("table");
writer.writeText("\n", table, null);
}
/**
* Renders the caption of the table applying the values of
* <code>captionClass</code> as the class and <code>captionStyle</code>
* as the style if either are present.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
protected void renderCaption(FacesContext context,
UIComponent table,
ResponseWriter writer) throws IOException {
UIComponent caption = getFacet(table, "caption");
if (caption != null) {
String captionClass =
(String) table.getAttributes().get("captionClass");
String captionStyle = (String)
table.getAttributes().get("captionStyle");
writer.startElement("caption", table);
if (captionClass != null) {
writer.writeAttribute("class", captionClass, "captionClass");
}
if (captionStyle != null) {
writer.writeAttribute("style", captionStyle, "captionStyle");
}
encodeRecursive(context, caption);
writer.endElement("caption");
}
}
/**
* Renders the starting <code>tbody</code> element.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void renderTableBodyStart(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException {
writer.startElement("tbody", table);
writer.writeText("\n", table, null);
}
/**
* Renders the closing <code>tbody</code> element.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void renderTableBodyEnd(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException {
writer.endElement("tbody");
writer.writeText("\n", table, null);
}
/**
* Renders the starting <code>tr</code> element applying any values
* from the <code>rowClasses</code> attribute.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
protected void renderRowStart(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException {
TableMetaInfo info = getMetaInfo(context, table);
writer.startElement("tr", table);
final String tableRowClass = info.rowClasses.length > 0 ? info.getCurrentRowClass() : null;
final String rowClass = (String) table.getAttributes().get("rowClass");
if(tableRowClass != null) {
if(rowClass != null) {
throw new IOException("Cannot define both rowClasses on a table and rowClass");
}
writer.writeAttribute("class", tableRowClass, "rowClasses");
}
if(rowClass != null){
if(tableRowClass != null) {
throw new IOException("Cannot define both rowClasses on a table and rowClass");
}
writer.writeAttribute("class", rowClass, "rowClass");
}
writer.writeText("\n", table, null);
}
/**
* Renders the closing <code>rt</code> element.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @param writer the current writer
* @throws IOException if content cannot be written
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void renderRowEnd(FacesContext context,
UIComponent table,
ResponseWriter writer)
throws IOException {
writer.endElement("tr");
writer.writeText("\n", table, null);
}
/**
* Returns a <code>TableMetaInfo</code> object containing details such
* as row and column classes, columns, and a mechanism for scrolling through
* the row/column classes.
* @param context the <code>FacesContext</code> for the current request
* @param table the table that's being rendered
* @return the <code>TableMetaInfo</code> for provided table
*/
protected TableRenderer.TableMetaInfo getMetaInfo(FacesContext context,
UIComponent table) {
String key = createKey(table);
Map<Object,Object> attributes = context.getAttributes();
TableRenderer.TableMetaInfo info = (TableRenderer.TableMetaInfo)
attributes.get(key);
if (info == null) {
info = new TableRenderer.TableMetaInfo(table);
attributes.put(key, info);
}
return info;
}
/**
* Removes the cached TableMetaInfo from the specified component.
* @param context the <code>FacesContext</code> for the current request
* @param table the table from which the TableMetaInfo will be removed
*/
protected void clearMetaInfo(FacesContext context, UIComponent table) {
context.getAttributes().remove(createKey(table));
}
/**
* Creates a unique key based on the provided <code>UIComponent</code> with
* which the TableMetaInfo can be looked up.
*
* @param table the table that's being rendered
* @return a unique key to store the metadata in the request and still have
* it associated with a specific component.
*/
protected String createKey(UIComponent table) {
return TableMetaInfo.KEY + '_' + table.hashCode();
}
// ----------------------------------------------------------- Inner Classes
protected static class TableMetaInfo {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final String KEY = TableMetaInfo.class.getName();
public final String[] rowClasses;
public final String[] columnClasses;
public final List<UIColumn> columns;
public final boolean hasHeaderFacets;
public final boolean hasFooterFacets;
public final int columnCount;
public int columnStyleCounter;
public int rowStyleCounter;
// -------------------------------------------------------- Constructors
public TableMetaInfo(UIComponent table) {
rowClasses = getRowClasses(table);
columnClasses = getColumnClasses(table);
columns = getColumns(table);
columnCount = columns.size();
hasHeaderFacets = hasFacet("header", columns);
hasFooterFacets = hasFacet("footer", columns);
}
// ------------------------------------------------------ Public Methods
/**
* Reset the counter used to apply column styles.
*/
public void newRow() {
columnStyleCounter = 0;
}
/**
* Obtain the column class based on the current counter. Calling this
* method automatically moves the pointer to the next style. If the
* counter is larger than the number of total classes, the counter will
* be reset.
* @return the current style
*/
public String getCurrentColumnClass() {
String style = null;
if (columnStyleCounter < columnClasses.length
&& columnStyleCounter <= columnCount) {
style = columnClasses[columnStyleCounter++];
}
return ((style != null && style.length() > 0) ? style : null);
}
/**
* Obtain the row class based on the current counter. Calling this
* method automatically moves the pointer to the next style. If the
* counter is larger than the number of total classes, the counter will
* be reset.
* @return the current style
*/
public String getCurrentRowClass() {
String style = rowClasses[rowStyleCounter++];
if (rowStyleCounter >= rowClasses.length) {
rowStyleCounter = 0;
}
return style;
}
// ----------------------------------------------------- Private Methods
/**
* <p>Return an array of stylesheet classes to be applied to each column in
* the table in the order specified. Every column may or may not have a
* stylesheet.</p>
*
* @param table {@link javax.faces.component.UIComponent} component being rendered
*
* @return an array of column classes
*/
private static String[] getColumnClasses(UIComponent table) {
String values = (String) table.getAttributes().get("columnClasses");
if (values == null) {
return EMPTY_STRING_ARRAY;
}
Map<String, Object> appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap();
return Util.split(appMap, values.trim(), ",");
}
/**
* <p>Return an Iterator over the <code>UIColumn</code> children of the
* specified <code>UIData</code> that have a <code>rendered</code> property
* of <code>true</code>.</p>
*
* @param table the table from which to extract children
*
* @return the List of all UIColumn children
*/
private static List<UIColumn> getColumns(UIComponent table) {
if (table instanceof UIData) {
int childCount = table.getChildCount();
if (childCount > 0) {
List<UIColumn> results =
new ArrayList<>(childCount);
for (UIComponent kid : table.getChildren()) {
if ((kid instanceof UIColumn) && kid.isRendered()) {
results.add((UIColumn) kid);
}
}
return results;
} else {
return Collections.emptyList();
}
} else {
int count;
Object value = table.getAttributes().get("columns");
if ((value != null) && (value instanceof Integer)) {
count = ((Integer) value);
} else {
count = 2;
}
if (count < 1) {
count = 1;
}
List<UIColumn> result = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
result.add(new UIColumn());
}
return result;
}
}
/**
* <p>Return the number of child <code>UIColumn</code> components nested in
* the specified <code>UIData</code> that have a facet with the specified
* name.</p>
*
* @param name Name of the facet being analyzed
* @param columns the columns to search
*
* @return the number of columns associated with the specified Facet name
*/
private static boolean hasFacet(String name, List<UIColumn> columns) {
if (!columns.isEmpty()) {
for (UIColumn column : columns) {
if (column.getFacetCount() > 0) {
if (column.getFacets().containsKey(name)) {
return true;
}
}
}
}
return false;
}
/**
* <p>Return an array of stylesheet classes to be applied to each row in the
* table, in the order specified. Every row may or may not have a
* stylesheet.</p>
*
* @param table {@link javax.faces.component.UIComponent} component being rendered
*
* @return an array of row classes
*/
private static String[] getRowClasses(UIComponent table) {
String values = (String) table.getAttributes().get("rowClasses");
if (values == null) {
return (EMPTY_STRING_ARRAY);
}
Map<String, Object> appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap();
return Util.split(appMap, values.trim(), ",");
}
} // END UIDataMetaInfo
}