0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package com.sun.rave.web.ui.component;
0042:
0043: import com.sun.data.provider.FieldKey;
0044: import com.sun.data.provider.RowKey;
0045: import com.sun.data.provider.FilterCriteria;
0046: import com.sun.data.provider.SortCriteria;
0047: import com.sun.data.provider.TableDataFilter;
0048: import com.sun.data.provider.TableDataProvider;
0049: import com.sun.data.provider.TableDataSorter;
0050: import com.sun.data.provider.impl.BasicTableDataFilter;
0051: import com.sun.data.provider.impl.BasicTableDataSorter;
0052: import com.sun.data.provider.impl.ObjectListDataProvider;
0053: import com.sun.data.provider.impl.ObjectArrayDataProvider;
0054: import com.sun.data.provider.impl.TableRowDataProvider;
0055: import com.sun.rave.web.ui.theme.Theme;
0056: import com.sun.rave.web.ui.theme.ThemeImages;
0057: import com.sun.rave.web.ui.theme.ThemeStyles;
0058: import com.sun.rave.web.ui.util.ConversionUtilities;
0059: import com.sun.rave.web.ui.util.LogUtil;
0060: import com.sun.rave.web.ui.util.ThemeUtilities;
0061:
0062: import java.beans.Beans;
0063: import java.io.IOException;
0064: import java.io.Serializable;
0065: import java.util.ArrayList;
0066: import java.util.HashMap;
0067: import java.util.Iterator;
0068: import java.util.List;
0069: import java.util.Map;
0070:
0071: import javax.faces.application.FacesMessage;
0072: import javax.faces.context.FacesContext;
0073: import javax.faces.component.EditableValueHolder;
0074: import javax.faces.component.NamingContainer;
0075: import javax.faces.component.UIComponent;
0076: import javax.faces.component.UIComponentBase;
0077: import javax.faces.component.UIViewRoot;
0078: import javax.faces.el.ValueBinding;
0079: import javax.faces.event.AbortProcessingException;
0080: import javax.faces.event.FacesEvent;
0081: import javax.faces.event.FacesListener;
0082: import javax.faces.event.PhaseId;
0083:
0084: /**
0085: * Component that represents a group of table rows.
0086: * <p>
0087: * The TableRowGroup component provides a layout mechanism for displaying rows
0088: * of data. UI guidelines describe specific behavior that can applied to the
0089: * rows and columns of data such as sorting, filtering, pagination, selection,
0090: * and custom user actions. In addition, UI guidelines also define sections of
0091: * the table that can be used for titles, row group headers, and placement of
0092: * pre-defined and user defined actions.
0093: * </p><p>
0094: * The TableRowGroup component supports a data binding to a collection of data
0095: * objects represented by a TableDataProvider instance, which is the
0096: * current value of this component itself. During iterative processing over the
0097: * rows of data in the data provider, the TableDataProvider for the current row
0098: * is exposed as a request attribute under the key specified by the
0099: * var property.
0100: * </p><p>
0101: * Only children of type TableColumn should be processed by renderers associated
0102: * with this component.
0103: * </p><p>
0104: * Note: Column headers and footers are rendered by TableRowGroupRenderer. Table
0105: * column footers are rendered by TableRenderer.
0106: * </p><p>
0107: * Note: To see the messages logged by this class, set the following global
0108: * defaults in your JDK's "jre/lib/logging.properties" file.
0109: * </p><p><pre>
0110: * java.util.logging.ConsoleHandler.level = FINE
0111: * com.sun.rave.web.ui.component.TableRowGroup.level = FINE
0112: * </pre></p><p>
0113: * See TLD docs for more information.
0114: * </p>
0115: */
0116: public class TableRowGroup extends TableRowGroupBase implements
0117: NamingContainer {
0118: /** The id for the column footer bar. */
0119: public static final String COLUMN_FOOTER_BAR_ID = "_columnFooterBar"; //NOI18N
0120:
0121: /** The id for the column header bar. */
0122: public static final String COLUMN_HEADER_BAR_ID = "_columnHeaderBar"; //NOI18N
0123:
0124: /** The component id for the empty data column. */
0125: public static final String EMPTY_DATA_COLUMN_ID = "_emptyDataColumn"; //NOI18N
0126:
0127: /** The facet name for the empty data column. */
0128: public static final String EMPTY_DATA_COLUMN_FACET = "emptyDataColumn"; //NOI18N
0129:
0130: /** The component id for the empty data text. */
0131: public static final String EMPTY_DATA_TEXT_ID = "_emptyDataText"; //NOI18N
0132:
0133: /** The facet name for the empty data text. */
0134: public static final String EMPTY_DATA_TEXT_FACET = "emptyDataText"; //NOI18N
0135:
0136: /** The facet name for the group footer area. */
0137: public static final String FOOTER_FACET = "footer"; //NOI18N
0138:
0139: /** The id for the group footer bar. */
0140: public static final String GROUP_FOOTER_BAR_ID = "_groupFooterBar"; //NOI18N
0141:
0142: /** The component id for the group footer. */
0143: public static final String GROUP_FOOTER_ID = "_groupFooter"; //NOI18N
0144:
0145: /** The facet name for the group footer. */
0146: public static final String GROUP_FOOTER_FACET = "groupFooter"; //NOI18N
0147:
0148: /** The id for the table row group header bar. */
0149: public static final String GROUP_HEADER_BAR_ID = "_groupHeaderBar"; //NOI18N
0150:
0151: /** The component id for the table row group header. */
0152: public static final String GROUP_HEADER_ID = "_groupHeader"; //NOI18N
0153:
0154: /** The facet name for the table row group header. */
0155: public static final String GROUP_HEADER_FACET = "groupHeader"; //NOI18N
0156:
0157: /** The facet name for the group header area. */
0158: public static final String HEADER_FACET = "header"; //NOI18N
0159:
0160: /** The id for the table column footers bar. */
0161: public static final String TABLE_COLUMN_FOOTER_BAR_ID = "_tableColumnFooterBar"; //NOI18N
0162:
0163: // Key prefix for properties cached in the request map.
0164: private static final String REQUEST_KEY_PREFIX = "com.sun.rave.web.ui_"; //NOI18N
0165:
0166: // Key for properties cached in the request map.
0167: private static final String PROPERTIES = "_properties"; //NOI18N
0168:
0169: // This map contains SavedState instances for each descendant
0170: // component, keyed by the client identifier of the descendant. Because
0171: // descendant client identifiers will contain the RowKey value of the
0172: // parent, per-row state information is actually preserved.
0173: private Map saved = new HashMap();
0174:
0175: // TableDataFilter object used to apply filter. This object is not part of
0176: // the saved and restored state of the component.
0177: private transient TableDataFilter filter = null;
0178:
0179: // TableDataSorter object used to apply sort. This object is not part of
0180: // the saved and restored state of the component.
0181: private transient TableDataSorter sorter = null;
0182:
0183: // Flag indicating paginated state.
0184: private boolean paginated = false;
0185: private boolean paginated_set = false;
0186:
0187: // The TableRowDataProvider associated with this component, lazily
0188: // instantiated if requested. This object is not part of the saved and
0189: // restored state of the component.
0190: private TableRowDataProvider provider = null;
0191:
0192: // The Table ancestor enclosing this component.
0193: private Table table = null;
0194:
0195: /** Default constructor */
0196: public TableRowGroup() {
0197: super ();
0198: }
0199:
0200: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0201: // Child methods
0202: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0203:
0204: /**
0205: * Get the closest Table ancestor that encloses this component.
0206: *
0207: * @return The Table ancestor.
0208: */
0209: public Table getTableAncestor() {
0210: // Don't cache in the request since it's used as the properties key.
0211: if (table == null) {
0212: UIComponent component = this ;
0213: while (component != null) {
0214: component = component.getParent();
0215: if (component instanceof Table) {
0216: table = (Table) component;
0217: break;
0218: }
0219: }
0220: }
0221: return table;
0222: }
0223:
0224: /**
0225: * Get an Iterator over the TableColumn children found for
0226: * this component.
0227: *
0228: * @return An Iterator over the TableColumn children.
0229: */
0230: public Iterator getTableColumnChildren() {
0231: // Get properties cached in request map.
0232: Properties properties = getProperties();
0233: List tableColumnChildren = (properties != null) ? properties
0234: .getTableColumnChildren() : null;
0235:
0236: // Get TableColumn children.
0237: if (tableColumnChildren == null) {
0238: tableColumnChildren = new ArrayList();
0239: Iterator kids = getChildren().iterator();
0240: while (kids.hasNext()) {
0241: UIComponent kid = (UIComponent) kids.next();
0242: if ((kid instanceof TableColumn)) {
0243: tableColumnChildren.add(kid);
0244: }
0245: }
0246: // Save property in request map.
0247: if (properties != null) {
0248: properties.setTableColumnChildren(tableColumnChildren);
0249: }
0250: }
0251: return tableColumnChildren.iterator();
0252: }
0253:
0254: /**
0255: * Get the number of columns found for this component that have a rendered
0256: * property of true.
0257: *
0258: * @return The number of rendered columns.
0259: */
0260: public int getColumnCount() {
0261: // Get properties cached in request map.
0262: Properties properties = getProperties();
0263: int columnCount = (properties != null) ? properties
0264: .getColumnCount() : -1;
0265:
0266: // Get column count.
0267: if (columnCount == -1) {
0268: columnCount = 0; // Initialize min value.
0269: Iterator kids = getTableColumnChildren();
0270: while (kids.hasNext()) {
0271: TableColumn col = (TableColumn) kids.next();
0272: columnCount += col.getColumnCount();
0273: }
0274: // Save property in request map.
0275: if (properties != null) {
0276: properties.setColumnCount(columnCount);
0277: }
0278: }
0279: return columnCount;
0280: }
0281:
0282: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0283: // Component methods
0284: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0285:
0286: /**
0287: * Get empty data column.
0288: *
0289: * @return The empty data column.
0290: */
0291: public UIComponent getEmptyDataColumn() {
0292: UIComponent facet = getFacet(EMPTY_DATA_COLUMN_FACET);
0293: if (facet != null) {
0294: return facet;
0295: }
0296:
0297: // Get child.
0298: TableColumn child = new TableColumn();
0299: child.setId(EMPTY_DATA_COLUMN_ID);
0300: child.setColSpan(getColumnCount());
0301: child.getChildren().add(getEmptyDataText());
0302:
0303: // Save facet and return child.
0304: getFacets().put(child.getId(), child);
0305: return child;
0306: }
0307:
0308: /**
0309: * Get empty data text.
0310: *
0311: * @return The empty data text.
0312: */
0313: public UIComponent getEmptyDataText() {
0314: UIComponent facet = getFacet(EMPTY_DATA_TEXT_FACET);
0315: if (facet != null) {
0316: return facet;
0317: }
0318:
0319: Theme theme = getTheme();
0320:
0321: // Get message.
0322: String msg = null;
0323: if (getEmptyDataMsg() != null) {
0324: msg = getEmptyDataMsg();
0325: } else {
0326: // Get unfiltered row keys.
0327: RowKey[] rowKeys = getRowKeys();
0328: if (rowKeys != null && rowKeys.length > 0) {
0329: msg = theme.getMessage("table.filteredData"); //NOI18N
0330: } else {
0331: msg = theme.getMessage("table.emptyData"); //NOI18N
0332: }
0333: }
0334:
0335: // Get child.
0336: StaticText child = new StaticText();
0337: child.setId(EMPTY_DATA_TEXT_ID);
0338: child.setStyleClass(theme
0339: .getStyleClass(ThemeStyles.TABLE_MESSAGE_TEXT));
0340: child.setText(msg);
0341:
0342: // Save facet and return child.
0343: getFacets().put(child.getId(), child);
0344: return child;
0345: }
0346:
0347: /**
0348: * Get group footer.
0349: *
0350: * @return The group footer.
0351: */
0352: public UIComponent getGroupFooter() {
0353: UIComponent facet = getFacet(GROUP_FOOTER_FACET);
0354: if (facet != null) {
0355: return facet;
0356: }
0357:
0358: // Get child.
0359: TableFooter child = new TableFooter();
0360: child.setId(GROUP_FOOTER_ID);
0361: child.setColSpan(getColumnCount());
0362: child.setExtraHtml(getExtraFooterHtml());
0363: child.setGroupFooter(true);
0364:
0365: // Set rendered.
0366: facet = getFacet(FOOTER_FACET);
0367: if (!(facet != null && facet.isRendered() || getFooterText() != null)) {
0368: child.setRendered(false);
0369: } else {
0370: log("getGroupFooter", //NOI18N
0371: "Group footer not rendered, nothing to display"); //NOI18N
0372: }
0373:
0374: // Save facet and return child.
0375: getFacets().put(child.getId(), child);
0376: return child;
0377: }
0378:
0379: /**
0380: * Get group header.
0381: *
0382: * @return The group header.
0383: */
0384: public UIComponent getGroupHeader() {
0385: UIComponent facet = getFacet(GROUP_HEADER_FACET);
0386: if (facet != null) {
0387: return facet;
0388: }
0389:
0390: // Get child.
0391: TableHeader child = new TableHeader();
0392: child.setId(GROUP_HEADER_ID);
0393: child.setScope("colgroup"); //NOI18N
0394: child.setColSpan(getColumnCount());
0395: child.setExtraHtml(getExtraHeaderHtml());
0396: child.setGroupHeader(true);
0397:
0398: // Don't render for an empty table.
0399: boolean emptyTable = getRowCount() == 0;
0400: boolean renderControls = !emptyTable
0401: && (isSelectMultipleToggleButton() || isGroupToggleButton());
0402:
0403: // Set rendered.
0404: facet = getFacet(HEADER_FACET);
0405: if (!(facet != null && facet.isRendered()
0406: || getHeaderText() != null || renderControls)) {
0407: child.setRendered(false);
0408: } else {
0409: log("getGroupHeader", //NOI18N
0410: "Group header not rendered, nothing to display"); //NOI18N
0411: }
0412:
0413: // Save facet and return child.
0414: getFacets().put(child.getId(), child);
0415: return child;
0416: }
0417:
0418: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0419: // Filter methods
0420: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0421:
0422: /**
0423: * Clear FilterCriteria objects from the TableDataFilter instance used by
0424: * this component.
0425: * <p>
0426: * Note: This method clears the cached filter and sort, then resets
0427: * pagination to the first page per UI guidelines.
0428: * </p>
0429: */
0430: public void clearFilter() {
0431: getTableDataFilter().setFilterCriteria(null); // Clear all FilterCriteria.
0432: setFirst(0); // Reset to first page.
0433:
0434: // Clear properties cached in request map.
0435: Properties properties = getProperties();
0436: if (properties != null) {
0437: properties.setFilteredRowKeys(null); // Clear filtered row keys.
0438: properties.setSortedRowKeys(null); // Clear sorted row keys.
0439: } else {
0440: log("clearFilter", //NOI18N
0441: "Cannot clear filtered and sorted row keys, Properties is null"); //NOI18N
0442: }
0443: }
0444:
0445: /**
0446: * Get an array containing filtered RowKey objects.
0447: * <p>
0448: * Note: This filter depends on the FilterCriteria objects provided to the
0449: * TableDataFilter instance used by this component. Due to filtering, the
0450: * size of the returned array may be less than the total number of RowKey
0451: * objects for the underlying TableDataProvider.
0452: * </p><p>
0453: * Note: The returned RowKey objects are cached. If the TableDataFilter
0454: * instance used by this component is modified directly, invoke the
0455: * clearFilter method to clear the previous filter.
0456: * </p>
0457: * @return An array containing filtered RowKey objects.
0458: */
0459: public RowKey[] getFilteredRowKeys() {
0460: // Get properties cached in request map.
0461: Properties properties = getProperties();
0462: RowKey[] filteredRowKeys = (properties != null) ? properties
0463: .getFilteredRowKeys() : null;
0464:
0465: // Initialize RowKey objects, if not cached already.
0466: if (filteredRowKeys != null) {
0467: return filteredRowKeys;
0468: } else {
0469: filteredRowKeys = getRowKeys();
0470: }
0471:
0472: // Do not attempt to filter with a null provider.
0473: TableDataProvider provider = getTableRowDataProvider()
0474: .getTableDataProvider();
0475: if (provider == null) {
0476: log("getFilteredRowKeys", //NOI18N
0477: "Cannot obtain filtered row keys, TableDataProvider is null"); //NOI18N
0478: return filteredRowKeys;
0479: }
0480:
0481: // If TableDataFilter and TableDataProvider are the same instance, the
0482: // filter method is never called. The filter order is assumed to be
0483: // intrinsic in the row data of the TableDataProvider.
0484: TableDataFilter filter = getTableDataFilter();
0485: if (provider != filter) {
0486: filteredRowKeys = filter.filter(provider, filteredRowKeys);
0487: } else {
0488: log(
0489: "getFilteredRowKeys", //NOI18N
0490: "Row keys already filtered, TableDataFilter and TableDataProvider are the same instance"); //NOI18N
0491: }
0492:
0493: // Save properties.
0494: if (properties != null) {
0495: properties.setFilteredRowKeys(filteredRowKeys);
0496: } else {
0497: log("getFilteredRowKeys", //NOI18N
0498: "Cannot save filtered row keys, Properties is null"); //NOI18N
0499: }
0500: return filteredRowKeys;
0501: }
0502:
0503: /**
0504: * Get the TableDataFilter object used to filter rows.
0505: *
0506: * @return The TableDataFilter object used to filter rows.
0507: */
0508: public TableDataFilter getTableDataFilter() {
0509: // Method is overriden because TableDataFilter is not serializable.
0510: TableDataFilter tdf = super .getTableDataFilter();
0511: if (tdf != null) {
0512: return tdf;
0513: }
0514:
0515: // Get default filter.
0516: if (filter == null) {
0517: filter = new BasicTableDataFilter();
0518: }
0519: return filter;
0520: }
0521:
0522: /**
0523: * Set FilterCriteria objects for the TableDataFilter instance used by this
0524: * component.
0525: * <p>
0526: * Note: This method clears the cached filter and sort, then resets
0527: * pagination to the first page per UI guidelines.
0528: * </p>
0529: * @param filterCriteria An array of FilterCriteria objects defining the
0530: * filter order on this TableDataFilter.
0531: */
0532: public void setFilterCriteria(FilterCriteria[] filterCriteria) {
0533: clearFilter();
0534: getTableDataFilter().setFilterCriteria(filterCriteria);
0535: }
0536:
0537: /**
0538: * Set the TableDataFilter object used to filter rows.
0539: *
0540: * @param filter The TableDataFilter object used to filter rows.
0541: */
0542: public void setTableDataFilter(TableDataFilter filter) {
0543: // Method is overriden because TableDataFilter is not serializable.
0544: this .filter = filter;
0545: }
0546:
0547: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0548: // Pagination methods
0549: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0550:
0551: /**
0552: * Get the zero-relative row number of the first row to be displayed for
0553: * a paginated table.
0554: * <p>
0555: * Note: When ever a new DataProvider is used, UI Guiedlines recommend that
0556: * pagination should be reset (e.g., remaining on the 4th page of a new set
0557: * of data makes no sense).
0558: * </p><p>
0559: * Note: If rows have been removed from the table, there is a chance that
0560: * the first row could be greater than the total number of rows. In this
0561: * case, the zero-relative row number of the last page to be displayed is
0562: * returned.
0563: * </p>
0564: * @return The zero-relative row number of the first row to be displayed.
0565: */
0566: public int getFirst() {
0567: // Ensure the first row is less than the row number of the last page.
0568: int last = getLast();
0569: int first = isPaginated() ? Math.max(0, super .getFirst()) : 0;
0570: return (first < last) ? first : last;
0571: }
0572:
0573: /**
0574: * Set the zero-relative row number of the first row to be displayed for
0575: * a paginated table.
0576: *
0577: * @param first The first row number.
0578: * @exception IllegalArgumentException for negative values.
0579: */
0580: public void setFirst(int first) {
0581: if (first < 0) {
0582: log("setFirst", "First row cannot be < 0"); //NOI18N
0583: throw new IllegalArgumentException(Integer.toString(first));
0584: }
0585: super .setFirst(first);
0586: }
0587:
0588: /**
0589: * Get the zero-relative row number of the last page to be displayed.
0590: *
0591: * @return The zero-relative row number of the last page to be displayed.
0592: */
0593: public int getLast() {
0594: return Math.max(0, getPages() - 1) * getRows();
0595: }
0596:
0597: /**
0598: * Get current page number to be displayed.
0599: * <p>
0600: * Note: The default is 1 when the table is not paginated.
0601: * </p>
0602: * @return The current page number to be displayed.
0603: */
0604: public int getPage() {
0605: if (!isPaginated()) { // Rows is zero when paginated.
0606: return 1;
0607: }
0608: return (getFirst() / getRows()) + 1;
0609: }
0610:
0611: /**
0612: * Get total number of pages to be displayed. The default is 1 when the
0613: * table is not paginated.
0614: * <p>
0615: * Note: The page count depends on the FilterCriteria objects provided to
0616: * the TableDataFilter instance used by this component. Further, the filter
0617: * used to obtain the page count is cached. If the TableDataFilter instance
0618: * used by this component is to be modified directly, invoke the clearFilter
0619: * method to clear the previous filter.
0620: * </p>
0621: * @return The total number of pages to be displayed.
0622: */
0623: public int getPages() {
0624: if (!isPaginated()) {
0625: return 1;
0626: }
0627:
0628: int rowCount = getRowCount(); // Get row count.
0629: int rows = getRows(); // Get rows per page.
0630:
0631: // Note: Rows should be > 0 when paginated.
0632: int modulus = (rows > 0) ? rowCount % rows : 0;
0633: int result = (rows > 0) ? rowCount / rows : 1;
0634:
0635: // Increment result for extra rows.
0636: return (modulus > 0) ? ++result : result;
0637: }
0638:
0639: /**
0640: * Test the paginated state of this component.
0641: * <p>
0642: * Note: If the paginationControls property of the Table component is true,
0643: * this property will be initialized as true.
0644: * </p>
0645: * @return true for paginate mode, false for scroll mode.
0646: */
0647: public boolean isPaginated() {
0648: if (!paginated_set) {
0649: Table table = getTableAncestor();
0650: if (table != null) {
0651: setPaginated(table.isPaginationControls());
0652: } else {
0653: log("isPaginated", //NOI18N
0654: "Cannot initialize paginated state, Table is null"); //NOI18N
0655: }
0656: }
0657: return paginated;
0658: }
0659:
0660: /**
0661: * A convenience method to set the current page to be displayed.
0662: * <p>
0663: * Note: You can also set the current, first, next, prev, and last pages by
0664: * invoking the setFirst(int) method directly. For example, you could use
0665: * setFirst(0) to display the first page and setFirst(getLast()) to display
0666: * the last page. The setFirst(int) method is particularly useful when a
0667: * subset of data is displayed in scroll mode or when overriding pagination.
0668: * </p><p>
0669: * Note: When ever a new DataProvider is used, UI Guiedlines recommend that
0670: * pagination should be reset (e.g., remaining on the 4th page of a new set
0671: * of data makes no sense).
0672: * </p>
0673: * @param page The current page.
0674: */
0675: public void setPage(int page) {
0676: // Set the starting row for the new page.
0677: int row = (page - 1) * getRows();
0678:
0679: // Result cannot be greater than the row index for the last page.
0680: int result = Math.min(row, getLast());
0681:
0682: // Result cannot be greater than total number of rows or less than zero.
0683: setFirst(Math.min(Math.max(result, 0), getRowCount()));
0684: }
0685:
0686: /**
0687: * Set the paginated state of this component.
0688: * <p>
0689: * Note: When pagination controls are used, a value of true allows both
0690: * pagination controls and paginate buttons to be displayed. A value of
0691: * false allows only paginate buttons to be displayed. However, when all
0692: * data fits on one page, neither pagination controls or paginate buttons
0693: * are displayed.
0694: * </p><p>
0695: * Note: To properly maintain the paginated state of the table per UI
0696: * guidelines, the paginated property is cached. If the paginationControls
0697: * property of the table component changes (e.g., in an application builder
0698: * environment), use this method to set the paginated property accordingly.
0699: * </p>
0700: * @param paginated The paginated state of this component.
0701: */
0702: public void setPaginated(boolean paginated) {
0703: this .paginated = paginated;
0704: paginated_set = true;
0705: }
0706:
0707: /**
0708: * Get the number of rows to be displayed for a paginated table.
0709: * <p>
0710: * Note: UI guidelines recommend a default value of 25 rows per page.
0711: * </p>
0712: * @return The number of rows to be displayed for a paginated table.
0713: */
0714: public int getRows() {
0715: return isPaginated() ? Math.max(1, super .getRows()) : 0;
0716: }
0717:
0718: /**
0719: * Set the number of rows to be displayed for a paginated table.
0720: *
0721: * @param rows The number of rows to be displayed for a paginated table.
0722: * @exception IllegalArgumentException for negative values.
0723: */
0724: public void setRows(int rows) {
0725: if (rows < 0) {
0726: log("setRows", "Paginated rows cannot be < 0"); //NOI18N
0727: throw new IllegalArgumentException(Integer.toString(rows));
0728: }
0729: super .setRows(rows);
0730: }
0731:
0732: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0733: // Row methods
0734: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0735:
0736: /**
0737: * Get the flag indicating whether there is row data available for the
0738: * current RowKey. If no row data is available, false is returned.
0739: *
0740: * @return The flag indicating whether there is row data available.
0741: */
0742: public boolean isRowAvailable() {
0743: boolean result = false;
0744: TableDataProvider provider = getTableRowDataProvider()
0745: .getTableDataProvider();
0746: if (provider != null) {
0747: result = provider.isRowAvailable(getRowKey());
0748: } else {
0749: log("isRowAvailable", //NOI18N
0750: "Cannot determine if row is available, TableDataProvider is null"); //NOI18N
0751: }
0752: return result;
0753: }
0754:
0755: /**
0756: * Get an array of hidden RowKey objects from the underlying
0757: * TableDataProvider taking filtering, sorting, and pagination into account.
0758: * <p>
0759: * Note: The returned RowKey objects depend on the FilterCriteria and
0760: * SortCriteria objects provided to the TableDataFilter and TableDataSorter
0761: * instances used by this component. If TableDataFilter and TableDataSorter
0762: * are modified directly, invoke the clearSort and clearFilter method to
0763: * clear the previous sort and filter.
0764: * </p>
0765: * @return An array of RowKey objects.
0766: */
0767: public RowKey[] getHiddenRowKeys() {
0768: if (!isPaginated()) {
0769: return null; // No rows are hidden during scroll mode.
0770: }
0771:
0772: // Get sorted RowKey objects.
0773: RowKey[] rowKeys = getSortedRowKeys();
0774: if (rowKeys == null) {
0775: return rowKeys;
0776: }
0777:
0778: // Find the number of selected rows hidden from view.
0779: ArrayList list = new ArrayList();
0780: int first = getFirst();
0781: int rows = getRows();
0782: for (int i = 0; i < rowKeys.length; i++) {
0783: // Have we displayed the paginated number of rows?
0784: if (i >= first && i < first + rows) {
0785: continue;
0786: }
0787: list.add(rowKeys[i]);
0788: }
0789: rowKeys = new RowKey[list.size()];
0790: return (RowKey[]) list.toArray(rowKeys);
0791: }
0792:
0793: /**
0794: * Get the FieldKey from the underlying TableDataProvider.
0795: *
0796: * @param fieldId The id of the requested FieldKey.
0797: * @return The RowKey from the underlying TableDataProvider.
0798: */
0799: public FieldKey getFieldKey(String fieldId) {
0800: return getTableRowDataProvider().getFieldKey(fieldId);
0801: }
0802:
0803: /**
0804: * Get the number of rows in the underlying TableDataProvider. If the
0805: * number of available rows is unknown, -1 is returned.
0806: * <p>
0807: * Note: This row count depends on the FilterCriteria objects provided to
0808: * the TableDataFilter instance used by this component. Further, the filter
0809: * used to obtain the row count is cached. If the TableDataFilter instance
0810: * used by this component is modified directly, invoke the clearFilter
0811: * method to clear the previous filter.
0812: * </p>
0813: * @return The number of rows in the underlying TableDataProvider.
0814: */
0815: public int getRowCount() {
0816: RowKey[] rowKeys = getFilteredRowKeys();
0817: return (rowKeys != null) ? rowKeys.length : 0;
0818: }
0819:
0820: /**
0821: * Get the RowKey associated with the current row.
0822: *
0823: * @return The RowKey associated with the current row.
0824: */
0825: public RowKey getRowKey() {
0826: return getTableRowDataProvider().getTableRow();
0827: }
0828:
0829: /**
0830: * Get all RowKey objects for the underlying TableDataProvider.
0831: *
0832: * @return All RowKey objects for the underlying TableDataProvider.
0833: */
0834: public RowKey[] getRowKeys() {
0835: RowKey[] rowKeys = null;
0836: TableDataProvider provider = getTableRowDataProvider()
0837: .getTableDataProvider();
0838: if (provider == null) {
0839: log("getRowKeys", //NOI18N
0840: "Cannot obtain row keys, TableDataProvider is null"); //NOI18N
0841: return rowKeys;
0842: }
0843:
0844: // Create fake data for design-time behavior. The ResultSetDataProvider
0845: // returns 3 rows of dummy data; however, this is not enough to display
0846: // pagination controls properly. When all rows fit on a single page, or
0847: // when we have an empty table, certain controls are hidden from view.
0848: // Thus, if a user specifies 20 rows per page, we want to create 20 + 1
0849: // rows of data forcing controls to be displayed.
0850: if (Beans.isDesignTime()) {
0851: log("getRowKeys",
0852: "Creating dummy data for design-time behavior"); //NOI18N
0853: rowKeys = provider.getRowKeys(provider.getRowCount(), null);
0854: // If pagination is not enabled, dummy data is not required.
0855: if (getRows() == 0 || rowKeys == null
0856: || rowKeys.length == 0) {
0857: log("getRowKeys", //NOI18N
0858: "Cannot create dummy data, DataProvider has no rows"); //NOI18N
0859: return rowKeys;
0860: } else {
0861: ArrayList list = new ArrayList();
0862: for (int i = 0; i < getRows() + 1; i++) {
0863: list.add(rowKeys[i % rowKeys.length]);
0864: }
0865: rowKeys = new RowKey[list.size()];
0866: return ((RowKey[]) list.toArray(rowKeys));
0867: }
0868: }
0869:
0870: // It's possible that the provider returned -1 because it does not
0871: // actually have all the rows, so it's up to the consumer of the
0872: // interface to fetch them. Typically, 99% of the data providers will
0873: // return a valid row count (at least our providers will), but we still
0874: // need to handle the scenario where -1 is returned.
0875: int rowCount = provider.getRowCount();
0876: if (rowCount == -1) {
0877: log("getRowKeys", //NOI18N
0878: "Manually calculating row count, DataProvider.getRowCount() is -1"); //NOI18N
0879: int index = 0;
0880: do {
0881: // Keep trying until all rows are obtained.
0882: rowCount = 1000000 * ++index;
0883: rowKeys = provider.getRowKeys(rowCount, null);
0884: } while (rowKeys != null && rowKeys.length - 1 == rowCount);
0885: } else {
0886: rowKeys = provider.getRowKeys(rowCount, null);
0887: }
0888: return rowKeys;
0889: }
0890:
0891: /**
0892: * Get the TableRowDataProvider object representing the data objects that
0893: * we will iterate over in this component's rendering.
0894: *
0895: * @return The TableRowDataProvider object.
0896: */
0897: protected TableRowDataProvider getTableRowDataProvider() {
0898: // Get properties cached in request map.
0899: Properties properties = getProperties();
0900: TableRowDataProvider provider = (properties != null) ? properties
0901: .getTableRowDataProvider()
0902: : null;
0903:
0904: // Get provider.
0905: if (provider == null) {
0906: log("getTableRowDataProvider", //NOI18N
0907: "Re-evaluating sourceData, TableRowDataProvider is null"); //NOI18N
0908:
0909: // Synthesize a TableDataProvider around source data, if possible.
0910: TableDataProvider tdp;
0911: Object obj = getSourceData();
0912: if (obj == null) {
0913: tdp = null;
0914: } else if (obj instanceof TableDataProvider) {
0915: tdp = (TableDataProvider) obj;
0916: } else if (obj instanceof List) {
0917: tdp = new ObjectListDataProvider((List) obj);
0918: } else if (Object[].class.isAssignableFrom(obj.getClass())) {
0919: tdp = new ObjectArrayDataProvider((Object[]) obj);
0920: } else {
0921: // Default "single variable" case.
0922: ArrayList list = new ArrayList(1);
0923: list.add(obj);
0924: tdp = new ObjectListDataProvider(list);
0925: }
0926: provider = new TableRowDataProvider(tdp);
0927:
0928: // Save property in request map.
0929: if (properties != null) {
0930: properties.setTableRowDataProvider(provider);
0931: } else {
0932: log("getTableRowDataProvider", //NOI18N
0933: "Cannot save TableRowDataProvider, Properties is null"); //NOI18N
0934: }
0935: }
0936: return provider;
0937: }
0938:
0939: /**
0940: * Get the data type of the data element referenced by the given FieldKey.
0941: *
0942: * @param fieldKey The FieldKey identifying the data element whose type is
0943: * to be returned.
0944: * @return The data type of the data element referenced by the given FieldKey.
0945: */
0946: public Class getType(FieldKey fieldKey) {
0947: return getTableRowDataProvider().getType(fieldKey);
0948: }
0949:
0950: /**
0951: * Get an array of rendered RowKey objects from the underlying
0952: * TableDataProvider taking filtering, sorting, and pagination into account.
0953: * <p>
0954: * Note: The returned RowKey objects depend on the FilterCriteria and
0955: * SortCriteria objects provided to the TableDataFilter and TableDataSorter
0956: * instances used by this component. If TableDataFilter and TableDataSorter
0957: * are modified directly, invoke the clearSort and clearFilter method to
0958: * clear the previous sort and filter.
0959: * </p>
0960: * @return An array of RowKey objects.
0961: */
0962: public RowKey[] getRenderedRowKeys() {
0963: // Get sorted RowKey objects.
0964: RowKey[] rowKeys = getSortedRowKeys();
0965: if (rowKeys == null) {
0966: return rowKeys;
0967: }
0968:
0969: // Find the number of selected rows hidden from view.
0970: ArrayList list = new ArrayList();
0971: int first = getFirst();
0972: int rows = getRows();
0973: for (int i = first; i < rowKeys.length; i++) {
0974: // Have we displayed the paginated number of rows?
0975: if (isPaginated() && i >= first + rows) {
0976: break;
0977: }
0978: list.add(rowKeys[i]);
0979: }
0980: rowKeys = new RowKey[list.size()];
0981: return (RowKey[]) list.toArray(rowKeys);
0982: }
0983:
0984: /**
0985: * Set the RowKey associated with the current row or null for no current row
0986: * association.
0987: * <p>
0988: * Note: It is possible to set the RowKey at a value for which the
0989: * underlying TableDataProvider does not contain any row data. Therefore,
0990: * callers may use the isRowAvailable() method to detect whether row data
0991: * will be available.
0992: * <ul>
0993: * <li>Save current state information for all descendant components (as
0994: * described below).
0995: * <li>Store the new RowKey, and pass it on to the TableDataProvider
0996: * associated with this TableRowGroup instance.</li>
0997: * <li>If the new RowKey value is null:
0998: * <ul>
0999: * <li>If the var property is not null,
1000: * remove the corresponding request scope attribute (if any).</li>
1001: * <li>Reset the state information for all descendant components
1002: * (as described below).</li>
1003: * </ul></li>
1004: * <li>If the new RowKey value is not null:
1005: * <ul>
1006: * <li>If the var property is not null, expose the
1007: * data provider as a request scope attribute whose key is the
1008: * var property value.</li>
1009: * <li>Reset the state information for all descendant components
1010: * (as described below).
1011: * </ul></li>
1012: * </ul></p><p>
1013: * To save current state information for all descendant components,
1014: * TableRowGroup must maintain per-row information for each descendant as
1015: * follows:
1016: * <ul>
1017: * <li>If the descendant is an instance of EditableValueHolder,
1018: * save the state of its localValue property.</li>
1019: * <li>If the descendant is an instance of EditableValueHolder,
1020: * save the state of the localValueSet property.</li>
1021: * <li>If the descendant is an instance of EditableValueHolder,
1022: * save the state of the valid property.</li>
1023: * <li>If the descendant is an instance of EditableValueHolder,
1024: * save the state of the submittedValue property.</li>
1025: * </ul></p><p>
1026: * To restore current state information for all descendant components,
1027: * TableRowGroup must reference its previously stored information
1028: * for the current RowKey and call setters for each descendant
1029: * as follows:
1030: * <ul>
1031: * <li>If the descendant is an instance of EditableValueHolder,
1032: * restore the value property.</li>
1033: * <li>If the descendant is an instance of EditableValueHolder,
1034: * restore the state of the localValueSet property.</li>
1035: * <li>If the descendant is an instance of EditableValueHolder,
1036: * restore the state of the valid property.</li>
1037: * <li>If the descendant is an instance of EditableValueHolder,
1038: * restore the state of the submittedValue property.</li>
1039: * </ul></p>
1040: *
1041: * @param rowKey The RowKey associated with the current row or
1042: * null for no association.
1043: */
1044: public void setRowKey(RowKey rowKey) {
1045: // Save current state for the previous row.
1046: saveDescendantState();
1047:
1048: // Update to the new row.
1049: getTableRowDataProvider().setTableRow(rowKey);
1050:
1051: // Clear or expose the current row data as a request scope attribute
1052: String sourceVar = getSourceVar();
1053: if (sourceVar != null) {
1054: Map requestMap = getFacesContext().getExternalContext()
1055: .getRequestMap();
1056: if (rowKey == null) {
1057: requestMap.remove(sourceVar);
1058: } else if (isRowAvailable()) {
1059: requestMap.put(sourceVar, getTableRowDataProvider());
1060: } else {
1061: requestMap.remove(sourceVar);
1062: }
1063: } else {
1064: log("setRowKey",
1065: "Cannot set row key, sourceVar property is null"); //NOI18N
1066: }
1067:
1068: // Reset current state information for the new row.
1069: restoreDescendantState();
1070: }
1071:
1072: /**
1073: * Set the source data of the TableRowGroup.
1074: * <p>
1075: * Note: When ever a new DataProvider is used, UI Guiedlines recommend that
1076: * pagination should be reset (e.g., remaining on the 4th page of a new set
1077: * of data makes no sense). However, properties such as the sort and filter
1078: * criteria should not automatically be cleared (e.g., there may be
1079: * situations where one or both should be left as specified by the user). In
1080: * this scenario, pagination is set to the first page.
1081: * </p>
1082: * @param sourceData The source data of the TableRowGroup.
1083: */
1084: public void setSourceData(Object sourceData) {
1085: super .setSourceData(sourceData);
1086: init();
1087: }
1088:
1089: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1090: // Selected methods
1091: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1092:
1093: /**
1094: * Get the number of objects from the underlying data provider where the
1095: * selected property of this component is set to true and the row is
1096: * currently hidden from view.
1097: * <p>
1098: * Note: UI guidelines recomend that rows should be unselected when no
1099: * longer in view. For example, when a user selects rows of the table and
1100: * navigates to another page. Or, when a user applies a filter or sort that
1101: * may hide previously selected rows from view. If a user invokes an action
1102: * to delete the currently selected rows, they may inadvertently remove rows
1103: * not displayed on the current page. That said, there are cases when
1104: * maintaining state across table pages is necessary. When maintaining state
1105: * and there are currently no hidden selections, UI guidelines recomend that
1106: * the number zero should be shown.
1107: * </p><p>
1108: * Note: This count depends on the FilterCriteria and SortCriteria objects
1109: * provided to the TableDataFilter and TableDataSorter instances used by
1110: * this component. If TableDataFilter and TableFilterSorter are modified
1111: * directly, invoke the clearFilter method to clear the previous filter and
1112: * sort.
1113: * </p>
1114: * @return The number of selected rows currently hidden from view.
1115: */
1116: public int getHiddenSelectedRowsCount() {
1117: RowKey[] rowKeys = getHiddenSelectedRowKeys();
1118: return (rowKeys != null) ? rowKeys.length : 0;
1119: }
1120:
1121: /**
1122: * Get an array of RowKey objects from the underlying data provider where
1123: * the selected property of this component is set to true and the row is
1124: * currently hidden from view.
1125: * <p>
1126: * Note: UI guidelines recomend that rows should be unselected when no
1127: * longer in view. For example, when a user selects rows of the table and
1128: * navigates to another page. Or, when a user applies a filter or sort that
1129: * may hide previously selected rows from view. If a user invokes an action
1130: * to delete the currently selected rows, they may inadvertently remove rows
1131: * not displayed on the current page.
1132: * </p><p>
1133: * Note: The returned RowKey objects depend on the FilterCriteria and
1134: * SortCriteria objects provided to the TableDataFilter and TableDataSorter
1135: * instances used by this component. If TableDataFilter and TableDataSorter
1136: * are modified directly, invoke the clearSort and clearFilter method to
1137: * clear the previous sort and filter.
1138: * </p>
1139: * @return An array of RowKey objects.
1140: */
1141: public RowKey[] getHiddenSelectedRowKeys() {
1142: // Get hidden RowKey objects.
1143: RowKey[] rowKeys = getHiddenRowKeys();
1144: if (rowKeys == null) {
1145: return rowKeys;
1146: }
1147:
1148: // Save the current RowKey.
1149: RowKey rowKey = getRowKey();
1150:
1151: // Find the number of selected rows hidden from view.
1152: ArrayList list = new ArrayList();
1153: for (int i = 0; i < rowKeys.length; i++) {
1154: setRowKey(rowKeys[i]);
1155: if (isRowAvailable() && isSelected()) {
1156: list.add(rowKeys[i]);
1157: }
1158: }
1159: setRowKey(rowKey); // Restore the current RowKey.
1160: rowKeys = new RowKey[list.size()];
1161: return (RowKey[]) list.toArray(rowKeys);
1162: }
1163:
1164: /**
1165: * Get the number of selected rows from the underlying data provider where
1166: * the selected property of this component is set to true.
1167: * <p>
1168: * Note: This count depends on the FilterCriteria objects provided to the
1169: * TableDataFilter instance used by this component. If TableDataFilter is
1170: * modified directly, invoke the clearFilter method to clear the previous
1171: * filter.
1172: * </p>
1173: * @return The number of selected rows.
1174: */
1175: public int getSelectedRowsCount() {
1176: RowKey[] rowKeys = getSelectedRowKeys();
1177: return (rowKeys != null) ? rowKeys.length : 0;
1178: }
1179:
1180: /**
1181: * Get an array of RowKey objects from the underlying data provider where
1182: * the selected property of this component is set to true.
1183: * <p>
1184: * Note: The returned RowKey objects depend on the FilterCriteria objects
1185: * provided to the TableDataFilter instance used by this component. If
1186: * TableDataFilter is modified directly, invoke the clearFilter method to
1187: * clear the previous filter.
1188: * </p>
1189: * @return An array of RowKey objects.
1190: */
1191: public RowKey[] getSelectedRowKeys() {
1192: // Get filtered RowKey objects.
1193: RowKey[] rowKeys = getFilteredRowKeys();
1194: if (rowKeys == null) {
1195: return rowKeys;
1196: }
1197:
1198: // Save the current RowKey.
1199: RowKey rowKey = getRowKey();
1200:
1201: // Find the number of selected rows.
1202: ArrayList list = new ArrayList();
1203: for (int i = 0; i < rowKeys.length; i++) {
1204: setRowKey(rowKeys[i]);
1205: if (isRowAvailable() && isSelected()) {
1206: list.add(rowKeys[i]);
1207: }
1208: }
1209: setRowKey(rowKey); // Restore the current RowKey.
1210: rowKeys = new RowKey[list.size()];
1211: return (RowKey[]) list.toArray(rowKeys);
1212: }
1213:
1214: /**
1215: * Get the number of objects from the underlying data provider where the
1216: * selected property of this component is set to true and the row is
1217: * rendered.
1218: * <p>
1219: * Note: UI guidelines recomend that rows should be unselected when no
1220: * longer in view. For example, when a user selects rows of the table and
1221: * navigates to another page. Or, when a user applies a filter or sort that
1222: * may hide previously selected rows from view. If a user invokes an action
1223: * to delete the currently selected rows, they may inadvertently remove rows
1224: * not displayed on the current page.
1225: * </p><p>
1226: * Note: This count depends on the FilterCriteria and SortCriteria objects
1227: * provided to the TableDataFilter and TableDataSorter instances used by
1228: * this component. If TableDataFilter and TableFilterSorter are modified
1229: * directly, invoke the clearFilter method to clear the previous filter and
1230: * sort.
1231: * </p>
1232: * @return The number of selected rows currently hidden from view.
1233: */
1234: public int getRenderedSelectedRowsCount() {
1235: RowKey[] rowKeys = getRenderedSelectedRowKeys();
1236: return (rowKeys != null) ? rowKeys.length : 0;
1237: }
1238:
1239: /**
1240: * Get an array of RowKey objects from the underlying data provider where
1241: * the selected property of this component is set to true and the row is
1242: * rendered.
1243: * <p>
1244: * Note: UI guidelines recomend that rows should be unselected when no
1245: * longer in view. For example, when a user selects rows of the table and
1246: * navigates to another page. Or, when a user applies a filter or sort that
1247: * may hide previously selected rows from view. If a user invokes an action
1248: * to delete the currently selected rows, they may inadvertently remove rows
1249: * not displayed on the current page.
1250: * </p><p>
1251: * Note: The returned RowKey objects depend on the FilterCriteria and
1252: * SortCriteria objects provided to the TableDataFilter and TableDataSorter
1253: * instances used by this component. If TableDataFilter and TableDataSorter
1254: * are modified directly, invoke the clearSort and clearFilter method to
1255: * clear the previous sort and filter.
1256: * </p>
1257: * @return An array of RowKey objects.
1258: */
1259: public RowKey[] getRenderedSelectedRowKeys() {
1260: // Get rendered RowKey objects.
1261: RowKey[] rowKeys = getRenderedRowKeys();
1262: if (rowKeys == null) {
1263: return rowKeys;
1264: }
1265:
1266: // Save the current RowKey.
1267: RowKey rowKey = getRowKey();
1268:
1269: // Find the number of selected rows in view.
1270: ArrayList list = new ArrayList();
1271: for (int i = 0; i < rowKeys.length; i++) {
1272: setRowKey(rowKeys[i]);
1273: if (isRowAvailable() && isSelected()) {
1274: list.add(rowKeys[i]);
1275: }
1276: }
1277: setRowKey(rowKey); // Restore the current RowKey.
1278: rowKeys = new RowKey[list.size()];
1279: return (RowKey[]) list.toArray(rowKeys);
1280: }
1281:
1282: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1283: // Sort methods
1284: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1285:
1286: /**
1287: * Add a SortCriteria object to sort.
1288: * <p>
1289: * Note: Objects are sorted in the reverse order they were added. For
1290: * example, the first object added, will be the last sort applied as the
1291: * primary sort. The second object added, will be the second to last sort
1292: * applied as the secondary sort. The third object added, will be the third
1293: * to last sort applied as the tertiary sort and so on. If an existing
1294: * SortCriteria object is found with the same FieldKey, the sort order is
1295: * replaced with the new value. Note that sorts are not actually applied
1296: * until the getSortedRowKeys() method is invoked, which happens
1297: * automatically by the renderer.
1298: * </p><p>
1299: * Note: This method also resets pagination to the first page per UI
1300: * guidelines.
1301: * </p>
1302: * @param criteria The SortCriteria object to sort.
1303: */
1304: public void addSort(SortCriteria criteria) {
1305: if (criteria == null) {
1306: return;
1307: }
1308:
1309: TableDataSorter sorter = getTableDataSorter();
1310: SortCriteria[] oldCriteria = sorter.getSortCriteria();
1311:
1312: // Iterate over each SortCriteria object and check for a match.
1313: if (oldCriteria != null) {
1314: for (int i = 0; i < oldCriteria.length; i++) {
1315: if (oldCriteria[i] == null) {
1316: continue;
1317: }
1318: String key = oldCriteria[i].getCriteriaKey();
1319: if (key != null
1320: && key.equals(criteria.getCriteriaKey())) {
1321: oldCriteria[i] = criteria;
1322: return; // No further processing is required.
1323: }
1324: }
1325: }
1326:
1327: // Create array to hold new criteria.
1328: int oldLength = (oldCriteria != null) ? oldCriteria.length : 0;
1329: SortCriteria[] newCriteria = new SortCriteria[oldLength + 1];
1330: for (int i = 0; i < oldLength; i++) {
1331: newCriteria[i] = oldCriteria[i];
1332: }
1333:
1334: // Add new SortCriteria object.
1335: newCriteria[oldLength] = criteria;
1336: sorter.setSortCriteria(newCriteria); // Set new SortCriteria.
1337: setFirst(0); // Reset to first page.
1338:
1339: // Clear properties cached in request map.
1340: Properties properties = getProperties();
1341: if (properties != null) {
1342: properties.setSortedRowKeys(null);
1343: } else {
1344: log("addSort",
1345: "Cannot clear sorted row keys, Properties is null"); //NOI18N
1346: }
1347: }
1348:
1349: /**
1350: * Clear SortCriteria objects from the TableDataSorter instance used by this
1351: * component.
1352: * <p>
1353: * Note: This method clears the cached sort, then resets pagination to the
1354: * first page per UI guidelines.
1355: * </p>
1356: */
1357: public void clearSort() {
1358: getTableDataSorter().setSortCriteria(null); // Clear all SortCriteria.
1359: setFirst(0); // Reset to first page.
1360:
1361: // Clear properties cached in request map.
1362: Properties properties = getProperties();
1363: if (properties != null) {
1364: properties.setSortedRowKeys(null);
1365: } else {
1366: log("clearSort",
1367: "Cannot clear sorted row keys, Properties is null"); //NOI18N
1368: }
1369: }
1370:
1371: /**
1372: * Get the number of SortCriteria objects to sort.
1373: *
1374: * @return The number of SortCriteria objects to sort.
1375: */
1376: public int getSortCount() {
1377: int result = 0;
1378: SortCriteria[] sortCriteria = getTableDataSorter()
1379: .getSortCriteria();
1380: if (sortCriteria != null) {
1381: result = sortCriteria.length;
1382: }
1383: return result;
1384: }
1385:
1386: /**
1387: * Get the level of the given SortCriteria object to sort.
1388: * <p>
1389: * Note: The primary sort is level 1, the secondary sort is level 2, the
1390: * tertiary sort is level 3, and so on. If the SortCriteria
1391: * object was not previously added using the addSort method, the level will
1392: * be returned as -1.
1393: * </p>
1394: * @param criteria The SortCriteria object to sort.
1395: * @return The sort level or -1 if the SortCriteria object was not
1396: * previously added.
1397: */
1398: public int getSortLevel(SortCriteria criteria) {
1399: int result = -1;
1400: if (criteria == null) {
1401: return result;
1402: }
1403:
1404: // Iterate over each SortCriteria object and check for a match.
1405: SortCriteria[] sortCriteria = getTableDataSorter()
1406: .getSortCriteria();
1407: if (sortCriteria != null) {
1408: for (int i = 0; i < sortCriteria.length; i++) {
1409: if (sortCriteria[i] == null) {
1410: continue;
1411: }
1412: String key = sortCriteria[i].getCriteriaKey();
1413: if (key != null
1414: && key.equals(criteria.getCriteriaKey())) {
1415: result = i + 1;
1416: break;
1417: }
1418: }
1419: }
1420: return result;
1421: }
1422:
1423: /**
1424: * Test if given SortCriteria object is a descending sort.
1425: *
1426: * @param criteria The SortCriteria object to sort.
1427: * @return true if descending, else false.
1428: */
1429: public boolean isDescendingSort(SortCriteria criteria) {
1430: boolean result = false;
1431: if (criteria == null) {
1432: return result;
1433: }
1434:
1435: // Iterate over each SortCriteria object and check for a match.
1436: SortCriteria[] sortCriteria = getTableDataSorter()
1437: .getSortCriteria();
1438: if (sortCriteria != null) {
1439: for (int i = 0; i < sortCriteria.length; i++) {
1440: if (sortCriteria[i] == null) {
1441: continue;
1442: }
1443: String key = sortCriteria[i].getCriteriaKey();
1444: if (key != null
1445: && key.equals(criteria.getCriteriaKey())) {
1446: // Note: SortCriteria tests ascending instead of descending.
1447: result = !sortCriteria[i].isAscending();
1448: break;
1449: }
1450: }
1451: }
1452: return result;
1453: }
1454:
1455: /**
1456: * Get an array containing sorted RowKey objects.
1457: * <p>
1458: * Note: This sort depends on the SortCriteria objects provided to the
1459: * TableDataSorter instance used by this component. For better performance,
1460: * this sort also depends on the FilterCriteria objects provided to the
1461: * TableDataFilter instance used by this component. Due to filtering, the
1462: * size of the returned array may be less than the total number of RowKey
1463: * objects for the underlying TableDataProvider.
1464: * </p><p>
1465: * Note: The returned RowKey objects are cached. If the TableDataSorter and
1466: * TableDataFilter instances used by this component are modified directly,
1467: * invoke the clearSort and clearFilter methods to clear the previous sort
1468: * and filter.
1469: * </p>
1470: * @return An array containing sorted RowKey objects.
1471: */
1472: public RowKey[] getSortedRowKeys() {
1473: // Get properties cached in request map.
1474: Properties properties = getProperties();
1475: RowKey[] sortedRowKeys = (properties != null) ? properties
1476: .getSortedRowKeys() : null;
1477:
1478: // Initialize RowKey objects, if not cached already.
1479: if (sortedRowKeys != null) {
1480: return sortedRowKeys;
1481: } else {
1482: sortedRowKeys = getFilteredRowKeys();
1483: }
1484:
1485: // Do not attempt to sort with a null provider. BasicTableDataSorter
1486: // throws NullPointerException -- bugtraq id #6268451.
1487: TableDataProvider provider = getTableRowDataProvider()
1488: .getTableDataProvider();
1489: if (provider == null) {
1490: log("getSortedRowKeys", //NOI18N
1491: "Cannot obtain sorted row keys, TableDataProvider is null"); //NOI18N
1492: return sortedRowKeys;
1493: }
1494:
1495: // If TableDataSorter and TableDataProvider are the same instance, the
1496: // sort method is never called. The sort order is assumed to be
1497: // intrinsic in the row order of the TableDataProvider.
1498: TableDataSorter sorter = getTableDataSorter();
1499: if (provider != sorter) {
1500: sortedRowKeys = sorter.sort(provider, sortedRowKeys);
1501: }
1502:
1503: // Save properties.
1504: if (properties != null) {
1505: properties.setSortedRowKeys(sortedRowKeys);
1506: } else {
1507: log("getSortedRowKeys", //NOI18N
1508: "Cannot save sorted row keys, Properties is null"); //NOI18N
1509: }
1510: return sortedRowKeys;
1511: }
1512:
1513: /**
1514: * Get the TableDataSorter object used to sort rows.
1515: *
1516: * @return The TableDataSorter object used to sort rows.
1517: */
1518: public TableDataSorter getTableDataSorter() {
1519: // Method is overriden because TableDataSorter is not serializable.
1520: TableDataSorter tds = super .getTableDataSorter();
1521: if (tds != null) {
1522: return tds;
1523: }
1524:
1525: // Get default sorter.
1526: if (sorter == null) {
1527: sorter = new BasicTableDataSorter();
1528: }
1529: return sorter;
1530: }
1531:
1532: /**
1533: * Set the TableDataSorter object used to sort rows.
1534: *
1535: * @param sorter The TableDataSorter object used to sort rows.
1536: */
1537: public void setTableDataSorter(TableDataSorter sorter) {
1538: // Method is overriden because TableDataSorter is not serializable.
1539: this .sorter = sorter;
1540: }
1541:
1542: /**
1543: * Set SortCriteria objects for the TableDataSorter instance used by this
1544: * component.
1545: * <p>
1546: * Note: This method clears the cached sort, then resets pagination to the
1547: * first page per UI guidelines.
1548: * </p>
1549: * @param sortCriteria An array of SortCriteria objects defining the sort
1550: * order on this TableDataSorter.
1551: */
1552: public void setSortCriteria(SortCriteria[] sortCriteria) {
1553: clearSort();
1554: getTableDataSorter().setSortCriteria(sortCriteria);
1555: }
1556:
1557: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1558: // State methods
1559: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1560:
1561: /**
1562: * Restore the state of this component.
1563: */
1564: public void restoreState(FacesContext context, Object state) {
1565: Object values[] = (Object[]) state;
1566: super .restoreState(context, values[0]);
1567: saved = (Map) values[1];
1568: setPaginated(((Boolean) values[2]).booleanValue());
1569:
1570: // Note: When the iterate method is called (during the decode, validate,
1571: // update phases), the previously displayed sort must be used to iterate
1572: // over the previously displayed children. If child values have changed
1573: // (e.g., TableSelectPhaseListener has cleared checkbox state after the
1574: // rendering phase), a new sort would not represent the same rows and
1575: // state may be lost. Thus, we must restore the previously sorted RowKey
1576: // objects.
1577:
1578: // Restore SortCriteria.
1579: TableDataSorter sorter = getTableDataSorter();
1580: sorter.setSortCriteria((SortCriteria[]) values[3]);
1581:
1582: // Restore FilterCriteria.
1583: TableDataFilter filter = getTableDataFilter();
1584: filter.setFilterCriteria((FilterCriteria[]) values[4]);
1585:
1586: // Restore previously filtered and sorted RowKey objects.
1587: Properties properties = getProperties();
1588: if (properties != null) {
1589: properties.setFilteredRowKeys((RowKey[]) values[5]);
1590: properties.setSortedRowKeys((RowKey[]) values[6]);
1591: } else {
1592: log("restoreState", //NOI18N
1593: "Cannot save sorted and filtered row keys, Properties is null"); //NOI18N
1594: }
1595: }
1596:
1597: /**
1598: * Save the state of this component.
1599: *
1600: * @return An array of Object values.
1601: */
1602: public Object saveState(FacesContext context) {
1603: Object values[] = new Object[8];
1604: values[0] = super .saveState(context);
1605: values[1] = saved;
1606: values[2] = isPaginated() ? Boolean.TRUE : Boolean.FALSE;
1607: values[3] = getTableDataSorter().getSortCriteria(); // Save SortCriteria.
1608: values[4] = getTableDataFilter().getFilterCriteria(); // Save FilterCriteria.
1609: values[5] = getFilteredRowKeys(); // Save filtered RowKey objects.
1610: values[6] = getSortedRowKeys(); // Save sorted RowKey objects.
1611: return values;
1612: }
1613:
1614: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1615: // UIComponent methods
1616: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1617:
1618: /**
1619: * Set the ValueBinding used to calculate the value for the specified
1620: * attribute or property name, if any. In addition, if a ValueBinding is
1621: * set for the value property, remove any synthesized TableDataProvider for
1622: * the data previously bound to this component.
1623: *
1624: * @param name Name of the attribute or property for which to set a
1625: * ValueBinding.
1626: * @param binding The ValueBinding to set, or null to remove any currently
1627: * set ValueBinding.
1628: *
1629: * @exception IllegalArgumentException If name is one of sourceVar.
1630: * @exception NullPointerException If name is null.
1631: */
1632: public void setValueBinding(String name, ValueBinding binding) {
1633: if ("sourceData".equals(name)) { //NOI18N
1634: init();
1635: } else if ("sourceVar".equals(name)) { //NOI18N
1636: log("setValueBinding", "sourceVar cannot equal given name"); //NOI18N
1637: throw new IllegalArgumentException();
1638: }
1639: super .setValueBinding(name, binding);
1640: }
1641:
1642: /**
1643: * Return a client identifier for this component that includes the current
1644: * value of the RowKey property, if it is not set to null. This implies that
1645: * multiple calls to getClientId() may return different results, but ensures
1646: * that child components can themselves generate row-specific client
1647: * identifiers (since TableRowGroup is a NamingContainer).
1648: *
1649: * @exception NullPointerException if FacesContext is null.
1650: * @return The client id.
1651: */
1652: public String getClientId(FacesContext context) {
1653: if (context == null) {
1654: log("getClientId",
1655: "Cannot obtain client Id, FacesContext is null"); //NOI18N
1656: throw new NullPointerException();
1657: }
1658:
1659: String baseClientId = super .getClientId(context);
1660: if (getRowKey() != null) {
1661: return (baseClientId + NamingContainer.SEPARATOR_CHAR + getRowKey()
1662: .getRowId());
1663: } else {
1664: return (baseClientId);
1665: }
1666: }
1667:
1668: /**
1669: * Override the default UIComponentBase.queueEvent() processing to wrap any
1670: * queued events in a wrapper so that we can reset the current RowKey in
1671: * broadcast().
1672: *
1673: * @param event FacesEvent to be queued.
1674: *
1675: * @exception IllegalStateException If this component is not a descendant
1676: * of a UIViewRoot.
1677: * @exception NullPointerException If FacesEvent is null.
1678: */
1679: public void queueEvent(FacesEvent event) {
1680: super .queueEvent(new WrapperEvent(this , event, getRowKey()));
1681: }
1682:
1683: /**
1684: * Override the default UIComponentBase.broadcast() processing to unwrap any
1685: * wrapped FacesEvent and reset the current RowKey, before the event is
1686: * actually broadcast. For events that we did not wrap (in queueEvent()),
1687: * default processing will occur.
1688: *
1689: * @param event The FacesEvent to be broadcast.
1690: *
1691: * @exception AbortProcessingException Signal the JavaServer Faces
1692: * implementation that no further processing on the current event
1693: * should be performed.
1694: * @exception IllegalArgumentException if the implementation class
1695: * of this FacesEvent is not supported by this component.
1696: * @exception NullPointerException if FacesEvent is null.
1697: */
1698: public void broadcast(FacesEvent event)
1699: throws AbortProcessingException {
1700: if (!(event instanceof WrapperEvent)) {
1701: super .broadcast(event);
1702: return;
1703: }
1704:
1705: // Set up the correct context and fire our wrapped event
1706: WrapperEvent revent = (WrapperEvent) event;
1707: RowKey oldRowKey = getRowKey();
1708: setRowKey(revent.getRowKey());
1709: FacesEvent rowEvent = revent.getFacesEvent();
1710: rowEvent.getComponent().broadcast(rowEvent);
1711: setRowKey(oldRowKey);
1712: return;
1713: }
1714:
1715: /**
1716: * In addition to the default behavior, ensure that any saved per-row state
1717: * for our child input components is discarded unless it is needed to
1718: * rerender the current page with errors.
1719: *
1720: * @param context FacesContext for the current request.
1721: *
1722: * @exception IOException if an input/output error occurs while rendering.
1723: * @exception NullPointerException if FacesContext is null.
1724: */
1725: public void encodeBegin(FacesContext context) throws IOException {
1726: // Clear objects cached during the decode, validate, and update phases
1727: // so nested tables can render new TableDataProvider objects.
1728: if (isNestedWithinTableRowGroup()) {
1729: init();
1730: }
1731: if (!keepSaved(context)) {
1732: saved = new HashMap();
1733: }
1734: super .encodeBegin(context);
1735: }
1736:
1737: /**
1738: * Override the default UIComponentBase.processDecodes() processing to
1739: * perform the following steps.
1740: *
1741: * <ul>
1742: * <li>If the rendered property of this UIComponent is false, skip further
1743: * processing.</li>
1744: * <li>Set the current RowKey to null.</li>
1745: * <li>Call the processDecodes() method of all facets of this TableRowGroup,
1746: * in the order determined by a call to getFacets().keySet().iterator().</li>
1747: * <li>Call the processDecodes() method of all facets of the TableColumn
1748: * children of this TableRowGroup.</li>
1749: * <li>Iterate over the set of rows that were included when this component
1750: * was rendered (i.e. those defined by the first and rows properties),
1751: * performing the following processing for each row:</li>
1752: * <li>Set the current RowKey to the appropriate value for this row.</li>
1753: * <li>If isRowAvailable() returns true, iterate over the children
1754: * components of each TableColumn child of this TableRowGroup component,
1755: * calling the processDecodes() method for each such child.</li>
1756: * <li>Set the current RowKey to null.</li>
1757: * <li>Call the decode() method of this component.</li>
1758: * <li>If a RuntimeException is thrown during decode processing, call
1759: * FacesContext.renderResponse() and re-throw the exception.</li>
1760: * </ul>
1761: *
1762: * @param context FacesContext for the current request.
1763: *
1764: * @exception NullPointerException if FacesContext is null.
1765: */
1766: public void processDecodes(FacesContext context) {
1767: if (context == null) {
1768: log("processDecodes", "Cannot decode, FacesContext is null"); //NOI18N
1769: throw new NullPointerException();
1770: }
1771: if (!isRendered()) {
1772: log("processDecodes",
1773: "Component not rendered, nothing to decode"); //NOI18N
1774: return;
1775: }
1776: if (saved == null || !keepSaved(context)) {
1777: saved = new HashMap(); // We don't need saved state here
1778: }
1779: iterate(context, PhaseId.APPLY_REQUEST_VALUES);
1780: decode(context);
1781: }
1782:
1783: /**
1784: * Override the default UIComponentBase.processValidators() processing to
1785: * perform the following steps.
1786: *
1787: * <ul>
1788: * <li>If the rendered property of this UIComponent is false, skip further
1789: * processing.</li>
1790: * <li>Set the current RowKey to null.</li>
1791: * <li>Call the processValidators() method of all facets of this
1792: * TableRowGroup, in the order determined by a call to
1793: * getFacets().keySet().iterator().</li>
1794: * <li>Call the processValidators() method of all facets of the TableColumn
1795: * children of this TableRowGroup.</li>
1796: * <li>Iterate over the set of rows that were included when this component
1797: * was rendered (i.e. those defined by the first and rows properties),
1798: * performing the following processing for each row:</li>
1799: * <li>Set the current RowKey to the appropriate value for this row.</li>
1800: * <li>If isRowAvailable() returns true, iterate over the children
1801: * components of each TableColumn child of this TableRowGroup component,
1802: * calling the processValidators() method for each such child.</li>
1803: * <li>Set the current RowKey to null.</li>
1804: * </ul>
1805: *
1806: * @param context FacesContext for the current request.
1807: *
1808: * @exception NullPointerException if FacesContext is null.
1809: */
1810: public void processValidators(FacesContext context) {
1811: if (context == null) {
1812: log("processValidators",
1813: "Cannot validate, FacesContext is null"); //NOI18N
1814: throw new NullPointerException();
1815: }
1816: if (!isRendered()) {
1817: log("processValidators", //NOI18N
1818: "Component not rendered, nothing to validate"); //NOI18N
1819: return;
1820: }
1821: iterate(context, PhaseId.PROCESS_VALIDATIONS);
1822: // This is not a EditableValueHolder, so no further processing is required
1823: }
1824:
1825: /**
1826: * Override the default UIComponentBase.processUpdates() processing to
1827: * perform the following steps.
1828: *
1829: * <ul>
1830: * <li>If the rendered property of this UIComponent is false, skip further
1831: * processing.</li>
1832: * <li>Set the current RowKey to null.</li>
1833: * <li>Call the processUpdates() method of all facets of this TableRowGroup,
1834: * in the order determined by a call to getFacets().keySet().iterator().</li>
1835: * <li>Call the processUpdates() method of all facets of the TableColumn
1836: * children of this TableRowGroup.</li>
1837: * <li>Iterate over the set of rows that were included when this component
1838: * was rendered (i.e. those defined by the first and rows properties),
1839: * performing the following processing for each row:</li>
1840: * <li>Set the current RowKey to the appropriate value for this row.</li>
1841: * <li>If isRowAvailable() returns true, iterate over the children
1842: * components of each TableColumn child of this TableRowGroup component,
1843: * calling the processUpdates() method for each such child.</li>
1844: * <li>Set the current RowKey to null.</li>
1845: * </ul>
1846: *
1847: * @param context FacesContext for the current request.
1848: *
1849: * @exception NullPointerException if FacesContext is null.
1850: */
1851: public void processUpdates(FacesContext context) {
1852: if (context == null) {
1853: log("processUpdates", "Cannot update, FacesContext is null"); //NOI18N
1854: throw new NullPointerException();
1855: }
1856: if (!isRendered()) {
1857: log("processUpdates",
1858: "Component not rendered, nothing to update"); //NOI18N
1859: return;
1860: }
1861: iterate(context, PhaseId.UPDATE_MODEL_VALUES);
1862:
1863: // Set collapsed property applied client-side.
1864: UIComponent header = getFacet(GROUP_HEADER_ID);
1865: UIComponent field = (header != null) ? (UIComponent) header
1866: .getFacets().get(TableHeader.COLLAPSED_HIDDEN_FIELD_ID)
1867: : null;
1868: if (field instanceof HiddenField) {
1869: Boolean value = (field != null) ? (Boolean) ((HiddenField) field)
1870: .getValue()
1871: : null;
1872: setCollapsed(value.booleanValue());
1873: } else {
1874: log("processUpdates",
1875: "Cannot obtain collapsed hidden field value"); //NOI18N
1876: }
1877: // This is not a EditableValueHolder, so no further processing is required
1878: }
1879:
1880: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1881: // Private methods
1882: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1883:
1884: /**
1885: * Get the properties for this component cached in the request map.
1886: * <p>
1887: * Note: Properties may have been cached via the apply request values,
1888: * validate, and update phases and must be initialized for the render
1889: * response phase. Table components are forced to reinitialize by setting
1890: * the cached properties map to null when the table title and actions bar
1891: * are rendered. New column and row counts are required each time the table
1892: * is redisplayed, for example.
1893: * </p>
1894: * @return The properties for this component.
1895: */
1896: private Properties getProperties() {
1897: // Get Table ancestor.
1898: Table table = getTableAncestor();
1899: if (table == null) {
1900: log("getProperties",
1901: "Cannot obtain Properties, Table is null"); //NOI18N
1902: return null;
1903: }
1904:
1905: // Get properties for all components.
1906: FacesContext context = getFacesContext();
1907: Map requestMap = context.getExternalContext().getRequestMap();
1908: String propertiesMapId = REQUEST_KEY_PREFIX
1909: + table.getClientId(context) + PROPERTIES;
1910: Map propertiesMap = (Map) requestMap.get(propertiesMapId);
1911: if (propertiesMap == null) {
1912: propertiesMap = new HashMap();
1913: requestMap.put(propertiesMapId, propertiesMap);
1914: }
1915:
1916: // Get properties for this component.
1917: String propertiesId = super .getClientId(context); // Don't append row ID.
1918: Properties properties = (Properties) propertiesMap
1919: .get(propertiesId);
1920: if (properties == null) {
1921: properties = new Properties();
1922: propertiesMap.put(propertiesId, properties);
1923: }
1924:
1925: return properties;
1926: }
1927:
1928: /**
1929: * Helper method to get Theme objects.
1930: *
1931: * @return The current theme.
1932: */
1933: private Theme getTheme() {
1934: return ThemeUtilities.getTheme(getFacesContext());
1935: }
1936:
1937: /**
1938: * Initialize member variables.
1939: * <p>
1940: * Note: When ever a new DataProvider is used, UI Guiedlines recommend that
1941: * pagination should be reset (e.g., remaining on the 4th page of a new set
1942: * of data makes no sense). However, properties such as the sort and filter
1943: * criteria should not automatically be cleared (e.g., there may be
1944: * situations where one or both should be left as specified by the user). In
1945: * this scenario, pagination is set to the first page.
1946: * </p><p>
1947: * Note: When ever the underlying DataProvider has changed, cached
1948: * properties must be re-evaluated even with server-side state saving --
1949: * bugtraq #6304818.
1950: * </p>
1951: */
1952: private void init() {
1953: setFirst(0); // Reset to first page.
1954:
1955: // Get Table ancestor.
1956: Table table = getTableAncestor();
1957: if (table == null) {
1958: log("init", "Cannot initialize Properties, Table is null"); //NOI18N
1959: return;
1960: }
1961:
1962: // Get properties for all components.
1963: FacesContext context = getFacesContext();
1964: Map requestMap = context.getExternalContext().getRequestMap();
1965: String propertiesId = REQUEST_KEY_PREFIX
1966: + table.getClientId(context) + PROPERTIES;
1967: Map propertiesMap = (Map) requestMap.get(propertiesId);
1968:
1969: // Clear all properties cached in request map for this component.
1970: if (propertiesMap != null) {
1971: propertiesMap.put(super .getClientId(context), null);
1972: } else {
1973: log("init", //NOI18N
1974: "Cannot initialize Properties, request properties map is null"); //NOI18N
1975: }
1976: }
1977:
1978: /**
1979: * Helper method to determine if this component is nested within another
1980: * TableRowGroup component.
1981: *
1982: * @return true if this component is nested, else false.
1983: */
1984: private boolean isNestedWithinTableRowGroup() {
1985: UIComponent parent = this ;
1986: while (null != (parent = parent.getParent())) {
1987: if (parent instanceof TableRowGroup) {
1988: return true;
1989: }
1990: }
1991: return (false);
1992: }
1993:
1994: /**
1995: * Helper method to perform the appropriate phase-specific processing and
1996: * per-row iteration for the specified phase, as follows:
1997: *
1998: * <ul>
1999: * <li>Set the RowKey property to null, and process the facets
2000: * of this TableRowGroup component exactly once.</li>
2001: * <li>Set the RowKey property to null, and process the facets
2002: * of the TableColumn children of this TableRowGroup component exactly
2003: * once.</li>
2004: * <li>Iterate over the relevant rows, based on the first and row
2005: * properties, and process the children of the TableColumn children of
2006: * this TableRowGroup component once per row.</li>
2007: * </ul>
2008: *
2009: * @param context FacesContext for the current request.
2010: * @param phaseId PhaseId of the phase we are currently running.
2011: */
2012: private void iterate(FacesContext context, PhaseId phaseId) {
2013: // Note: When the iterate method is called via the processDecode,
2014: // processValidate, and processUpdate methods), the previously displayed
2015: // sort must be used to iterate over the previously displayed children.
2016: // (The previously displayed sort is cached/restored via the
2017: // save/restoreState methods.) If child values have changed (e.g.,
2018: // TableSelectPhaseListener has cleared checkbox state after the
2019: // rendering phase), obtaining a new sort here may not represent the
2020: // same rows and state may be lost. Thus, don't clear cached properties
2021: // unless nested.
2022: if (isNestedWithinTableRowGroup()) {
2023: // Re-evaluate even with server-side state saving.
2024: init();
2025: }
2026:
2027: // Process each facet of this component exactly once.
2028: setRowKey(null);
2029: Iterator facets = getFacets().keySet().iterator(); // Get facet keys.
2030: while (facets.hasNext()) {
2031: // Get facet.
2032: UIComponent facet = (UIComponent) getFacets().get(
2033: facets.next());
2034: if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
2035: facet.processDecodes(context);
2036: } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
2037: facet.processValidators(context);
2038: } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
2039: facet.processUpdates(context);
2040: } else {
2041: log("iterate", //NOI18N
2042: "Cannot process component facets, Invalid phase ID"); //NOI18N
2043: throw new IllegalArgumentException();
2044: }
2045: }
2046:
2047: // Process the facet of each TableColumn child exactly once.
2048: setRowKey(null);
2049: Iterator kids = getTableColumnChildren();
2050: while (kids.hasNext()) {
2051: TableColumn kid = (TableColumn) kids.next();
2052: if (!kid.isRendered()) {
2053: log("iterate", //NOI18N
2054: "Cannot process TableColumn facets, TableColumn not rendered"); //NOI18N
2055: continue;
2056: }
2057: iterateTableColumnFacets(context, kid, phaseId);
2058: }
2059:
2060: // Get rendered row keys.
2061: RowKey[] rowKeys = getRenderedRowKeys();
2062: if (rowKeys == null) {
2063: log("iterate", //NOI18N
2064: "Cannot iterate over TableColumn children, RowKey array is null"); //NOI18N
2065: return;
2066: }
2067:
2068: // Iterate over the sorted, rendered RowKey objects.
2069: for (int i = 0; i < rowKeys.length; i++) {
2070: setRowKey(rowKeys[i]);
2071: if (!isRowAvailable()) {
2072: log("iterate", //NOI18N
2073: "Cannot iterate over TableColumn children, row not available"); //NOI18N
2074: break;
2075: }
2076:
2077: // Perform phase-specific processing as required on the children
2078: // of the TableColumn (facets have been done a single time with
2079: // setRowKey(null) already)
2080: kids = getTableColumnChildren();
2081: while (kids.hasNext()) {
2082: TableColumn kid = (TableColumn) kids.next();
2083: if (!kid.isRendered()) {
2084: log("iterate",
2085: "Cannot process TableColumn, not rendered"); //NOI18N
2086: continue;
2087: }
2088: Iterator grandkids = kid.getChildren().iterator();
2089: while (grandkids.hasNext()) {
2090: UIComponent grandkid = (UIComponent) grandkids
2091: .next();
2092: if (!grandkid.isRendered()) {
2093: log("iterate", //NOI18N
2094: "Cannot process TableColumn child, not rendered"); //NOI18N
2095: continue;
2096: }
2097: iterateTableColumnChildren(context, grandkid,
2098: phaseId);
2099: }
2100: }
2101: }
2102: setRowKey(null); // Clean up after ourselves.
2103: }
2104:
2105: /**
2106: * Helper method to iterate over nested TableColumn facets.
2107: *
2108: * @param context FacesContext for the current request.
2109: * @param component The TableColumn component to be rendered.
2110: * @param phaseId PhaseId of the phase we are currently running.
2111: */
2112: private void iterateTableColumnFacets(FacesContext context,
2113: TableColumn component, PhaseId phaseId) {
2114: if (component == null) {
2115: log("iterateTableColumnFacets", //NOI18N
2116: "Cannot iterate over TableColumn facets, TableColumn is null"); //NOI18N
2117: return;
2118: }
2119:
2120: Iterator kids = component.getTableColumnChildren();
2121: if (kids.hasNext()) {
2122: while (kids.hasNext()) {
2123: TableColumn col = (TableColumn) kids.next();
2124: iterateTableColumnFacets(context, col, phaseId);
2125: }
2126: } else {
2127: // Get facet keys.
2128: Iterator facets = component.getFacets().keySet().iterator();
2129: while (facets.hasNext()) {
2130: // Get facet.
2131: UIComponent facet = (UIComponent) component.getFacets()
2132: .get(facets.next());
2133: if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
2134: facet.processDecodes(context);
2135: } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
2136: facet.processValidators(context);
2137: } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
2138: facet.processUpdates(context);
2139: } else {
2140: log("iterateTableColumnFacets", //NOI18N
2141: "Cannot iterate over TableColumn facets, Invalid phase ID"); //NOI18N
2142: throw new IllegalArgumentException();
2143: }
2144: }
2145: }
2146: }
2147:
2148: /**
2149: * Helper method to iterate over nested TableColumn children.
2150: *
2151: * @param context FacesContext for the current request.
2152: * @param component The TableColumn component to be rendered.
2153: * @param phaseId PhaseId of the phase we are currently running.
2154: */
2155: private void iterateTableColumnChildren(FacesContext context,
2156: UIComponent component, PhaseId phaseId) {
2157: if (component == null) {
2158: log("iterateTableColumnChildren", //NOI18N
2159: "Cannot iterate over TableColumn children, UIComponent is null"); //NOI18N
2160: return;
2161: }
2162:
2163: // Do not process nested TableColumn components so facets will not be
2164: // decoded for each row of the table.
2165: if (component instanceof TableColumn) {
2166: Iterator kids = component.getChildren().iterator();
2167: if (kids.hasNext()) {
2168: while (kids.hasNext()) {
2169: UIComponent kid = (UIComponent) kids.next();
2170: iterateTableColumnChildren(context, kid, phaseId);
2171: }
2172: }
2173: } else {
2174: if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
2175: component.processDecodes(context);
2176: } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
2177: component.processValidators(context);
2178: } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
2179: component.processUpdates(context);
2180: } else {
2181: log("iterateTableColumnChildren", //NOI18N
2182: "Cannot iterate over TableColumn children, Invalid phase ID"); //NOI18N
2183: throw new IllegalArgumentException();
2184: }
2185: }
2186: }
2187:
2188: /**
2189: * Helper method to get flag indicating that we need to keep the saved
2190: * per-child state information. This will be the case if any of the
2191: * following are true:
2192: *
2193: * <ul>
2194: * <li>Any of the saved state corresponds to components that have messages
2195: * that must be displayed.</li>
2196: * <li>This TableRowGroup instance is nested inside of another TableRowGroup
2197: * instance.</li>
2198: * </ul>
2199: *
2200: * @param context FacesContext for the current request.
2201: * @return true if state should be saved, else false.
2202: */
2203: private boolean keepSaved(FacesContext context) {
2204: Iterator clientIds = saved.keySet().iterator();
2205: while (clientIds.hasNext()) {
2206: String clientId = (String) clientIds.next();
2207:
2208: // Fix for immediate property -- see bugtraq #6269737.
2209: SavedState state = (SavedState) saved.get(clientId);
2210: if (state != null && state.getSubmittedValue() != null) {
2211: return (true);
2212: }
2213: }
2214: //<RAVE>
2215: //bug 6377769 -- check all messages for an error, not just the messages on EditableValueHolders within the TableRowGroup
2216: Iterator messages = context.getMessages();
2217: while (messages.hasNext()) {
2218: FacesMessage message = (FacesMessage) messages.next();
2219: if (message.getSeverity().compareTo(
2220: FacesMessage.SEVERITY_ERROR) >= 0) {
2221: return (true);
2222: }
2223: }
2224: //</RAVE>
2225: return (isNestedWithinTableRowGroup());
2226: }
2227:
2228: /**
2229: * Log fine messages.
2230: */
2231: private void log(String method, String message) {
2232: // Get class.
2233: Class clazz = this .getClass();
2234: if (LogUtil.fineEnabled(clazz)) {
2235: // Log method name and message.
2236: LogUtil.fine(clazz, clazz.getName() + "." + method + ": "
2237: + message); //NOI18N
2238: }
2239: }
2240:
2241: /**
2242: * Helper method to restore state information for all descendant components,
2243: * as described for setRowKey().
2244: */
2245: private void restoreDescendantState() {
2246: FacesContext context = getFacesContext();
2247: Iterator kids = getTableColumnChildren();
2248: while (kids.hasNext()) {
2249: TableColumn kid = (TableColumn) kids.next();
2250: if (!kid.isRendered()) {
2251: continue;
2252: }
2253: restoreDescendantState(kid, context);
2254: }
2255: }
2256:
2257: /**
2258: * Helper method to restore state information for the specified component
2259: * and its descendants.
2260: *
2261: * @param component Component for which to restore state information.
2262: * @param context FacesContext for the current request.
2263: */
2264: private void restoreDescendantState(UIComponent component,
2265: FacesContext context) {
2266: // Reset the client identifier for this component
2267: String id = component.getId();
2268: component.setId(id); // Forces client id to be reset
2269:
2270: // Restore state for this component (if it is a EditableValueHolder)
2271: if (component instanceof EditableValueHolder) {
2272: EditableValueHolder input = (EditableValueHolder) component;
2273: String clientId = component.getClientId(context);
2274: SavedState state = (SavedState) saved.get(clientId);
2275: if (state == null) {
2276: state = new SavedState();
2277: }
2278: input.setValue(state.getValue());
2279: input.setValid(state.isValid());
2280: input.setSubmittedValue(state.getSubmittedValue());
2281: // This *must* be set after the call to setValue(), since
2282: // calling setValue() always resets "localValueSet" to true.
2283: input.setLocalValueSet(state.isLocalValueSet());
2284:
2285: ConversionUtilities.restoreRenderedValueState(context,
2286: component);
2287: }
2288:
2289: // Restore state for children of this component
2290: Iterator kids = component.getChildren().iterator();
2291: while (kids.hasNext()) {
2292: restoreDescendantState((UIComponent) kids.next(), context);
2293: }
2294: }
2295:
2296: /**
2297: * Helper method to save state information for all descendant components, as
2298: * described for setRowKey().
2299: */
2300: private void saveDescendantState() {
2301: FacesContext context = getFacesContext();
2302: Iterator kids = getTableColumnChildren();
2303: while (kids.hasNext()) {
2304: TableColumn kid = (TableColumn) kids.next();
2305: if (!kid.isRendered()) {
2306: log("saveDescendantState", //NOI18N
2307: "Cannot save descendant state, TableColumn not rendered"); //NOI18N
2308: continue;
2309: }
2310: saveDescendantState(kid, context);
2311: }
2312: }
2313:
2314: /**
2315: * Helper method to save state information for the specified component and
2316: * its descendants.
2317: *
2318: * @param component Component for which to save state information.
2319: * @param context FacesContext for the current request.
2320: */
2321: private void saveDescendantState(UIComponent component,
2322: FacesContext context) {
2323:
2324: // Save state for this component (if it is a EditableValueHolder)
2325: if (component instanceof EditableValueHolder) {
2326: EditableValueHolder input = (EditableValueHolder) component;
2327: String clientId = component.getClientId(context);
2328: SavedState state = (SavedState) saved.get(clientId);
2329: if (state == null) {
2330: state = new SavedState();
2331: saved.put(clientId, state);
2332: }
2333: state.setValue(input.getLocalValue());
2334: state.setValid(input.isValid());
2335: state.setSubmittedValue(input.getSubmittedValue());
2336: state.setLocalValueSet(input.isLocalValueSet());
2337:
2338: ConversionUtilities.saveRenderedValueState(context,
2339: component);
2340: }
2341:
2342: // Note: Don't bother logging messages here -- too many messages.
2343: // For example, staticText is not an EditableValueHolder.
2344:
2345: // Save state for children of this component
2346: Iterator kids = component.getChildren().iterator();
2347: while (kids.hasNext()) {
2348: saveDescendantState((UIComponent) kids.next(), context);
2349: }
2350: }
2351:
2352: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2353: // Inner classes
2354: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2355:
2356: /**
2357: * Object used to cache properties in the request.
2358: */
2359: private class Properties {
2360: // The TableRowDataProvider associated with this component, lazily
2361: // instantiated if requested. This object is not part of the saved and
2362: // restored state of the component.
2363: private TableRowDataProvider provider = null;
2364:
2365: // Array containing currently filtered RowKey objects.
2366: private RowKey[] filteredRowKeys = null;
2367:
2368: // Array containing currently sorted RowKey objects. This sort will be
2369: // cached and used to iterate over children during the decode, validate,
2370: // and update phases.
2371: private RowKey[] sortedRowKeys = null;
2372:
2373: // A List of TableColumn children found for this component.
2374: private List tableColumnChildren = null;
2375:
2376: // The number of columns to be rendered.
2377: private int columnCount = -1;
2378:
2379: /** Default constructor. */
2380: public Properties() {
2381: }
2382:
2383: /**
2384: * Get the number of columns found for this component that have a
2385: * rendered property of true.
2386: *
2387: * @return The number of rendered columns.
2388: */
2389: public int getColumnCount() {
2390: return columnCount;
2391: }
2392:
2393: /**
2394: * Set the number of columns found for this component that have a
2395: * rendered property of true.
2396: *
2397: * @param columnCount The number of rendered columns.
2398: */
2399: public void setColumnCount(int columnCount) {
2400: this .columnCount = columnCount;
2401: }
2402:
2403: /**
2404: * Get an array containing filtered RowKey objects.
2405: *
2406: * @return An array containing filtered RowKey objects.
2407: */
2408: public RowKey[] getFilteredRowKeys() {
2409: return filteredRowKeys;
2410: }
2411:
2412: /**
2413: * Set an array containing filtered RowKey objects.
2414: *
2415: * @param filteredRowKeys An array containing filtered RowKey objects.
2416: */
2417: public void setFilteredRowKeys(RowKey[] filteredRowKeys) {
2418: this .filteredRowKeys = filteredRowKeys;
2419: }
2420:
2421: /**
2422: * Get an array containing sorted RowKey objects.
2423: *
2424: * @return An array containing sorted RowKey objects.
2425: */
2426: public RowKey[] getSortedRowKeys() {
2427: return sortedRowKeys;
2428: }
2429:
2430: /**
2431: * Set an array containing sorted RowKey objects.
2432: *
2433: * @param sortedRowKeys An array containing sorted RowKey objects.
2434: */
2435: public void setSortedRowKeys(RowKey[] sortedRowKeys) {
2436: this .sortedRowKeys = sortedRowKeys;
2437: }
2438:
2439: /**
2440: * Get the TableColumn children found for this component.
2441: *
2442: * @return The TableColumn children.
2443: */
2444: public List getTableColumnChildren() {
2445: return tableColumnChildren;
2446: }
2447:
2448: /**
2449: * Set the TableColumn children found for this component.
2450: *
2451: * @param tableColumnChildren The TableColumn children.
2452: */
2453: public void setTableColumnChildren(List tableColumnChildren) {
2454: this .tableColumnChildren = tableColumnChildren;
2455: }
2456:
2457: /**
2458: * Get the TableRowDataProvider object representing the data objects
2459: * that we will iterate over in this component's rendering.
2460: *
2461: * @return The TableRowDataProvider object.
2462: */
2463: public TableRowDataProvider getTableRowDataProvider() {
2464: return provider;
2465: }
2466:
2467: /**
2468: * Set the TableRowDataProvider object representing the data objects
2469: * that we will iterate over in this component's rendering.
2470: *
2471: * @return The TableRowDataProvider object.
2472: */
2473: public void setTableRowDataProvider(
2474: TableRowDataProvider provider) {
2475: this .provider = provider;
2476: }
2477: }
2478: }
2479:
2480: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2481: // Private classes
2482: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2483:
2484: // Private class to represent saved state information.
2485: class SavedState implements Serializable {
2486: private Object submittedValue;
2487: private boolean valid = true;
2488: private Object value;
2489: private boolean localValueSet;
2490:
2491: public Object getSubmittedValue() {
2492: return (this .submittedValue);
2493: }
2494:
2495: public void setSubmittedValue(Object submittedValue) {
2496: this .submittedValue = submittedValue;
2497: }
2498:
2499: public boolean isValid() {
2500: return (this .valid);
2501: }
2502:
2503: public void setValid(boolean valid) {
2504: this .valid = valid;
2505: }
2506:
2507: public Object getValue() {
2508: return (this .value);
2509: }
2510:
2511: public void setValue(Object value) {
2512: this .value = value;
2513: }
2514:
2515: public boolean isLocalValueSet() {
2516: return (this .localValueSet);
2517: }
2518:
2519: public void setLocalValueSet(boolean localValueSet) {
2520: this .localValueSet = localValueSet;
2521: }
2522:
2523: public String toString() {
2524: return ("submittedValue: " + submittedValue + " value: "
2525: + value + //NOI18N
2526: " localValueSet: " + localValueSet); //NOI18N
2527: }
2528: }
2529:
2530: // Private class to wrap an event with a RowKey.
2531: class WrapperEvent extends FacesEvent {
2532: private FacesEvent event = null;
2533: private RowKey rowKey = null;
2534:
2535: public WrapperEvent(UIComponent component, FacesEvent event,
2536: RowKey rowKey) {
2537: super (component);
2538: this .event = event;
2539: this .rowKey = rowKey;
2540: }
2541:
2542: public FacesEvent getFacesEvent() {
2543: return (this .event);
2544: }
2545:
2546: public RowKey getRowKey() {
2547: return (this .rowKey);
2548: }
2549:
2550: public PhaseId getPhaseId() {
2551: return (this .event.getPhaseId());
2552: }
2553:
2554: public void setPhaseId(PhaseId phaseId) {
2555: this .event.setPhaseId(phaseId);
2556: }
2557:
2558: public boolean isAppropriateListener(FacesListener listener) {
2559: return (false);
2560: }
2561:
2562: public void processListener(FacesListener listener) {
2563: throw new IllegalStateException();
2564: }
2565: }
|