View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.component.html.ext;
20  
21  import java.io.IOException;
22  import java.sql.ResultSet;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.StringTokenizer;
30  
31  import javax.el.ValueExpression;
32  import javax.faces.FacesException;
33  import javax.faces.application.Application;
34  import javax.faces.component.ContextCallback;
35  import javax.faces.component.EditableValueHolder;
36  import javax.faces.component.UIColumn;
37  import javax.faces.component.UIComponent;
38  import javax.faces.component.UINamingContainer;
39  import javax.faces.component.UIPanel;
40  import javax.faces.component.visit.VisitCallback;
41  import javax.faces.component.visit.VisitContext;
42  import javax.faces.component.visit.VisitResult;
43  import javax.faces.context.FacesContext;
44  import javax.faces.model.DataModel;
45  
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
49  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFacet;
50  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
51  import org.apache.myfaces.component.NewspaperTable;
52  import org.apache.myfaces.component.UserRoleAware;
53  import org.apache.myfaces.component.UserRoleUtils;
54  import org.apache.myfaces.custom.column.HtmlSimpleColumn;
55  import org.apache.myfaces.custom.crosstable.UIColumns;
56  import org.apache.myfaces.custom.sortheader.HtmlCommandSortHeader;
57  import org.apache.myfaces.renderkit.html.ext.HtmlTableRenderer;
58  import org.apache.myfaces.renderkit.html.util.TableContext;
59  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
60  
61  /**
62   * The MyFacesDataTable extends the standard JSF DataTable by two
63   * important features:
64   * <br/>
65   * <ul>
66   *   <li>Possiblity to save the state of the DataModel.</li>
67   * 
68   *   <li>Support for clickable sort headers (see SortHeader
69   *   component).</li>
70   * </ul>
71   * <br/>
72   * Extended data_table that adds some additional features to the 
73   * standard data_table action: see attribute descriptions for 
74   * preserveDataModel, sortColumn, sortAscending and preserveSort. 
75   * <br/>
76   * Unless otherwise specified, all attributes accept static values or EL expressions.
77   * 
78   * @since 1.1.7
79   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
80   * @author Manfred Geiler
81   * @version $Revision: 691856 $ $Date: 2008-09-03 21:40:30 -0500 (miƩ, 03 sep 2008) $
82   */
83  @JSFComponent(
84     name = "t:dataTable",
85     clazz = "org.apache.myfaces.component.html.ext.HtmlDataTable",
86     tagClass = "org.apache.myfaces.generated.taglib.html.ext.HtmlDataTableTag",
87     tagHandler = "org.apache.myfaces.component.html.ext.HtmlDataTableTagHandler")
88  public abstract class AbstractHtmlDataTable extends HtmlDataTableHack implements UserRoleAware, NewspaperTable
89  {
90      private static final Log log = LogFactory.getLog(AbstractHtmlDataTable.class);
91  
92      private static final int PROCESS_DECODES = 1;
93      private static final int PROCESS_VALIDATORS = 2;
94      private static final int PROCESS_UPDATES = 3;
95  
96      private static final boolean DEFAULT_SORTASCENDING = true;
97      private static final boolean DEFAULT_SORTABLE = false;
98      private static final boolean DEFAULT_EMBEDDED = false;
99      private static final boolean DEFAULT_DETAILSTAMP_EXPANDED = false;
100     private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
101 
102     private static final Integer DEFAULT_NEWSPAPER_COLUMNS = new Integer(1);
103     private static final String DEFAULT_NEWSPAPER_ORIENTATION = "vertical";
104     private static final String DEFAULT_DETAILSTAMP_LOCATION = "after";
105 
106     private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
107     /**
108      * the property names
109      */
110     public static final String NEWSPAPER_COLUMNS_PROPERTY = "newspaperColumns";
111     public static final String SPACER_FACET_NAME = "spacer";
112     public static final String NEWSPAPER_ORIENTATION_PROPERTY = "newspaperOrientation";
113 
114     public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
115 
116     public static final String DETAIL_STAMP_ROW_FACET_NAME = "detailStampRow";
117     
118     public static final String TABLE_ROW_FACET_NAME = "row";
119 
120     private _SerializableDataModel _preservedDataModel;
121 
122     private boolean _isValidChildren = true;
123 
124     private Map<String, Boolean> _expandedNodes = new HashMap<String, Boolean>();
125 
126     //private Map<String, Object> _detailRowStates = new HashMap<String, Object>();
127 
128     private TableContext _tableContext = null;
129 
130     public TableContext getTableContext()
131     {
132         if (_tableContext == null)
133         {
134             _tableContext = new TableContext();
135         }
136         return _tableContext;
137     }
138     
139     public void setDetailStamp(UIComponent facet)
140     {
141         getFacets().put(DETAIL_STAMP_FACET_NAME, facet);
142     }
143 
144     /**
145      * This facet renders an additional row after or before (according
146      * to detailStampLocation value) the current row, usually containing
147      * additional information of the related row. It is toggled usually
148      * using varDetailToggle variable and the method toggleDetail().
149      * 
150      * @JSFFacet name="detailStamp"
151      */
152     public UIComponent getDetailStamp()
153     {
154         return (UIComponent) getFacets().get(DETAIL_STAMP_FACET_NAME);
155     }
156 
157     @Override
158     public String getContainerClientId(FacesContext context)
159     {
160         String standardClientId = super.getContainerClientId(context);
161         int rowIndex = getRowIndex();
162         if (rowIndex == -1)
163         {
164             return standardClientId;
165         }
166 
167         String forcedIdIndex = getForceIdIndexFormula();
168         if (forcedIdIndex == null || forcedIdIndex.length() == 0)
169             return standardClientId;
170 
171         // we can get the index less client id directly, because only
172         // getContainerClientId() adds the row index, getClientId() does not.
173         final String indexLessClientId = getClientId(context);
174 
175         //noinspection UnnecessaryLocalVariable
176         String parsedForcedClientId = indexLessClientId + forcedIdIndex;
177         return parsedForcedClientId;
178     }
179 
180     public UIComponent findComponent(String expr)
181     {
182         if (expr.length() > 0 && Character.isDigit(expr.charAt(0)))
183         {
184             char separator = UINamingContainer.getSeparatorChar(getFacesContext());
185             int separatorIndex = expr.indexOf(separator);
186 
187             String rowIndexStr = expr;
188             String remainingPart = null;
189 
190             if (separatorIndex != -1)
191             {
192                 rowIndexStr = expr.substring(0, separatorIndex);
193                 remainingPart = expr.substring(separatorIndex + 1);
194             }
195 
196             int rowIndex = Integer.valueOf(rowIndexStr).intValue();
197 
198             if (remainingPart == null)
199             {
200                 log.error("Wrong syntax of expression : " + expr +
201                         " rowIndex was provided, but no component name.");
202                 return null;
203             }
204 
205             UIComponent comp = super.findComponent(remainingPart);
206 
207             if (comp == null)
208                 return null;
209 
210             //noinspection UnnecessaryLocalVariable
211             UIComponentPerspective perspective = new UIComponentPerspective(this, comp, rowIndex);
212             return perspective;
213         }
214         else
215         {
216             return super.findComponent(expr);
217         }
218     }
219 
220     @Override
221     public boolean invokeOnComponent(FacesContext context, String clientId,
222             ContextCallback callback) throws FacesException
223     {
224         if (context == null || clientId == null || callback == null)
225         {
226             throw new NullPointerException();
227         }
228 
229         final String baseClientId = getClientId(context);
230         
231         // searching for this component?
232         boolean returnValue = baseClientId.equals(clientId);
233 
234         pushComponentToEL(context, this);
235         try
236         {
237             if (returnValue)
238             {
239                 try
240                 {
241                     callback.invokeContextCallback(context, this);
242                     return true;
243                 }
244                 catch (Exception e)
245                 {
246                     throw new FacesException(e);
247                 }
248             }
249     
250             // Now Look throught facets on this UIComponent
251             for (Iterator<UIComponent> it = this.getFacets().values().iterator(); !returnValue && it.hasNext();)
252             {
253                 returnValue = it.next().invokeOnComponent(context, clientId, callback);
254             }
255     
256             if (returnValue)
257             {
258                 return returnValue;
259             }
260             
261             // is the component an inner component?
262             if (clientId.startsWith(baseClientId))
263             {
264                 // Check if the clientId for the component, which we 
265                 // are looking for, has a rowIndex attached
266                 char separator = UINamingContainer.getSeparatorChar(context);
267                 
268                 ValueExpression rowKeyVE = getValueExpression("rowKey");
269                 boolean rowKeyFound = false;
270                 
271                 if (rowKeyVE != null)
272                 {
273                     int oldRow = this.getRowIndex();
274                     try
275                     {
276                         // iterate over the rows
277                         int rowsToProcess = getRows();
278                         // if getRows() returns 0, all rows have to be processed
279                         if (rowsToProcess == 0)
280                         {
281                             rowsToProcess = getRowCount();
282                         }
283                         int rowIndex = getFirst();
284                         for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
285                         {
286                             setRowIndex(rowIndex);
287                             if (!isRowAvailable())
288                             {
289                                 break;
290                             }
291                             
292                             if (clientId.startsWith(getContainerClientId(context)))
293                             {
294                                 rowKeyFound = true;
295                                 break;
296                             }
297                         }
298                         
299                         if (rowKeyFound)
300                         {
301                             returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
302                         }
303                     }
304                     finally
305                     {
306                         this.setRowIndex(oldRow);
307                     }
308                 }
309                 if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
310                 {
311                     String subId = clientId.substring(baseClientId.length() + 1);
312                     String clientRow = subId.substring(0, subId.indexOf(separator));
313         
314                     //Now we save the current position
315                     int oldRow = this.getRowIndex();
316                     
317                     // try-finally --> make sure, that the old row index is restored
318                     try
319                     {
320                         //The conversion is safe, because its already checked on the
321                         //regular expresion
322                         this.setRowIndex(Integer.parseInt(clientRow));
323                         
324                         // check, if the row is available
325                         if (!isRowAvailable())
326                         {
327                             return false;
328                         }
329             
330                         returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
331                     }
332                     finally
333                     {
334                         //Restore the old position. Doing this prevent
335                         //side effects.
336                         this.setRowIndex(oldRow);
337                     }
338                 }
339                 else if (!rowKeyFound) //If rowKeyVE == null --> rowKeyFound = false
340                 {
341                     // MYFACES-2370: search the component in the childrens' facets too.
342                     // We have to check the childrens' facets here, because in MyFaces
343                     // the rowIndex is not attached to the clientId for the children of
344                     // facets of the UIColumns. However, in RI the rowIndex is 
345                     // attached to the clientId of UIColumns' Facets' children.
346                     for (Iterator<UIComponent> itChildren = this.getChildren().iterator();
347                             !returnValue && itChildren.hasNext();)
348                     {
349                         UIComponent child = itChildren.next();
350                         // This is the only part different to UIData.invokeOnComponent. Since we have
351                         // an auto wrapping on columns feature, it is necessary to check columns ids
352                         // without row for invokeOnComponent, but do not traverse all elements, so
353                         // save/restore algorithm could be able to remove / add them.  
354                         if (child instanceof UIColumn && clientId.equals(child.getClientId(context)))
355                         {
356                             try {
357                                 callback.invokeContextCallback(context, child);
358                             } catch (Exception e) {
359                                 throw new FacesException(e);
360                             }
361                             returnValue = true;
362                         }
363                         // process the child's facets
364                         for (Iterator<UIComponent> itChildFacets = child.getFacets().values().iterator(); 
365                                 !returnValue && itChildFacets.hasNext();)
366                         {
367                             //recursive call to find the component
368                             returnValue = itChildFacets.next().invokeOnComponent(context, clientId, callback);
369                         }
370                     }
371                 }
372             }
373         }
374         finally
375         {
376             //all components must call popComponentFromEl after visiting is finished
377             popComponentFromEL(context);
378         }
379 
380         return returnValue;
381     }
382     
383     private boolean invokeOnComponentTraverseRow(FacesContext context, String clientId,
384             ContextCallback callback)
385     {
386         boolean returnValue = false;
387         for (Iterator<UIComponent> it1 = getChildren().iterator(); 
388             !returnValue && it1.hasNext();)
389         {
390             //recursive call to find the component
391             returnValue = it1.next().invokeOnComponent(context, clientId, callback);
392         }
393 
394         if (!returnValue)
395         {
396             UIComponent detailStampRowFacet = getFacet(DETAIL_STAMP_ROW_FACET_NAME);
397             if (detailStampRowFacet != null)
398             {
399                 returnValue = detailStampRowFacet.invokeOnComponent(context, clientId, callback);
400             }
401             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
402             if (detailStampFacet != null)
403             {
404                 returnValue = detailStampFacet.invokeOnComponent(context, clientId, callback);
405             }
406             UIComponent tableRowFacet = getFacet(TABLE_ROW_FACET_NAME);
407             if (tableRowFacet != null)
408             {
409                 returnValue = tableRowFacet.invokeOnComponent(context, clientId, callback);
410             }
411         }
412         return returnValue;
413     }
414     
415     public boolean visitTree(VisitContext context, VisitCallback callback)
416     {
417         if (!isVisitable(context))
418         {
419             return false;
420         }
421 
422         // save the current row index
423         int oldRowIndex = getRowIndex();
424         // set row index to -1 to process the facets and to get the rowless clientId
425         setRowIndex(-1);
426         // push the Component to EL
427         pushComponentToEL(context.getFacesContext(), this);
428         try
429         {
430             VisitResult visitResult = context.invokeVisitCallback(this,
431                     callback);
432             switch (visitResult)
433             {
434             //we are done nothing has to be processed anymore
435             case COMPLETE:
436                 return true;
437 
438             case REJECT:
439                 return false;
440 
441                 //accept
442             default:
443                 // determine if we need to visit our children 
444                 Collection<String> subtreeIdsToVisit = context
445                         .getSubtreeIdsToVisit(this);
446                 boolean doVisitChildren = subtreeIdsToVisit != null
447                         && !subtreeIdsToVisit.isEmpty();
448                 if (doVisitChildren)
449                 {
450                     // visit the facets of the component
451                     for (UIComponent facet : getFacets().values())
452                     {
453                         if (facet.visitTree(context, callback))
454                         {
455                             return true;
456                         }
457                     }
458                     Boolean skipIterationHint = (Boolean) context.getFacesContext().getAttributes().get(SKIP_ITERATION_HINT);
459                     if (skipIterationHint != null && skipIterationHint.booleanValue())
460                     {
461                         // If SKIP_ITERATION is enabled, do not take into account rows.
462                         if (getChildCount() > 0) {
463                             for (UIComponent child : getChildren()) {
464                                 if (child.visitTree(context, callback)) {
465                                     return true;
466                                 }
467                             }
468                         }
469                     }
470                     else
471                     {
472                         // visit every column directly without visiting its children 
473                         // (the children of every UIColumn will be visited later for 
474                         // every row) and also visit the column's facets
475                         for (UIComponent child : getChildren())
476                         {
477                             if (child instanceof UIColumn)
478                             {
479                                 VisitResult columnResult = context.invokeVisitCallback(child, callback);
480                                 if (columnResult == VisitResult.COMPLETE)
481                                 {
482                                     return true;
483                                 }
484                                 for (UIComponent facet : child.getFacets().values())
485                                 {
486                                     if (facet.visitTree(context, callback))
487                                     {
488                                         return true;
489                                     }
490                                 }
491                             }
492                         }
493                         boolean visitDetailStamp = (getFacet(DETAIL_STAMP_FACET_NAME) != null);
494                         boolean visitDetailStampRow = (getFacet(DETAIL_STAMP_ROW_FACET_NAME) != null);
495                         boolean visitTableRow = (getFacet(TABLE_ROW_FACET_NAME) != null);
496                         
497                         // iterate over the rows
498                         int rowsToProcess = getRows();
499                         // if getRows() returns 0, all rows have to be processed
500                         if (rowsToProcess == 0)
501                         {
502                             rowsToProcess = getRowCount();
503                         }
504                         int rowIndex = getFirst();
505                         for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
506                         {
507                             setRowIndex(rowIndex);
508                             if (!isRowAvailable())
509                             {
510                                 return false;
511                             }
512                             // visit the children of every child of the UIData that is an instance of UIColumn
513                             for (UIComponent child : getChildren())
514                             {
515                                 if (child instanceof UIColumn)
516                                 {
517                                     for (UIComponent grandchild : child
518                                             .getChildren())
519                                     {
520                                         if (grandchild.visitTree(context, callback))
521                                         {
522                                             return true;
523                                         }
524                                     }
525                                 }
526                             }
527                             if (visitDetailStampRow)
528                             {
529                                 UIComponent detailStampRowFacet = getFacet(DETAIL_STAMP_ROW_FACET_NAME);
530                                 if (detailStampRowFacet.visitTree(context, callback))
531                                 {
532                                     return true;
533                                 }
534                             }
535                             if (visitDetailStamp)
536                             {
537                                 UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
538                                 if (detailStampFacet.visitTree(context, callback))
539                                 {
540                                     return true;
541                                 }
542                             }
543                             if (visitTableRow)
544                             {
545                                 UIComponent tableRowFacet = getFacet(TABLE_ROW_FACET_NAME);
546                                 if (tableRowFacet.visitTree(context, callback))
547                                 {
548                                     return true;
549                                 }
550                             }
551                         }
552                     }
553                 }
554             }
555         }
556         finally
557         {
558             // pop the component from EL and restore the old row index
559             popComponentFromEL(context.getFacesContext());
560             setRowIndex(oldRowIndex);
561         }
562 
563         // Return false to allow the visiting to continue
564         return false;
565     }
566 
567     public void setRowIndex(int rowIndex)
568     {
569         //FacesContext facesContext = FacesContext.getCurrentInstance();
570 
571         if (rowIndex < -1)
572         {
573             throw new IllegalArgumentException("rowIndex is less than -1");
574         }
575 
576         //UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
577         /*Just for obtaining an iterator which must be passed to saveDescendantComponentStates()*/
578         //ArrayList<UIComponent> detailStampList = new ArrayList<UIComponent>(1);
579         //detailStampList.add(facet);
580         //if (getRowIndex() != -1 && facet != null)
581         //{
582         //    _detailRowStates.put(getContainerClientId(facesContext), saveDescendantComponentStates(detailStampList.iterator(), false));
583         //}
584 
585         String rowIndexVar = getRowIndexVar();
586         String rowCountVar = getRowCountVar();
587         String previousRowDataVar = getPreviousRowDataVar();
588         if (rowIndexVar != null || rowCountVar != null || previousRowDataVar != null)
589         {
590             Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
591 
592             if (previousRowDataVar != null && rowIndex >= 0) //we only need to provide the previousRowDataVar for a valid rowIndex
593             {
594                 if (isRowAvailable())
595                 {
596                     //previous row is available
597                     requestMap.put(previousRowDataVar, getRowData());
598                 }
599                 else
600                 {
601                     //no previous row available
602                     requestMap.put(previousRowDataVar, null);
603                 }
604             }
605 
606             super.setRowIndex(rowIndex);
607 
608             if (rowIndex >= 0)
609             {
610                 //regular row index, update request scope variables
611                 if (rowIndexVar != null)
612                 {
613                     requestMap.put(rowIndexVar, new Integer(rowIndex));
614                 }
615 
616                 if (rowCountVar != null)
617                 {
618                     requestMap.put(rowCountVar, new Integer(getRowCount()));
619                 }
620             }
621             else
622             {
623                 //rowIndex == -1 means end of loop --> remove request scope variables
624                 if (rowIndexVar != null)
625                 {
626                     requestMap.remove(rowIndexVar);
627                 }
628 
629                 if (rowCountVar != null)
630                 {
631                     requestMap.remove(rowCountVar);
632                 }
633 
634                 if (previousRowDataVar != null)
635                 {
636                     requestMap.remove(previousRowDataVar);
637                 }
638             }
639         }
640         else
641         {
642             // no extended var attributes defined, no special treatment
643             super.setRowIndex(rowIndex);
644         }
645 
646         //if (rowIndex != -1 && facet != null)
647         //{
648         //    Object rowState = _detailRowStates.get(getContainerClientId(facesContext));
649 
650         //    restoreDescendantComponentStates(detailStampList.iterator(),
651         //            rowState, false);
652 
653         //}
654         UIComponent detailStampRowFacet = getFacet(DETAIL_STAMP_ROW_FACET_NAME);
655         if (detailStampRowFacet != null)
656         {
657             detailStampRowFacet.setId(detailStampRowFacet.getId());
658         }
659         UIComponent tableRowFacet = getFacet(TABLE_ROW_FACET_NAME);
660         if (tableRowFacet != null){
661             tableRowFacet.setId(tableRowFacet.getId());
662         }
663 
664         if (getStateHelper().get(PropertyKeys.varDetailToggler) != null)
665         {
666             Map requestMap = getFacesContext().getExternalContext().getRequestMap();
667             requestMap.put(getStateHelper().get(PropertyKeys.varDetailToggler), this);
668         }
669     }
670     
671     @Override
672     protected Object saveDescendantComponentStates()
673     {
674         if (getFacetCount() > 0)
675         {
676             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
677             if (detailStampFacet != null)
678             {
679                 return saveDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false);
680             }
681         }
682         return super.saveDescendantComponentStates();
683     }
684     
685     @Override
686     protected void restoreDescendantComponentStates(Object state)
687     {
688         if (getFacetCount() > 0)
689         {
690             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
691             if (detailStampFacet != null)
692             {
693                 restoreDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), state, false);
694                 return;
695             }
696         }
697         super.restoreDescendantComponentStates(state);
698     }
699 
700     @Override
701     protected void restoreFullDescendantComponentDeltaStates(FacesContext facesContext,
702             Map<String, Object> rowState, Object initialState)
703     {
704         if (getFacetCount() > 0)
705         {
706             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
707             if (detailStampFacet != null)
708             {
709                 restoreFullDescendantComponentDeltaStates(facesContext, new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), rowState, initialState, false, getContainerClientId(facesContext));
710                 return;
711             }
712         }
713         super.restoreFullDescendantComponentDeltaStates(facesContext, rowState, initialState);
714     }
715 
716     @Override
717     protected void restoreFullDescendantComponentStates(FacesContext facesContext,
718             Object initialState)
719     {
720         if (getFacetCount() > 0)
721         {
722             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
723             if (detailStampFacet != null)
724             {
725                 restoreFullDescendantComponentStates(facesContext, new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), initialState, false);
726                 return;
727             }
728         }
729         super.restoreFullDescendantComponentStates(facesContext, initialState);
730     }
731 
732     @Override
733     protected Collection<Object[]> saveDescendantInitialComponentStates(
734             FacesContext facesContext)
735     {
736         if (getFacetCount() > 0)
737         {
738             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
739             if (detailStampFacet != null)
740             {
741                 return saveDescendantInitialComponentStates(facesContext, new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false);
742             }
743         }
744         return super.saveDescendantInitialComponentStates(facesContext);
745     }
746     
747     @Override
748     protected Map<String,Object> saveFullDescendantComponentStates(FacesContext facesContext)
749     {
750         if (getFacetCount() > 0)
751         {
752             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
753             if (detailStampFacet != null)
754             {
755                 return saveFullDescendantComponentStates(facesContext, null, new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false, getContainerClientId(facesContext));
756             }
757         }
758         return super.saveFullDescendantComponentStates(facesContext);
759     }
760     
761     /*
762     @Override
763     public void deleteRowStateForRow(int deletedIndex)
764     {
765         super.deleteRowStateForRow(deletedIndex);
766 
767         if (getFacetCount() > 0)
768         {
769             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
770             if (detailStampFacet != null)
771             {
772                 // save row index
773                 int savedRowIndex = getRowIndex();
774                 
775                 FacesContext facesContext = getFacesContext();
776                 setRowIndex(deletedIndex);
777                 String currentRowStateKey = getContainerClientId(facesContext);
778         
779                 int rowCount = getRowCount();
780                 if (isPreserveRowComponentState())
781                 {
782                     for (int index = deletedIndex + 1; index < rowCount; ++index)
783                     {
784                         setRowIndex(index);
785                         String nextRowStateKey = getContainerClientId(facesContext);
786             
787                         Object nextRowState = _detailRowStates.get(nextRowStateKey);
788                         if (nextRowState == null)
789                         {
790                             _detailRowStates.remove(currentRowStateKey);
791                         }
792                         else
793                         {
794                             _detailRowStates.put(currentRowStateKey, nextRowState);
795                         }
796                         currentRowStateKey = nextRowStateKey;
797                     }
798                     
799                     // restore saved row index
800                     setRowIndex(savedRowIndex);
801             
802                     // Remove last row
803                     _detailRowStates.remove(currentRowStateKey);
804                 }
805                 else
806                 {
807                     for (int index = deletedIndex + 1; index < rowCount; ++index)
808                     {
809                         setRowIndex(index);
810                         String nextRowStateKey = getContainerClientId(facesContext);
811             
812                         Object nextRowState = _detailRowStates.get(nextRowStateKey);
813                         if (nextRowState == null)
814                         {
815                             _detailRowStates.remove(currentRowStateKey);
816                         }
817                         else
818                         {
819                             _detailRowStates.put(currentRowStateKey, nextRowState);
820                         }
821                         currentRowStateKey = nextRowStateKey;
822                     }
823                     
824                     // restore saved row index
825                     setRowIndex(savedRowIndex);
826             
827                     // Remove last row
828                     _detailRowStates.remove(currentRowStateKey);
829                 }
830             }
831         }
832     }*/
833 
834     public void processDecodes(FacesContext context)
835     {
836         if (!isRendered())
837         {
838             return;
839         }
840 
841         // We must remove and then replace the facet so that
842         // it is not processed by default facet handling code
843         //
844         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
845         //super.processDecodes(context);
846         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME, (UIComponent)facet);
847         setRowIndex(-1);
848         processFacets(context, PROCESS_DECODES);
849         processColumnFacets(context, PROCESS_DECODES);
850         processColumnChildren(context, PROCESS_DECODES);
851         setRowIndex(-1);
852         try
853         {
854             decode(context);
855         }
856         catch (RuntimeException e)
857         {
858             context.renderResponse();
859             throw e;
860         }
861 
862         setRowIndex(-1);
863         processColumns(context, PROCESS_DECODES);
864         setRowIndex(-1);
865         processDetails(context, PROCESS_DECODES);
866         setRowIndex(-1);
867     }
868     
869     private void processFacets(FacesContext context, int processAction)
870     {
871         for (Map.Entry<String, UIComponent> entry : getFacets().entrySet())
872         {
873             if (!DETAIL_STAMP_FACET_NAME.equals(entry.getKey()))
874             {
875                 process(context, entry.getValue(), processAction);
876             }
877         }
878     }
879 
880     /**
881      * Invoke the specified phase on all facets of all UIColumn children of this component. Note that no methods are
882      * called on the UIColumn child objects themselves.
883      * 
884      * @param context
885      *            is the current faces context.
886      * @param processAction
887      *            specifies a JSF phase: decode, validate or update.
888      */
889     private void processColumnFacets(FacesContext context, int processAction)
890     {
891         for (UIComponent child : getChildren())
892         {
893             if (child instanceof UIColumn)
894             {
895                 if (!child.isRendered())
896                 {
897                     // Column is not visible
898                     continue;
899                 }
900                 
901                 for (UIComponent facet : child.getFacets().values())
902                 {
903                     process(context, facet, processAction);
904                 }
905             }
906         }
907     }
908 
909     /**
910      * Invoke the specified phase on all non-facet children of all UIColumn children of this component. Note that no
911      * methods are called on the UIColumn child objects themselves.
912      * 
913      * @param context
914      *            is the current faces context.
915      * @param processAction
916      *            specifies a JSF phase: decode, validate or update.
917      */
918     private void processColumnChildren(FacesContext context, int processAction)
919     {
920         int first = getFirst();
921         int rows = getRows();
922         int last;
923         if (rows == 0)
924         {
925             last = getRowCount();
926         }
927         else
928         {
929             last = first + rows;
930         }
931         for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
932         {
933             setRowIndex(rowIndex);
934 
935             // scrolled past the last row
936             if (!isRowAvailable())
937             {
938                 break;
939             }
940 
941             for (UIComponent child : getChildren())
942             {
943                 if (child instanceof UIColumn)
944                 {
945                     if (!child.isRendered())
946                     {
947                         // Column is not visible
948                         continue;
949                     }
950                     for (UIComponent columnChild : child.getChildren())
951                     {
952                         process(context, columnChild, processAction);
953                     }
954                 }
955             }
956         }
957     }
958 
959     /**
960      * @param context
961      * @param processAction
962      */
963     private void processDetails(FacesContext context, int processAction)
964     {
965         UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
966 
967         if (facet != null)
968         {
969             int first = getFirst();
970             int rows = getRows();
971             int last;
972             if (rows == 0)
973             {
974                 last = getRowCount();
975             }
976             else
977             {
978                 last = first + rows;
979             }
980             for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
981             {
982                 setRowIndex(rowIndex);
983 
984                 //scrolled past the last row
985                 if (!isRowAvailable())
986                 {
987                     break;
988                 }
989 
990                 if (!isCurrentDetailExpanded())
991                 {
992                     continue;
993                 }
994 
995                 // If we are in the decode phase, the values restored into our
996                 // facet in setRowIndex() may be incorrect. This will happen
997                 // if some input fields are rendered in some rows, but not
998                 // rendered in others. In this case the input field components
999                 // will still contain the _submittedValue from the previous row
1000                 // that had that input field and _submittedValue will not be set to
1001                 // null by the process() method if there was no value submitted.
1002                 // Thus, an invalid component state for that row will be saved in
1003                 // _detailRowStates. The validation phase will not put a null into
1004                 // _sumbittedValue either, b/c the component is not rendered, so
1005                 // validation code doesn't run. This erroneous value will propagate all the way
1006                 // to the render phase, and result in all rows on the current page being
1007                 // rendered with the "stuck" _submittedValue, rather than evaluating the
1008                 // value to render for every row.
1009                 //
1010                 // We can fix this by initializing _submittedValue of all input fields in the facet
1011                 // to null before calling the process() method below during the decode phase.
1012                 //
1013                 if (PROCESS_DECODES == processAction)
1014                 {
1015                     resetAllSubmittedValues(facet);
1016                 }
1017 
1018                 process(context, facet, processAction);
1019 
1020                 // This code comes from TOMAHAWK-493, but really the problem was caused by
1021                 // TOMAHAWK-1534, by a bad comparison of rowIndex. The solution proposed is override
1022                 // the default iterator for save/restore on HtmlDataTableHack.setRowIndex(), to
1023                 // include the detailStamp. In this way, we'll be sure that the state is correctly
1024                 // saved and the component clientId is reset for all components that requires it
1025                 //if ( rowIndex == (last - 1) )
1026                 //{
1027                 //    ArrayList<UIComponent> detailStampList = new ArrayList<UIComponent>(1);
1028                 //    detailStampList.add(facet);
1029                 //    _detailRowStates.put(
1030                 //            getContainerClientId(FacesContext.getCurrentInstance()),
1031                 //                saveDescendantComponentStates(detailStampList.iterator(),false));
1032                 //}
1033             }
1034         }
1035     }
1036 
1037     private void resetAllSubmittedValues(UIComponent component)
1038     {
1039         if (component instanceof EditableValueHolder)
1040         {
1041             ((EditableValueHolder) component).setSubmittedValue(null);
1042         }
1043 
1044         for (Iterator it = component.getFacetsAndChildren(); it.hasNext();)
1045         {
1046             resetAllSubmittedValues((UIComponent) it.next());
1047         }
1048     }
1049 
1050     /**
1051      * @param context
1052      * @param processAction
1053      */
1054     private void processColumns(FacesContext context, int processAction)
1055     {
1056         for (Iterator it = getChildren().iterator(); it.hasNext();)
1057         {
1058             UIComponent child = (UIComponent) it.next();
1059             if (child instanceof UIColumns)
1060             {
1061                 process(context, child, processAction);
1062             }
1063         }
1064     }
1065 
1066     private void process(FacesContext context, UIComponent component, int processAction)
1067     {
1068         switch (processAction)
1069         {
1070             case PROCESS_DECODES:
1071                 component.processDecodes(context);
1072                 break;
1073             case PROCESS_VALIDATORS:
1074                 component.processValidators(context);
1075                 break;
1076             case PROCESS_UPDATES:
1077                 component.processUpdates(context);
1078                 break;
1079         }
1080     }
1081 
1082     public void processValidators(FacesContext context)
1083     {
1084         if (!isRendered())
1085         {
1086             return;
1087         }
1088         // We must remove and then replace the facet so that
1089         // it is not processed by default facet handling code
1090         //
1091         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
1092         //super.processValidators(context);
1093         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
1094         setRowIndex(-1);
1095         processFacets(context, PROCESS_VALIDATORS);
1096         processColumnFacets(context, PROCESS_VALIDATORS);
1097         processColumnChildren(context, PROCESS_VALIDATORS);
1098         setRowIndex(-1);
1099 
1100         processColumns(context, PROCESS_VALIDATORS);
1101         setRowIndex(-1);
1102         processDetails(context, PROCESS_VALIDATORS);
1103         setRowIndex(-1);
1104 
1105         if (context.getRenderResponse())
1106         {
1107             _isValidChildren = false;
1108         }
1109     }
1110 
1111     public void processUpdates(FacesContext context)
1112     {
1113         if (!isRendered())
1114         {
1115             return;
1116         }
1117 
1118         // We must remove and then replace the facet so that
1119         // it is not processed by default facet handling code
1120         //
1121         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
1122         //super.processUpdates(context);
1123         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
1124         
1125         setRowIndex(-1);
1126         processFacets(context, PROCESS_UPDATES);
1127         processColumnFacets(context, PROCESS_UPDATES);
1128         processColumnChildren(context, PROCESS_UPDATES);
1129         setRowIndex(-1);
1130 
1131         processColumns(context, PROCESS_UPDATES);
1132         setRowIndex(-1);
1133         processDetails(context, PROCESS_UPDATES);
1134         setRowIndex(-1);
1135 
1136         if (isPreserveDataModel())
1137         {
1138             updateModelFromPreservedDataModel(context);
1139         }
1140 
1141         if (context.getRenderResponse())
1142         {
1143             _isValidChildren = false;
1144         }
1145     }
1146 
1147     private void updateModelFromPreservedDataModel(FacesContext context)
1148     {
1149         ValueExpression vb = getValueExpression("value");
1150         if (vb != null && !vb.isReadOnly(context.getELContext()))
1151         {
1152             DataModel qdm = getDataModel();
1153             if (qdm instanceof _SerializableDataModel)
1154             {
1155                 _SerializableDataModel dm = (_SerializableDataModel) qdm;
1156                 Class type = (getValueType() == null) ? 
1157                         vb.getType(context.getELContext()) : 
1158                             ClassUtils.simpleClassForName(getValueType());
1159                 Class dmType = dm.getClass();
1160                 if (DataModel.class.isAssignableFrom(type))
1161                 {
1162                     vb.setValue(context.getELContext(), dm);
1163                 }
1164                 else if (List.class.isAssignableFrom(type) || _SerializableListDataModel.class.isAssignableFrom(dmType))
1165                 {
1166                     vb.setValue(context.getELContext(), dm.getWrappedData());
1167                 }
1168                 else if (OBJECT_ARRAY_CLASS.isAssignableFrom(type))
1169                 {
1170                     List lst = (List) dm.getWrappedData();
1171                     vb.setValue(context.getELContext(), lst.toArray(new Object[lst.size()]));
1172                 }
1173                 else if (ResultSet.class.isAssignableFrom(type))
1174                 {
1175                     throw new UnsupportedOperationException(this.getClass().getName()
1176                             + " UnsupportedOperationException");
1177                 }
1178                 else
1179                 {
1180                     //Assume scalar data model
1181                     List lst = (List) dm.getWrappedData();
1182                     if (lst!= null && lst.size() > 0)
1183                     {
1184                         vb.setValue(context.getELContext(), lst.get(0));
1185                     }
1186                     else
1187                     {
1188                         vb.setValue(context.getELContext(), null);
1189                     }
1190                 }
1191             }
1192         }
1193         _preservedDataModel = null;
1194     }
1195     
1196     public void markInitialState()
1197     {
1198         if (isPreserveRowComponentState() || isRowStatePreserved())
1199         {
1200             if (getFacesContext().getAttributes().containsKey("javax.faces.view.ViewDeclarationLanguage.IS_BUILDING_INITIAL_STATE"))
1201             {
1202                 //Since we re
1203                 replaceColumnsWithCommandSortHeadersIfNeeded(getFacesContext());
1204             }
1205         }
1206         super.markInitialState();
1207     }
1208 
1209     public void encodeBegin(FacesContext context) throws IOException
1210     {
1211         if (!isRendered())
1212             return;
1213 
1214         if (_isValidChildren && !hasErrorMessages(context))
1215         {
1216             _preservedDataModel = null;
1217         }
1218 
1219         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1220         {
1221             UIComponent component = (UIComponent) iter.next();
1222             if (component instanceof UIColumns)
1223             {
1224                 // Merge the columns from the tomahawk dynamic component
1225                 // into this object.
1226                 ((UIColumns) component).encodeTableBegin(context);
1227             }
1228         }
1229         
1230         replaceColumnsWithCommandSortHeadersIfNeeded(context);
1231 
1232         // Now invoke the superclass encodeBegin, which will eventually
1233         // execute the encodeBegin for the associated renderer.
1234         super.encodeBegin(context);
1235     }
1236     
1237     private void replaceColumnsWithCommandSortHeadersIfNeeded(FacesContext context)
1238     {
1239         //replace facet header content component of the columns, with a new command sort header component
1240         //if sortable=true, replace it for all, if not just for the columns marked as sortable
1241         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1242         {
1243             UIComponent component = (UIComponent) iter.next();
1244             if (component instanceof UIColumn)
1245             {
1246                 UIColumn aColumn = (UIColumn) component;
1247                 UIComponent headerFacet = aColumn.getHeader();
1248 
1249                 boolean replaceHeaderFacets = isSortable(); //if the table is sortable, all
1250                 //header facets can be changed if needed
1251                 String columnName = null;
1252                 String propertyName = null;
1253                 boolean defaultSorted = false;
1254 
1255                 if (aColumn instanceof HtmlSimpleColumn)
1256                 {
1257                     HtmlSimpleColumn asColumn = (HtmlSimpleColumn) aColumn;
1258                     propertyName = asColumn.getSortPropertyName();
1259                     defaultSorted = asColumn.isDefaultSorted();
1260 
1261                     if (asColumn.isSortable())
1262                         replaceHeaderFacets = true;
1263                 }
1264 
1265                 //replace header facet with a sortable header component if needed
1266                 if (replaceHeaderFacets && isSortHeaderNeeded(aColumn, headerFacet))
1267                 {
1268                     propertyName = propertyName != null ? propertyName : getSortPropertyFromEL(aColumn);
1269                     if (propertyName == null)
1270                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
1271 
1272                     if (headerFacet instanceof UIPanel)
1273                     {
1274                         // In jsf 2.0, facets allow more than one component. That causes a side effect on
1275                         // facelets mode when auto wrapping is used, because the algorithm is not aware
1276                         // we wrap everything inside a HtmlCommandSortHeader. We have to check
1277                         // here that condition and if so, fix it and threat it correctly. 
1278                         HtmlCommandSortHeader sortHeader = null;
1279                         for (UIComponent childHeaderFacet : headerFacet.getChildren())
1280                         {
1281                             if (childHeaderFacet instanceof HtmlCommandSortHeader)
1282                             {
1283                                 sortHeader = (HtmlCommandSortHeader) childHeaderFacet;
1284                                 break;
1285                             }
1286                         }
1287                         if (sortHeader != null)
1288                         {
1289                             aColumn.getFacets().remove("header");
1290                             aColumn.setHeader(sortHeader);
1291                             headerFacet = sortHeader;
1292                             
1293                             //command sort headers are already in place, just store the column name and sort property name
1294                             columnName = sortHeader.getColumnName();
1295                             propertyName = sortHeader.getPropertyName();
1296 
1297                             //if the command sort header component doesn't specify a sort property, determine it
1298                             if (propertyName == null)
1299                             {
1300                                 propertyName = getSortPropertyFromEL(aColumn);
1301                                 sortHeader.setPropertyName(propertyName);
1302                             }
1303 
1304                             if (propertyName == null)
1305                                 log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");                            
1306                         }
1307                     }
1308                     if (headerFacet != null && isSortHeaderNeeded(aColumn, headerFacet))
1309                     {
1310                         // We need to force PreRemoveFromViewEvent on the wrapped facet, so we remove it manually here.
1311                         // Otherwise the component is just moved on the view and no event is triggered. 
1312                         aColumn.getFacets().remove("header");
1313                         
1314                         HtmlCommandSortHeader sortHeader = createSortHeaderComponent(context, aColumn, headerFacet, propertyName);
1315                         columnName = sortHeader.getColumnName();
1316 
1317                         aColumn.setHeader(sortHeader);
1318                         //setParent is called internally!
1319                         //sortHeader.setParent(aColumn);
1320                     }
1321                 }
1322                 else if (headerFacet instanceof HtmlCommandSortHeader)
1323                 {
1324                     //command sort headers are already in place, just store the column name and sort property name
1325                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
1326                     columnName = sortHeader.getColumnName();
1327                     propertyName = sortHeader.getPropertyName();
1328 
1329                     //if the command sort header component doesn't specify a sort property, determine it
1330                     if (propertyName == null)
1331                     {
1332                         propertyName = getSortPropertyFromEL(aColumn);
1333                         sortHeader.setPropertyName(propertyName);
1334                     }
1335 
1336                     if (propertyName == null)
1337                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
1338                 }
1339 
1340                 //make the column marked as default sorted be the current sorted column
1341                 //When getSortColumn() eval to a ValueExpression and it is a String, it could return
1342                 //null but here it is coerced to "", so we need to assume "" as null.
1343                 String sortColumn = getSortColumn();
1344                 if (defaultSorted &&  (sortColumn == null ? true : sortColumn.length() == 0))
1345                 {
1346                     setSortColumn(columnName);
1347                     setSortProperty(propertyName);
1348                 }
1349             }
1350         }
1351     }
1352 
1353     /**
1354      *
1355      */
1356     protected boolean isSortHeaderNeeded(UIColumn parentColumn, UIComponent headerFacet)
1357     {
1358         return !(headerFacet instanceof HtmlCommandSortHeader);
1359     }
1360 
1361     /**
1362      *
1363      */
1364     protected HtmlCommandSortHeader createSortHeaderComponent(FacesContext context, UIColumn parentColumn, UIComponent initialHeaderFacet, String propertyName)
1365     {
1366         Application application = context.getApplication();
1367 
1368         HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) application.createComponent(HtmlCommandSortHeader.COMPONENT_TYPE);
1369         String id = context.getViewRoot().createUniqueId();
1370         sortHeader.setId(id);
1371         sortHeader.setColumnName(id);
1372         sortHeader.setPropertyName(propertyName);
1373         sortHeader.setArrow(true);
1374         sortHeader.setImmediate(true); //needed to work when dataScroller is present
1375         sortHeader.getChildren().add(initialHeaderFacet);
1376         initialHeaderFacet.setParent(sortHeader);
1377 
1378         return sortHeader;
1379     }
1380 
1381     /**
1382      *
1383      */
1384     protected String getSortPropertyFromEL(UIComponent component)
1385     {
1386         if (getVar() == null)
1387         {
1388             log.warn("There is no 'var' specified on the dataTable, sort properties cannot be determined automaticaly.");
1389             return null;
1390         }
1391 
1392         for (Iterator<UIComponent> iter = component.getChildren().iterator(); iter.hasNext();)
1393         {
1394             UIComponent aChild = (UIComponent) iter.next();
1395             if (aChild.isRendered())
1396             {
1397                 ValueExpression vb = aChild.getValueExpression("value");
1398                 if (vb != null)
1399                 {
1400                     String expressionString = vb.getExpressionString();
1401 
1402                     int varIndex = expressionString.indexOf(getVar() + ".");
1403                     if (varIndex != -1)
1404                     {
1405                         int varEndIndex = varIndex + getVar().length();
1406                         String tempProp = expressionString.substring(varEndIndex + 1, expressionString.length());
1407 
1408                         StringTokenizer tokenizer = new StringTokenizer(tempProp, " +[]{}-/*|?:&<>!=()%");
1409                         if (tokenizer.hasMoreTokens())
1410                             return tokenizer.nextToken();
1411                     }
1412                 }
1413                 else
1414                 {
1415                     String sortProperty = getSortPropertyFromEL(aChild);
1416                     if (sortProperty != null)
1417                         return sortProperty;
1418                 }
1419             }
1420         }
1421 
1422         return null;
1423     }
1424 
1425     /**
1426      * @return the index coresponding to the given column name.
1427      */
1428     protected int columnNameToIndex(String columnName)
1429     {
1430         int index = 0;
1431         for (Iterator<UIComponent> iter = getChildren().iterator(); iter.hasNext();)
1432         {
1433             UIComponent aChild = (UIComponent) iter.next();
1434             if (aChild instanceof UIColumn)
1435             {
1436                 UIComponent headerFacet = ((UIColumn) aChild).getHeader();
1437                 if (headerFacet != null && headerFacet instanceof HtmlCommandSortHeader)
1438                 {
1439                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
1440                     if (columnName != null && columnName.length()>0 && columnName.equals(sortHeader.getColumnName()))
1441                         return index;
1442                 }
1443             }
1444 
1445             index += 1;
1446         }
1447 
1448         return -1;
1449     }
1450 
1451     /**
1452      * @see javax.faces.component.UIData#encodeEnd(javax.faces.context.FacesContext)
1453      */
1454     public void encodeEnd(FacesContext context) throws IOException
1455     {
1456         super.encodeEnd(context);
1457         for (Iterator<UIComponent> iter = getChildren().iterator(); iter.hasNext();)
1458         {
1459             UIComponent component = (UIComponent) iter.next();
1460             if (component instanceof UIColumns)
1461             {
1462                 ((UIColumns) component).encodeTableEnd(context);
1463             }
1464         }
1465     }
1466 
1467     /**
1468      * The index of the first row to be displayed, where 0 is the first row.
1469      * 
1470      */
1471     @JSFProperty
1472     public int getFirst()
1473     {
1474         if (_preservedDataModel != null)
1475         {
1476             //Rather get the currently restored DataModel attribute
1477             return _preservedDataModel.getFirst();
1478         }
1479         else
1480         {
1481             return super.getFirst();
1482         }
1483     }
1484 
1485     public void setFirst(int first)
1486     {
1487         if (_preservedDataModel != null)
1488         {
1489             //Also change the currently restored DataModel attribute
1490             _preservedDataModel.setFirst(first);
1491         }
1492         super.setFirst(first);
1493     }
1494 
1495     /**
1496      *  The number of rows to be displayed. Specify zero for all remaining rows in the table.
1497      * 
1498      */
1499     @JSFProperty
1500     public int getRows()
1501     {
1502         if (_preservedDataModel != null)
1503         {
1504             //Rather get the currently restored DataModel attribute
1505             return _preservedDataModel.getRows();
1506         }
1507         else
1508         {
1509             return super.getRows();
1510         }
1511     }
1512 
1513     public void setRows(int rows)
1514     {
1515         if (_preservedDataModel != null)
1516         {
1517             //Also change the currently restored DataModel attribute
1518             _preservedDataModel.setRows(rows);
1519         }
1520         super.setRows(rows);
1521     }
1522 
1523     public Object saveState(FacesContext context)
1524     {
1525         boolean preserveSort = isPreserveSort();
1526 
1527         if (initialStateMarked())
1528         {
1529             Object parentSaved = super.saveState(context);
1530             boolean preserveDataModel = isPreserveDataModel();
1531             
1532             if (parentSaved == null && !preserveSort 
1533                     && !preserveDataModel && isExpandedEmpty())
1534             {
1535                 //No values
1536                 return null;
1537             }
1538             
1539             Object[] values = new Object[5];
1540             values[0] = parentSaved;
1541             
1542             if (isPreserveDataModel())
1543             {
1544                 _preservedDataModel = getSerializableDataModel();
1545                 values[1] = saveAttachedState(context, _preservedDataModel);
1546             }
1547             else
1548             {
1549                 values[1] = null;
1550             }
1551             
1552             values[2] = preserveSort ? getSortColumn() : null;
1553             values[3] = preserveSort ? Boolean.valueOf(isSortAscending()) : null;
1554             values[4] = _expandedNodes;
1555             return values;
1556         }
1557         else
1558         {
1559             Object[] values = new Object[5];
1560             values[0] = super.saveState(context);
1561 
1562             if (isPreserveDataModel())
1563             {
1564                 _preservedDataModel = getSerializableDataModel();
1565                 values[1] = saveAttachedState(context, _preservedDataModel);
1566             }
1567             else
1568             {
1569                 values[1] = null;
1570             }
1571             
1572             values[2] = preserveSort ? getSortColumn() : null;
1573             values[3] = preserveSort ? Boolean.valueOf(isSortAscending()) : null;
1574             values[4] = _expandedNodes;
1575 
1576             return values;
1577         }
1578     }
1579 
1580     /**
1581      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#getDataModel()
1582      */
1583     protected DataModel getDataModel()
1584     {
1585         if (_preservedDataModel != null)
1586         {
1587             setDataModel(_preservedDataModel);
1588             _preservedDataModel = null;
1589         }
1590 
1591         return super.getDataModel();
1592     }
1593 
1594     /**
1595      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#createDataModel()
1596      */
1597     protected DataModel createDataModel()
1598     {
1599         DataModel dataModel = super.createDataModel();
1600 
1601         boolean isSortable = isSortable();
1602 
1603         if (!(dataModel instanceof SortableModel))
1604         {
1605             //if sortable=true make each column sortable
1606             //if sortable=false, check to see if at least one column sortable case in which
1607             //the current model needs to be wrapped by a sortable one.
1608             for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1609             {
1610                 UIComponent component = (UIComponent) iter.next();
1611                 if (component instanceof HtmlSimpleColumn)
1612                 {
1613                     HtmlSimpleColumn aColumn = (HtmlSimpleColumn) component;
1614                     if (isSortable())
1615                         aColumn.setSortable(true);
1616 
1617                     if (aColumn.isSortable())
1618                         isSortable = true;
1619 
1620                     String sortColumn = getSortColumn();
1621                     
1622                     if (aColumn.isDefaultSorted() && (sortColumn == null ? true : sortColumn.length() == 0))
1623                         setSortProperty(aColumn.getSortPropertyName());
1624                 }
1625             }
1626 
1627             if (isSortable)
1628                 dataModel = new SortableModel(dataModel);
1629         }
1630 
1631         if (isSortable && getSortProperty() != null)
1632         {
1633             SortCriterion criterion = new SortCriterion(getSortProperty(), isSortAscending());
1634             List criteria = new ArrayList();
1635             criteria.add(criterion);
1636 
1637             ((SortableModel) dataModel).setSortCriteria(criteria);
1638         }
1639 
1640         return dataModel;
1641     }
1642 
1643     public void restoreState(FacesContext context, Object state)
1644     {
1645         if (state == null)
1646         {
1647             return;
1648         }
1649         
1650         Object[] values = (Object[]) state;
1651         super.restoreState(context, values[0]);
1652         if (isPreserveDataModel())
1653         {
1654             _preservedDataModel = (_SerializableDataModel) restoreAttachedState(context, values[1]);
1655         }
1656         else
1657         {
1658             _preservedDataModel = null;
1659         }
1660 
1661         if (isPreserveSort())
1662         {
1663             String sortColumn = (String) values[2];
1664             Boolean sortAscending = (Boolean) values[3];
1665             if (sortColumn != null && sortAscending != null)
1666             {
1667                 ValueExpression vb = getValueExpression("sortColumn");
1668                 if (vb != null && !vb.isReadOnly(context.getELContext()))
1669                 {
1670                     vb.setValue(context.getELContext(), sortColumn);
1671                 }
1672 
1673                 vb = getValueExpression("sortAscending");
1674                 if (vb != null && !vb.isReadOnly(context.getELContext()))
1675                 {
1676                     vb.setValue(context.getELContext(), sortAscending);
1677                 }
1678             }
1679         }
1680 
1681         _expandedNodes = (Map) values[4];
1682     }
1683 
1684     public _SerializableDataModel getSerializableDataModel()
1685     {
1686         DataModel dm = getDataModel();
1687         if (dm instanceof _SerializableDataModel)
1688         {
1689             return (_SerializableDataModel) dm;
1690         }
1691         return createSerializableDataModel();
1692     }
1693 
1694     /**
1695      * @return _SerializableDataModel
1696      */
1697     private _SerializableDataModel createSerializableDataModel()
1698     {
1699         Object value = getValue();
1700         if (value == null)
1701         {
1702             return null;
1703         }
1704         else if (value instanceof DataModel)
1705         {
1706             return new _SerializableDataModel(getFirst(), getRows(), (DataModel) value);
1707         }
1708         else if (value instanceof List)
1709         {
1710             return new _SerializableListDataModel(getFirst(), getRows(), (List) value);
1711         }
1712         // accept a Collection is not supported in the Spec
1713         else if (value instanceof Collection)
1714         {
1715             return new _SerializableListDataModel(getFirst(), getRows(), new ArrayList((Collection) value));
1716         }
1717         else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
1718         {
1719             return new _SerializableArrayDataModel(getFirst(), getRows(), (Object[]) value);
1720         }
1721         else if (value instanceof ResultSet)
1722         {
1723             return new _SerializableResultSetDataModel(getFirst(), getRows(), (ResultSet) value);
1724         }
1725         else if (value instanceof javax.servlet.jsp.jstl.sql.Result)
1726         {
1727             return new _SerializableResultDataModel(getFirst(), getRows(),
1728                     (javax.servlet.jsp.jstl.sql.Result) value);
1729         }
1730         else
1731         {
1732             return new _SerializableScalarDataModel(getFirst(), getRows(), value);
1733         }
1734     }
1735 
1736     public boolean isRendered()
1737     {
1738         if (!UserRoleUtils.isVisibleOnUserRole(this))
1739             return false;
1740         return super.isRendered();
1741     }
1742 
1743     public void setForceIdIndexFormula(String forceIdIndexFormula)
1744     {
1745         getStateHelper().put(PropertyKeys.forceIdIndexFormula, forceIdIndexFormula);
1746         ValueExpression vb = getValueExpression("forceIdIndexFormula");
1747         if (vb != null)
1748         {
1749             vb.setValue(getFacesContext().getELContext(), forceIdIndexFormula);
1750             getStateHelper().put(PropertyKeys.forceIdIndexFormula, null);
1751         }
1752     }
1753 
1754     /**
1755      * A formula that overrides the default row index in the 
1756      * construction of table's body components. 
1757      * 
1758      * Example : #{myRowVar.key} Warning, the EL should 
1759      * evaluate to a unique value for each row !
1760      * 
1761      */
1762     @JSFProperty
1763     public String getForceIdIndexFormula()
1764     {
1765         return (String) getStateHelper().eval(PropertyKeys.forceIdIndexFormula);
1766     }
1767 
1768     /**
1769      * Specify what column the data should be sorted on.
1770      * <p/>
1771      * Note that calling this method <i>immediately</i> stores the value
1772      * via any value-binding with name "sortColumn". This is done because
1773      * this method is called by the HtmlCommandSortHeader component when
1774      * the user has clicked on a column's sort header. In this case, the
1775      * the model getter method mapped for name "value" needs to read this
1776      * value in able to return the data in the desired order - but the
1777      * HtmlCommandSortHeader component is usually "immediate" in order to
1778      * avoid validating the enclosing form. Yes, this is rather hacky -
1779      * but it works.
1780      */
1781     public void setSortColumn(String sortColumn)
1782     {
1783         getStateHelper().put(PropertyKeys.sortColumn, sortColumn);
1784         // update model is necessary here, because processUpdates is never called
1785         // reason: HtmlCommandSortHeader.isImmediate() == true
1786         ValueExpression vb = getValueExpression("sortColumn");
1787         if (vb != null)
1788         {
1789             vb.setValue(getFacesContext().getELContext(), sortColumn);
1790             getStateHelper().put(PropertyKeys.sortColumn, null);
1791         }
1792 
1793         setSortColumnIndex(columnNameToIndex(sortColumn));
1794     }
1795 
1796     /**
1797      * Value reference to a model property that gives the current 
1798      * sort column name. The target String property is set to 
1799      * the "columnName" of whichever column has been chosen 
1800      * to sort by, and the method which is bound to the "value" 
1801      * attribute of this table (ie which provides the DataModel used) 
1802      * is expected to use this property to determine how to sort 
1803      * the DataModel's contents.
1804      * 
1805      */
1806     @JSFProperty
1807     public String getSortColumn()
1808     {
1809         return (String) getStateHelper().eval(PropertyKeys.sortColumn);
1810     }
1811 
1812     public void setSortAscending(boolean sortAscending)
1813     {
1814         getStateHelper().put(PropertyKeys.sortAscending, sortAscending);
1815         
1816         // update model is necessary here, because processUpdates is never called
1817         // reason: HtmlCommandSortHeader.isImmediate() == true
1818         ValueExpression vb = getValueExpression("sortAscending");
1819         if (vb != null)
1820         {
1821             vb.setValue(getFacesContext().getELContext(), sortAscending);
1822             getStateHelper().put(PropertyKeys.sortAscending, null);
1823         }
1824     }
1825 
1826     /**
1827      * Value reference to a model property that gives the current 
1828      * sort direction. The target Boolean property is set to true 
1829      * when the selected sortColumn should be sorted in ascending 
1830      * order, and false otherwise. The method which is bound to 
1831      * the "value" attribute of this table (ie which provides the 
1832      * DataModel used) is expected to use this property to 
1833      * determine how to sort the DataModel's contents.
1834      * 
1835      */
1836     @JSFProperty(defaultValue="true")
1837     public boolean isSortAscending()
1838     {
1839         return (Boolean) getStateHelper().eval(PropertyKeys.sortAscending, DEFAULT_SORTASCENDING);
1840     }
1841 
1842     public abstract void setSortProperty(String sortProperty);
1843     
1844     /**
1845      * The name of a javabean property on which the table is sorted.
1846      * <p>
1847      * The datamodel should contain objects that have this property;
1848      * reflection will be used to sort the datamodel on that property
1849      * using the default comparator for that type. 
1850      * <p>
1851      * This value is part of the component state. However it is not
1852      * directly settable by users; instead it is set by other components
1853      * such as a CommandSortHeader.
1854      */
1855     @JSFProperty(literalOnly=true,tagExcluded=true)
1856     public abstract String getSortProperty();
1857 
1858     /**
1859      * Define if the table is sortable or not
1860      * 
1861      */
1862     @JSFProperty(defaultValue="false")
1863     public abstract boolean isSortable();
1864 
1865     /**
1866      * Avoids rendering the html table tags, thus, giving you a 
1867      * table rendering just rows. You can use this together 
1868      * with the detailStamp faces of the parent datatable 
1869      * to render child-tables using the same layout as the parent. 
1870      * 
1871      * Notice: You have to ensure both tables do have the same 
1872      * number of columns. Using the colspan attribute of the 
1873      * column tag might help alot.
1874      * 
1875      */
1876     @JSFProperty(defaultValue="false")
1877     public abstract boolean isEmbedded();
1878 
1879     /**
1880      * true|false - true if the detailStamp should be expanded by default. default: false
1881      * 
1882      */
1883     @JSFProperty(defaultValue="false")
1884     public abstract boolean isDetailStampExpandedDefault();
1885 
1886     /**
1887      * before|after - where to render the detailStamp, before the 
1888      * actual row or after it. default: after
1889      * 
1890      */
1891     @JSFProperty(defaultValue="after")
1892     public abstract String getDetailStampLocation();
1893 
1894     /**
1895      * Defines a JavaScript onmouseover event handler for each table row
1896      * 
1897      */
1898     @JSFProperty(clientEvent="rowMouseOver")
1899     public abstract String getRowOnMouseOver();
1900 
1901     /**
1902      * Defines a JavaScript onmouseout event handler for each table row
1903      * 
1904      */
1905     @JSFProperty(clientEvent="rowMouseOut")
1906     public abstract String getRowOnMouseOut();
1907 
1908     /**
1909      * Defines a JavaScript onclick event handler for each table row
1910      * 
1911      */
1912     @JSFProperty(clientEvent="rowClick")
1913     public abstract String getRowOnClick();
1914 
1915     /**
1916      * Defines a JavaScript ondblclick event handler for each table row
1917      * 
1918      */
1919     @JSFProperty(clientEvent="rowDblClick")
1920     public abstract String getRowOnDblClick();
1921 
1922     /**
1923      * Defines a JavaScript onkeydown event handler for each table row
1924      * 
1925      */
1926     @JSFProperty(clientEvent="rowKeyDown")
1927     public abstract String getRowOnKeyDown();
1928 
1929     /**
1930      * Defines a JavaScript onkeypress event handler for each table row
1931      * 
1932      */
1933     @JSFProperty(clientEvent="rowKeyPress")
1934     public abstract String getRowOnKeyPress();
1935 
1936     /**
1937      * Defines a JavaScript onkeyup event handler for each table row
1938      * 
1939      */
1940     @JSFProperty(clientEvent="rowKeyUp")
1941     public abstract String getRowOnKeyUp();
1942 
1943     /**
1944      * Corresponds to the HTML class attribute for the row tr tag.
1945      * 
1946      */
1947     @JSFProperty
1948     public abstract String getRowStyleClass();
1949 
1950     /**
1951      * Corresponds to the HTML style attribute for the row tr tag.
1952      * 
1953      */
1954     @JSFProperty
1955     public abstract String getRowStyle();
1956 
1957     /**
1958      * Defines a JavaScript onmpusedown event handler for each table row
1959      * 
1960      */
1961     @JSFProperty(clientEvent="rowMouseDown")
1962     public abstract String getRowOnMouseDown();
1963 
1964     /**
1965      * Defines a JavaScript onmousemove event handler for each table row
1966      * 
1967      */
1968     @JSFProperty(clientEvent="rowMouseMove")
1969     public abstract String getRowOnMouseMove();
1970 
1971     /**
1972      * Defines a JavaScript onmouseup event handler for each table row
1973      * 
1974      */
1975     @JSFProperty(clientEvent="rowMouseUp")
1976     public abstract String getRowOnMouseUp();
1977 
1978     /**
1979      */
1980     @JSFProperty(tagExcluded=true)
1981     protected boolean isValidChildren()
1982     {
1983         return _isValidChildren;
1984     }
1985 
1986     protected void setIsValidChildren(boolean isValidChildren)
1987     {
1988         _isValidChildren = isValidChildren;
1989     }
1990 
1991     protected _SerializableDataModel getPreservedDataModel()
1992     {
1993         return _preservedDataModel;
1994     }
1995 
1996     protected void setPreservedDataModel(_SerializableDataModel preservedDataModel)
1997     {
1998         _preservedDataModel = preservedDataModel;
1999     }
2000 
2001 
2002     public boolean isCurrentDetailExpanded()
2003     {
2004         Boolean expanded = (Boolean) _expandedNodes.get(getContainerClientId(getFacesContext()));
2005         if (expanded != null)
2006         {
2007             return expanded.booleanValue();
2008         }
2009 
2010         return isDetailStampExpandedDefault();
2011     }
2012 
2013     public void setVarDetailToggler(String varDetailToggler)
2014     {
2015         getStateHelper().put(PropertyKeys.varDetailToggler, varDetailToggler ); 
2016     }
2017 
2018     /**
2019      *  This variable has the boolean property "currentdetailExpanded" 
2020      *  which is true if the current detail row is expanded and the 
2021      *  action method "toggleDetail" which expand/collapse the current 
2022      *  detail row.
2023      * 
2024      */
2025     @JSFProperty
2026     public String getVarDetailToggler()
2027     {
2028         return (String) getStateHelper().eval(PropertyKeys.varDetailToggler);
2029     }
2030 
2031     /**
2032      * Corresponds to the HTML style attribute for grouped rows.
2033      *  
2034      */
2035     @JSFProperty
2036     public abstract String getRowGroupStyle();
2037 
2038     /**
2039      * StyleClass for grouped rows.
2040      * 
2041      */
2042     @JSFProperty
2043     public abstract String getRowGroupStyleClass();
2044     
2045     /**
2046      * Corresponds to the HTML style attribute for the table body tag
2047      * 
2048      */
2049     @JSFProperty
2050     public abstract String getBodyStyle();
2051 
2052     /**
2053      * Corresponds to the HTML class attribute for the table body tag.
2054      * 
2055      */
2056     @JSFProperty
2057     public abstract String getBodyStyleClass();
2058 
2059     public AbstractHtmlDataTable()
2060     {
2061         setRendererType(DEFAULT_RENDERER_TYPE);
2062     }
2063 
2064     /**
2065      * Change the status of the current detail row from collapsed to expanded or
2066      * viceversa.
2067      */
2068     public void toggleDetail()
2069     {
2070         String derivedRowKey = getContainerClientId(getFacesContext());
2071 
2072         // get the current expanded state of the row
2073         boolean expanded = isDetailExpanded();
2074         if (expanded)
2075         {
2076             // toggle to "collapsed"
2077 
2078             if (isDetailStampExpandedDefault())
2079             {
2080                 // if default is expanded we have to override with FALSE here
2081                 _expandedNodes.put(derivedRowKey, Boolean.FALSE);
2082             }
2083             else
2084             {
2085                 // if default is collapsed we can fallback to this default
2086                 _expandedNodes.remove(derivedRowKey);
2087             }
2088         }
2089         else
2090         {
2091             // toggle to "expanded"
2092 
2093             if (isDetailStampExpandedDefault())
2094             {
2095                 // if default is expanded we can fallback to this default
2096                 _expandedNodes.remove(derivedRowKey);
2097             }
2098             else
2099             {
2100                 // if default is collapsed we have to override with TRUE
2101                 _expandedNodes.put(derivedRowKey, Boolean.TRUE);
2102             }
2103         }
2104     }
2105 
2106     /**
2107      * Return true if the current detail row is expanded.
2108      *
2109      * @return true if the current detail row is expanded.
2110      */
2111     public boolean isDetailExpanded()
2112     {
2113         Boolean expanded = (Boolean) _expandedNodes.get(getContainerClientId(getFacesContext()));
2114         if (expanded == null)
2115         {
2116             return isDetailStampExpandedDefault();
2117         }
2118 
2119         return expanded.booleanValue();
2120     }
2121 
2122     public int getSortColumnIndex()
2123     {
2124         Integer sortColumnIndex = (Integer) getStateHelper().get(PropertyKeys.sortColumnIndex);
2125         if (sortColumnIndex == null)
2126         {
2127             //By default is -1 
2128             sortColumnIndex = -1;
2129         }
2130         if (sortColumnIndex == -1)
2131             sortColumnIndex = columnNameToIndex(getSortColumn());
2132 
2133         return sortColumnIndex;
2134     }
2135 
2136     public void setSortColumnIndex(int sortColumnIndex)
2137     {
2138         getStateHelper().put(PropertyKeys.sortColumnIndex, sortColumnIndex ); 
2139     }
2140 
2141     /**
2142      * The number of columns to wrap the table over. Default: 1
2143      * 
2144      * Set the number of columns the table will be divided over.
2145      * 
2146      */
2147     @JSFProperty(defaultValue="1")
2148     public abstract int getNewspaperColumns();
2149 
2150     /**
2151      * The orientation of the newspaper columns in the newspaper 
2152      * table - "horizontal" or "vertical". Default: vertical
2153      * 
2154      */
2155     @JSFProperty(defaultValue = "vertical")
2156     public abstract String getNewspaperOrientation();
2157 
2158     /**
2159      * Gets the spacer facet, between each pair of newspaper columns.
2160      * 
2161      */
2162     @JSFFacet
2163     public UIComponent getSpacer()
2164     {
2165         return (UIComponent) getFacets().get(SPACER_FACET_NAME);
2166     }
2167 
2168     public void setSpacer(UIComponent spacer)
2169     {
2170         getFacets().put(SPACER_FACET_NAME, spacer);
2171     }
2172 
2173     /**
2174      * Expand all details
2175      */
2176     public void expandAllDetails()
2177     {
2178         int rowCount = getRowCount();
2179 
2180         _expandedNodes.clear();
2181         
2182         if (getRowKey() != null)
2183         {
2184             int oldRow = getRowIndex();
2185             try
2186             {
2187                 for (int row = 0; row < rowCount; row++)
2188                 {
2189                     setRowIndex(row);
2190                     _expandedNodes.put(getContainerClientId(getFacesContext()), Boolean.TRUE);
2191                 }
2192             }
2193             finally
2194             {
2195                 setRowIndex(oldRow);
2196             }
2197         }
2198         else
2199         {
2200             for (int row = 0; row < rowCount; row++)
2201             {
2202                 _expandedNodes.put(new Integer(row).toString(), Boolean.TRUE);
2203             }
2204         }
2205     }
2206 
2207     /**
2208      * Collapse all details
2209      */
2210     public void collapseAllDetails()
2211     {
2212         _expandedNodes.clear();
2213     }
2214 
2215     /**
2216      * @return true is any of the details is expanded
2217      */
2218     public boolean isExpandedEmpty()
2219     {
2220         boolean expandedEmpty = true;
2221         if (_expandedNodes != null)
2222         {
2223             expandedEmpty = _expandedNodes.isEmpty();
2224         }
2225         return expandedEmpty;
2226     }
2227 
2228     /**
2229      * Clears expanded nodes set if expandedEmpty is true
2230      *
2231      * @param expandedEmpty
2232      */
2233     public void setExpandedEmpty(boolean expandedEmpty)
2234     {
2235         if (expandedEmpty)
2236         {
2237             if (_expandedNodes != null)
2238             {
2239                 _expandedNodes.clear();
2240             }
2241         }
2242     }
2243 
2244     //------------------ GENERATED CODE BEGIN (do not modify!) --------------------
2245 
2246     public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataTable";
2247     public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Table";
2248 
2249     private static final boolean DEFAULT_PRESERVEDATAMODEL = false;
2250     private static final boolean DEFAULT_PRESERVESORT = true;
2251     private static final boolean DEFAULT_RENDEREDIFEMPTY = true;
2252 
2253 
2254     /**
2255      * Indicates whether the state of the whole DataModel should 
2256      * be saved and restored. When set to false, the value-binding 
2257      * for the "value" attribute of this table is executed each 
2258      * time the page is rendered. When set to true, that 
2259      * value-binding is only executed when the component is first 
2260      * created, and the DataModel state is thereafter saved/restored 
2261      * automatically by the component. When column sorting is 
2262      * used for a table this property needs to be false so that 
2263      * the DataModel can be updated to reflect any changes in the 
2264      * sort criteria. Default: false
2265      * 
2266      */
2267     @JSFProperty
2268     public abstract boolean isPreserveDataModel();
2269 
2270     /**
2271      * Indicates whether the state of the sortColumn and sortAscending 
2272      * attribute should be saved and restored and written back to the 
2273      * model during the update model phase. Default: true
2274      * 
2275      */
2276     @JSFProperty(defaultValue = "true")
2277     public abstract boolean isPreserveSort();
2278 
2279     /**
2280      * Indicates whether this table should be rendered if the 
2281      * underlying DataModel is empty. You could as well use 
2282      * rendered="#{not empty bean.list}", but this one causes 
2283      * the getList method of your model bean beeing called up 
2284      * to five times per request, which is not optimal when 
2285      * the list is backed by a DB table. Using 
2286      * renderedIfEmpty="false" solves this problem, because 
2287      * the MyFaces extended HtmlDataTable automatically caches 
2288      * the DataModel and calles the model getter only once 
2289      * per request. Default: true
2290      * 
2291      */
2292     @JSFProperty(defaultValue = "true")
2293     public abstract boolean isRenderedIfEmpty();
2294 
2295     /**
2296      * A parameter name, under which the current rowIndex is set 
2297      * in request scope similar to the var parameter.
2298      * 
2299      */
2300     @JSFProperty
2301     public abstract String getRowIndexVar();
2302     
2303     /**
2304      * A parameter name, under which the rowCount is set in 
2305      * request scope similar to the var parameter.
2306      * 
2307      */
2308     @JSFProperty
2309     public abstract String getRowCountVar();
2310 
2311     /**
2312      * A parameter name, under which the previous RowData Object 
2313      * is set in request scope similar to the rowIndexVar and 
2314      * rowCountVar parameters. Mind that the value of this 
2315      * request scope attribute is null in the first row or 
2316      * when isRowAvailable returns false for the previous row.
2317      * 
2318      */
2319     @JSFProperty
2320     public abstract String getPreviousRowDataVar();
2321 
2322     /**
2323      * A parameter name, under which the a boolean is set in request 
2324      * scope similar to the var parameter. TRUE for the column that 
2325      * is currently sorted, FALSE otherwise.
2326      * 
2327      */
2328     @JSFProperty
2329     public abstract String getSortedColumnVar();
2330     
2331     /**
2332      * HTML: Specifies the horizontal alignment of this element. 
2333      * Deprecated in HTML 4.01.
2334      * 
2335      */
2336     @JSFProperty
2337     public abstract String getAlign();
2338 
2339     /**
2340      * The id to use for
2341      * 
2342      */
2343     @JSFProperty
2344     public abstract String getRowId();
2345         
2346     /**
2347      * Reserved for future use.
2348      * 
2349      */
2350     @JSFProperty
2351     public abstract String getDatafld();
2352     
2353     /**
2354      * Reserved for future use.
2355      * 
2356      */
2357     @JSFProperty
2358     public abstract String getDatasrc();
2359     
2360     /**
2361      * Reserved for future use.
2362      * 
2363      */
2364     @JSFProperty
2365     public abstract String getDataformatas();
2366 
2367     /**
2368      * Indicate the expected type of the EL expression pointed
2369      * by value property. It is useful when vb.getType() cannot
2370      * found the type, like when a map value is resolved on 
2371      * the expression.
2372      * 
2373      */
2374     @JSFProperty
2375     public abstract String getValueType(); 
2376     
2377     /**
2378      * Indicate if "row" can be a target for an ajax render
2379      * operation. In other words, if it is set to true,
2380      * a special component is added on a facet with name "row"
2381      * and with id="row" that can be used to indicate it is 
2382      * necessary to render the row.
2383      * By default is set to false.
2384      * 
2385      * @return
2386      */
2387     @JSFProperty(defaultValue="false")
2388     public abstract boolean isAjaxRowRender();
2389 
2390     protected enum PropertyKeys
2391     {
2392         preservedDataModel
2393         , forceIdIndexFormula
2394         , sortColumn
2395         , sortAscending
2396         , varDetailToggler
2397         , expandedNodes
2398         , sortColumnIndex
2399     }
2400 }