View Javadoc

1   // WARNING: This file was automatically generated. Do not edit it directly,
2   //          or you will lose your changes.
3   
4   /*
5    * Licensed to the Apache Software Foundation (ASF) under one
6    * or more contributor license agreements.  See the NOTICE file
7    * distributed with this work for additional information
8    * regarding copyright ownership.  The ASF licenses this file
9    * to you under the Apache License, Version 2.0 (the
10   * "License"); you may not use this file except in compliance
11   * with the License.  You may obtain a copy of the License at
12   *
13   *   http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing,
16   * software distributed under the License is distributed on an
17   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   * KIND, either express or implied.  See the License for the
19   * specific language governing permissions and limitations
20   * under the License.
21  */
22  package org.apache.myfaces.trinidad.component;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.IdentityHashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import javax.el.MethodExpression;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  import javax.faces.el.MethodBinding;
35  import javax.faces.event.AbortProcessingException;
36  import javax.faces.event.FacesEvent;
37  import javax.faces.event.PhaseId;
38  import org.apache.myfaces.trinidad.bean.FacesBean;
39  import org.apache.myfaces.trinidad.bean.PropertyKey;
40  import org.apache.myfaces.trinidad.event.RangeChangeEvent;
41  import org.apache.myfaces.trinidad.event.RangeChangeListener;
42  import org.apache.myfaces.trinidad.event.RowDisclosureEvent;
43  import org.apache.myfaces.trinidad.event.RowDisclosureListener;
44  import org.apache.myfaces.trinidad.event.SelectionEvent;
45  import org.apache.myfaces.trinidad.event.SelectionListener;
46  import org.apache.myfaces.trinidad.event.SortEvent;
47  import org.apache.myfaces.trinidad.event.SortListener;
48  import org.apache.myfaces.trinidad.model.CollectionModel;
49  import org.apache.myfaces.trinidad.model.RowKeySet;
50  import org.apache.myfaces.trinidad.model.RowKeySetImpl;
51  import org.apache.myfaces.trinidad.model.SortCriterion;
52  import org.apache.myfaces.trinidad.util.ComponentUtils;
53  
54  /**
55   *
56   * <h4>Events:</h4>
57   * <table border="1" width="100%" cellpadding="3" summary="">
58   * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
59   * <th align="left">Type</th>
60   * <th align="left">Phases</th>
61   * <th align="left">Description</th>
62   * </tr>
63   * <tr class="TableRowColor">
64   * <td valign="top"><code>org.apache.myfaces.trinidad.event.RowDisclosureEvent</code></td>
65   * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
66   * <td valign="top">The expansion event is generated for a table when the detail facet of a row is expanded or collapsed. For tree or a treeTable, the expansion
67                         event is generated when tree nodes are expanded or collapsed.</td>
68   * </tr>
69   * <tr class="TableRowColor">
70   * <td valign="top"><code>org.apache.myfaces.trinidad.event.SelectionEvent</code></td>
71   * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
72   * <td valign="top">The selection event is delivered when the table selection
73                         changes.</td>
74   * </tr>
75   * <tr class="TableRowColor">
76   * <td valign="top"><code>org.apache.myfaces.trinidad.event.RangeChangeEvent</code></td>
77   * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
78   * <td valign="top">The range change event is delivered when the user
79                         navigates.</td>
80   * </tr>
81   * <tr class="TableRowColor">
82   * <td valign="top"><code>org.apache.myfaces.trinidad.event.SortEvent</code></td>
83   * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
84   * <td valign="top">The sort event is delivered when the table column sort
85                         criteria is changed.</td>
86   * </tr>
87   * <tr class="TableRowColor">
88   * <td valign="top"><code>org.apache.myfaces.trinidad.event.AttributeChangeEvent</code></td>
89   * <td valign="top" nowrap>Invoke<br>Application<br>Apply<br>Request<br>Values</td>
90   * <td valign="top">Event delivered to describe an attribute change.  Attribute change events are not delivered for any programmatic change to a property.  They are only delivered when a renderer changes a property without the application's specific request.  An example of an attribute change event might include the width of a column that supported client-side resizing.</td>
91   * </tr>
92   * </table>
93   */
94  public class UIXTable extends UIXIterator
95                        implements CollectionComponent
96  {
97    static public final FacesBean.Type TYPE = new FacesBean.Type(
98      UIXIterator.TYPE);
99    static public final PropertyKey ROW_DISCLOSURE_LISTENER_KEY =
100     TYPE.registerKey("rowDisclosureListener", MethodExpression.class);
101   static public final PropertyKey DISCLOSED_ROW_KEYS_KEY =
102     TYPE.registerKey("disclosedRowKeys", RowKeySet.class, null, 0, PropertyKey.Mutable.OFTEN);
103   static public final PropertyKey SELECTION_LISTENER_KEY =
104     TYPE.registerKey("selectionListener", MethodExpression.class);
105   static public final PropertyKey SELECTED_ROW_KEYS_KEY =
106     TYPE.registerKey("selectedRowKeys", RowKeySet.class, null, 0, PropertyKey.Mutable.OFTEN);
107   static public final PropertyKey IMMEDIATE_KEY =
108     TYPE.registerKey("immediate", Boolean.class, Boolean.FALSE);
109   static public final PropertyKey SORT_LISTENER_KEY =
110     TYPE.registerKey("sortListener", MethodExpression.class);
111   static public final PropertyKey RANGE_CHANGE_LISTENER_KEY =
112     TYPE.registerKey("rangeChangeListener", MethodExpression.class);
113   static public final PropertyKey SHOW_ALL_KEY =
114     TYPE.registerKey("showAll", Boolean.class, Boolean.FALSE);
115   static public final String DETAIL_STAMP_FACET = "detailStamp";
116 
117   static public final String COMPONENT_FAMILY =
118     "org.apache.myfaces.trinidad.Table";
119   static public final String COMPONENT_TYPE =
120     "org.apache.myfaces.trinidad.Table";
121 
122   /**
123    * Construct an instance of the UIXTable.
124    */
125   public UIXTable()
126   {
127     super("org.apache.myfaces.trinidad.Table");
128   }
129   
130   // These are "fake" properties that allow the table to get the disclosed row keys and the
131   // selected row key without triggering the call to getCollectionModel from the
132   // RowKeyFacesBeanWrapper class. See the stamp state saving code for its usage.
133   static private final PropertyKey _DISCLOSED_ROW_KEYS_WITHOUT_MODEL_KEY =
134     TYPE.registerKey("disclosedRowKeysWithoutModel", RowKeySet.class);
135   static private final PropertyKey _SELECTED_ROW_KEYS_WITHOUT_MODEL_KEY =
136     TYPE.registerKey("selectedRowKeysWithoutModel", RowKeySet.class);
137 
138 
139   /**
140    * Override to update the container client id cache before decode
141    */
142   @Override
143   public void decode(FacesContext context)
144   {
145     _resetContainerClientIdCache();
146     super.decode(context);
147   }
148 
149   /**
150    * Override to update the container client id cache before validations
151    */
152   @Override
153   public void processValidators(FacesContext context)
154   {
155     _resetContainerClientIdCache();
156     super.processValidators(context);
157   }
158 
159 
160   /**
161    * Override to update the container client id cache before updates
162    */
163   @Override
164   public void processUpdates(FacesContext context)
165   {
166     _resetContainerClientIdCache();
167     super.processUpdates(context);
168   }
169 
170   /**
171    * Override to update the container client id cache before encode
172    */
173   @Override
174   void __encodeBegin(FacesContext context) throws IOException
175   {
176     _resetContainerClientIdCache();
177     super.__encodeBegin(context);
178   }
179 
180   /**
181    * Override to return clientd ids with no currency for items in header/footer facets
182    */
183   @Override
184   public String getContainerClientId(FacesContext context, UIComponent child)
185   {
186     String id;
187     if (_containerClientIdCache == null || _isStampedChild(child))
188     {
189       // call the UIXCollection getContainerClientId, which attaches currency string to the client id
190       id = getContainerClientId(context);
191     }
192     else
193     {
194       // The target is not a stamped child, so return a client id with no currency string
195       id = getClientId(context);
196     }
197 
198     return id;
199   }
200 
201   @Override
202   public void setSortCriteria(List<SortCriterion> criteria)
203   {
204     _sortCriteria = criteria;
205     super.setSortCriteria(criteria);
206   }
207 
208   /**
209    * Sets the phaseID of UI events depending on the "immediate" property.
210    */
211   @Override
212   public void queueEvent(FacesEvent event)
213   {
214     TableUtils.__handleQueueEvent(this, event);
215     super.queueEvent(event);
216   }
217 
218   /**
219    * Delivers an event to the appropriate listeners.
220    * @param event
221    * @throws javax.faces.event.AbortProcessingException
222    */
223   @Override
224   public void broadcast(FacesEvent event)
225     throws AbortProcessingException
226   {
227     // the order of processing is
228     // 1. do any default action handling
229     // 2. invoke any actionListener method binding
230     // 3. call all the registered ActionListener instances.
231 
232     // Deliver to the default RangeChangeListener
233     if (event instanceof RangeChangeEvent)
234     {
235       RangeChangeEvent rEvent = (RangeChangeEvent) event;
236       int first = rEvent.getNewStart();
237       setFirst(first);
238       //pu: Implicitly record a Change for 'first' attribute
239       addAttributeChange("first", Integer.valueOf(first));
240 
241       if ((first == 0) && (rEvent.getNewEnd() == getRowCount()))
242       {
243         setShowAll(true);
244         //pu: Implicitly record a Change for 'showAll' attribute
245         addAttributeChange("showAll", Boolean.TRUE);
246       }
247       else if (isShowAll())
248       {
249         setShowAll(false);
250         //pu: Implicitly record a Change for 'showAll' attribute
251         addAttributeChange("showAll", Boolean.FALSE);
252       }
253       // since the range is now different we can clear the currency cache:
254       clearCurrencyStringCache();
255 
256       broadcastToMethodExpression(event, getRangeChangeListener());
257     }
258     else if (event instanceof RowDisclosureEvent)
259     {
260       RowDisclosureEvent eEvent = (RowDisclosureEvent) event;
261       RowKeySet set = getDisclosedRowKeys();
262       set.addAll(eEvent.getAddedSet());
263       set.removeAll(eEvent.getRemovedSet());
264       addAttributeChange("disclosedRowKeys", set);
265       broadcastToMethodExpression(event, getRowDisclosureListener());
266     }
267     else if (event instanceof SortEvent)
268     {
269       SortEvent sEvent = (SortEvent) event;
270       setSortCriteria(sEvent.getSortCriteria());
271       broadcastToMethodExpression(event, getSortListener());
272     }
273     else if (event instanceof SelectionEvent)
274     {
275       //pu: Implicitly record a Change for 'selectionState' attribute
276       addAttributeChange("selectedRowKeys",
277                          getSelectedRowKeys());
278       broadcastToMethodExpression(event, getSelectionListener());
279     }
280 
281     super.broadcast(event);
282   }
283 
284 
285 
286   @Deprecated
287   public void setRangeChangeListener(MethodBinding binding)
288   {
289     setRangeChangeListener(adaptMethodBinding(binding));
290   }
291 
292   @Deprecated
293   public void setSortListener(MethodBinding binding)
294   {
295     setSortListener(adaptMethodBinding(binding));
296   }
297 
298   @Deprecated
299   public void setRowDisclosureListener(MethodBinding binding)
300   {
301     setRowDisclosureListener(adaptMethodBinding(binding));
302   }
303 
304   @Deprecated
305   public void setSelectionListener(MethodBinding binding)
306   {
307     setSelectionListener(adaptMethodBinding(binding));
308   }
309 
310   @Override
311   @SuppressWarnings("unchecked")
312   public Object saveState(FacesContext context)
313   {
314     Object o = super.saveState(context);
315     if ((o == null) &&
316         ((_sortCriteria == null) || _sortCriteria.isEmpty()))
317       return null;
318 
319     return new Object[]{o, _sortCriteria};
320   }
321 
322   @Override
323   @SuppressWarnings("unchecked")
324   public void restoreState(FacesContext context, Object state)
325   {
326     Object[] array = (Object[]) state;
327     super.restoreState(context, array[0]);
328 
329 
330     // Get the sort criteria - but *don't* call setSortCriteria()
331     // here;  doing so would require getting the collection model,
332     // and that may invoke client code that isn't quite in a state
333     // to be invoked, in part because component "binding"s have not been
334     // evaluated yet.
335     List<SortCriterion> criteria = (List<SortCriterion>) array[1];
336     _sortCriteria = criteria;
337   }
338 
339 
340   /**
341    * Gets the data for the first selected row.
342    * This is useful when using EL to get at column data for the selected
343    * row when using a table with single selection.
344    * @return null if there is nothing selected in the table.
345    */
346   public Object getSelectedRowData()
347   {
348     RowKeySet state = getSelectedRowKeys();
349     Iterator<Object> keys = state.iterator();
350     if (keys.hasNext())
351     {
352       Object key = keys.next();
353       CollectionModel model = getCollectionModel();
354       Object old = model.getRowKey();
355       try
356       {
357         model.setRowKey(key);
358         if (isRowAvailable())
359           return model.getRowData();
360       }
361       finally
362       {
363         model.setRowKey(old);
364       }
365     }
366     return null;
367   }
368 
369   @Override
370   protected void processFacetsAndChildren(
371     FacesContext context,
372     PhaseId phaseId)
373   {
374     // process all the facets of this table just once
375     // (except for the "detailStamp" facet which must be processed once
376     // per row):
377     TableUtils.processFacets(context, this, this, phaseId,
378       UIXTable.DETAIL_STAMP_FACET);
379 
380     // process all the facets of this table's column children:
381     TableUtils.processColumnFacets(context, this, this, phaseId);
382 
383     // process all the children and the detailStamp as many times as necessary
384     processStamps(context, phaseId);
385   }
386 
387   /**
388    * Gets the stamps. This returns the children of this component plus
389    * the detail stamp (if any).
390    */
391   // TODO cache the result
392   @Override
393   protected final List<UIComponent> getStamps()
394   {
395     List<UIComponent> children = super.getStamps();
396     UIComponent detail = getDetailStamp();
397     if (detail != null)
398     {
399       List<UIComponent> stamps = new ArrayList<UIComponent>(children.size() + 1);
400       stamps.addAll(children);
401       stamps.add(detail);
402       return stamps;
403     }
404     return children;
405   }
406 
407   /**
408    * Saves the state for the given stamp.
409    * This method avoids changing the state of facets on columns.
410    */
411   @Override
412   protected final Object saveStampState(FacesContext context, UIComponent stamp)
413   {
414     if (stamp instanceof UIXColumn)
415     {
416       // if it is a column, we don't want the facets processed.
417       // Only the children:
418       return StampState.saveChildStampState(context, stamp, this);
419     }
420     else
421     {
422       Object stampState = super.saveStampState(context, stamp);
423       
424       // Support for column stamping. Before this fix, nested UIXCollection can never be processed without setting 
425       // currency on the outer iterator. The inner collection's stamp state is only created if it is null. The inner 
426       // collection depends on the outer collection to save and restore the nested stamp state to null as it moves it 
427       // currency  during saveStampState and restoreStampState.
428       
429       // Now if the columns are stamped using an iterator, there could be state associated with the column headers, 
430       // footers etc and this is when the currency is null. So it is possible to process/iterate the af:iterator when 
431       // the currency on the outer table is null. This could be for layout of columns etc.
432       
433       // The following fix uses the internal _movingToNonNullCurrency variable to know that we are ready to start 
434       // iterating the stamps. If so, it saves off the state associated with  the null currency in a temporary map that 
435       // is restored when we are done processing our stamps (also see restoreStampState).
436       if(stamp instanceof UIXIterator && _movingToNonNullCurrency)
437       {
438         // JIT create our temporary map
439         if(_iteratorStampMap == null)
440         {
441           _iteratorStampMap = new HashMap<String, Object>();
442         }
443         
444         // save off the state associated with the null currency
445         _iteratorStampMap.put(stamp.getClientId(context), stampState);
446         
447         // reset the state of the iterator to the prestine state for null currency
448         ((UIXIterator)stamp).__resetMyStampState();
449         
450         // now save the pristine state to the null currency so that when fresh stamp states can be created if necessary
451         stampState = super.saveStampState(context, stamp);
452       }
453       
454       return stampState;
455     }
456   }
457 
458   /**
459    * Restores the state for the given stamp.
460    * This method avoids changing the state of facets on columns.
461    */
462   @Override
463   protected final void restoreStampState(FacesContext context, UIComponent stamp, Object stampState)
464   {
465     if (stamp instanceof UIXColumn)
466     {
467       // if it is a column, we don't want the facets processed.
468       // Only the children:
469       StampState.restoreChildStampState(context, stamp, this, stampState);
470     }
471     else
472     {
473       // If we are done processing our stamps and are moving back to null currency, restore the stamp state to the
474       // one saved off before processing our stamps
475       if(stamp instanceof UIXIterator && _movingBackToNullCurrency && _iteratorStampMap != null)
476       {
477         // clear the cached client id of the iterator so that we can get the one without currency
478         stamp.setId(stamp.getId());
479         stampState = _iteratorStampMap.get(stamp.getClientId(context));        
480       }
481       super.restoreStampState(context, stamp, stampState);
482     }
483   }
484 
485   @Override
486   protected final CollectionModel createCollectionModel(
487     CollectionModel current,
488     Object value)
489   {
490     return super.createCollectionModel(current, value);
491   }
492 
493   @Override
494   protected void postCreateCollectionModel(CollectionModel model)
495   {
496     RowKeySet selectedRowKeys = getSelectedRowKeys();
497 
498     if (selectedRowKeys == null)
499     {
500       selectedRowKeys = new RowKeySetImpl();
501       setSelectedRowKeys(selectedRowKeys);
502     }
503 
504     RowKeySet disclosedRowKeys = getDisclosedRowKeys();
505 
506     if (disclosedRowKeys == null)
507     {
508       disclosedRowKeys = new RowKeySetImpl();
509       setDisclosedRowKeys(disclosedRowKeys);
510     }
511 
512     selectedRowKeys.setCollectionModel(model);
513     disclosedRowKeys.setCollectionModel(model);
514 
515     // If we were perviously sorted, restore the sort order:
516     if (_sortCriteria != null)
517     {
518       model.setSortCriteria(_sortCriteria);
519     }
520   }
521 
522   /**
523    * Gets the internal state of this component.
524    */
525   @Override
526   Object __getMyStampState()
527   {
528     Object[] state = new Object[6];
529     state[0] = _sortCriteria;
530     state[1] = super.__getMyStampState();
531     state[2] = Integer.valueOf(getFirst());
532     state[3] = Boolean.valueOf(isShowAll());
533 
534     // Use "hidden" property keys to allow the row key sets to be retrieved without the
535     // RowKeyFacesBeanWrapper trying to resolve the collection model to be set into the row key
536     // set. This is needed to stop the unnecessary lookup of the collection model when it is not
537     // needed during stamp state saving of the table.
538     RowKeySet selectedRowKeys = (RowKeySet)getProperty(_SELECTED_ROW_KEYS_WITHOUT_MODEL_KEY);
539     RowKeySet disclosedRowKeys = (RowKeySet)getProperty(_DISCLOSED_ROW_KEYS_WITHOUT_MODEL_KEY);
540 
541     state[4] = selectedRowKeys;
542     state[5] = disclosedRowKeys;
543 
544     return state;
545   }
546 
547   /**
548    * Sets the internal state of this component.
549    * @param stampState the internal state is obtained from this object.
550    */
551   @Override
552   @SuppressWarnings("unchecked")
553   void __setMyStampState(Object stampState)
554   {
555     Object[] state = (Object[]) stampState;
556     _sortCriteria = (List<SortCriterion>) state[0];
557     super.__setMyStampState(state[1]);
558     setFirst(((Integer) state[2]).intValue());
559     setShowAll(Boolean.TRUE == state[3]);
560     setSelectedRowKeys((RowKeySet) state[4]);
561     setDisclosedRowKeys((RowKeySet) state[5]);
562   }
563 
564   @Override
565   void __resetMyStampState()
566   {
567     super.__resetMyStampState();
568     _sortCriteria = null;
569     setFirst((Integer)FIRST_KEY.getDefault());
570     setShowAll(Boolean.TRUE == SHOW_ALL_KEY.getDefault());
571     setSelectedRowKeys(null);
572     setDisclosedRowKeys(null);
573   }
574   
575   protected void processStamps(
576     FacesContext context,
577     PhaseId phaseId)
578   {
579     // Process all the children
580     CollectionModel tableData = getCollectionModel();
581     if (tableData.getRowCount() != 0)
582     {
583       int startIndex = getFirst();
584       int endIndex = isShowAll() ? getRowCount()-1 : TableUtils.getLast(this);
585 
586       UIComponent detail = getDetailStamp();
587       RowKeySet disclosureState =
588         (detail == null) ? null : getDisclosedRowKeys();
589 
590       for (int i = startIndex; i <= endIndex; i++)
591       {
592         setRowIndex(i);
593         if (isRowAvailable())
594         {
595           TableUtils.processStampedChildren(context, this, phaseId);
596 
597           if ((disclosureState != null) && disclosureState.isContained())
598           {
599             assert getRowIndex() == i;
600             processComponent(context, detail, phaseId);
601           }
602         }
603       }
604 
605       setRowIndex(-1);
606     }
607   }
608 
609   /**
610    * Is target a stamped child UIComponent in the table body
611    */
612   private boolean _isStampedChild(UIComponent target)
613   {
614     assert _containerClientIdCache != null;
615     return !_containerClientIdCache.containsKey(target);
616   }
617 
618   /**
619    * Reset the cache of child components used in getContainerClientId
620    */
621   private void _resetContainerClientIdCache()
622   {
623     if(_containerClientIdCache == null)
624       _containerClientIdCache = new IdentityHashMap<UIComponent, Boolean>();
625     else
626       _containerClientIdCache.clear();
627 
628     TableUtils.cacheHeaderFooterFacets(this, _containerClientIdCache);
629     TableUtils.cacheColumnHeaderFooterFacets(this, _containerClientIdCache);
630   }
631 
632 
633   @Override
634   void __init()
635   {
636     super.__init();
637     if (getSelectedRowKeys() == null)
638       setSelectedRowKeys(new RowKeySetImpl());
639     if (getDisclosedRowKeys() == null)
640       setDisclosedRowKeys(new RowKeySetImpl());
641     // if "first" is valueBound, we can't deal with it changing
642     // during the lifecycle. So stash it as a local value.
643     // see bug 4537121:
644     setFirst(getFirst());
645   }
646 
647   @Override
648   protected FacesBean createFacesBean(String rendererType)
649   {
650     return new RowKeyFacesBeanWrapper(super.createFacesBean(rendererType));
651   }
652 
653   private class RowKeyFacesBeanWrapper
654     extends FacesBeanWrapper
655   {
656     private boolean _retrievingDisclosedRows = false;
657     private boolean _retrievingSelectedRows = false;
658 
659     RowKeyFacesBeanWrapper(FacesBean bean)
660     {
661       super(bean);
662     }
663 
664     @Override
665     public Object getProperty(PropertyKey key)
666     {
667       if (key == _DISCLOSED_ROW_KEYS_WITHOUT_MODEL_KEY)
668       {
669         // This case is only true if the table is trying to serialize the disclosed row keys to
670         // the stamp state of a parent UIXCollection. This work-around prevents EL evaluation to
671         // get the collection model during stamp state saving. This should be permissible as the
672         // state saving code does not need the collection model to be set in the row key set in
673         // order to save its state.
674         return super.getProperty(DISCLOSED_ROW_KEYS_KEY);
675       }
676       else if (key == _SELECTED_ROW_KEYS_WITHOUT_MODEL_KEY)
677       {
678         // This case is only true if the table is trying to serialize the selected row keys to
679         // the stamp state of a parent UIXCollection. This work-around prevents EL evaluation to
680         // get the collection model during stamp state saving. This should be permissible as the
681         // state saving code does not need the collection model to be set in the row key set in
682         // order to save its state.
683         return super.getProperty(SELECTED_ROW_KEYS_KEY);
684       }
685 
686       Object value = super.getProperty(key);
687       if (key == DISCLOSED_ROW_KEYS_KEY)
688       {
689         if (!_retrievingDisclosedRows && value instanceof RowKeySet)
690         {
691           // Ensure that when we are retrieving and setting the collection model, this property
692           // is not asked for which would create an infinite loop
693           _retrievingDisclosedRows = true;
694 
695           try
696           {
697             RowKeySet rowKeys = (RowKeySet) value;
698             // row key sets need the most recent collection model, but there is no one common entry
699             // point to set this on the set besides when code asks for the value from the bean
700             __flushCachedModel();  //insist that we populate with the very lastest instance of the collection model
701             rowKeys.setCollectionModel(getCollectionModel());
702           }
703           finally
704           {
705             _retrievingDisclosedRows = false;
706           }
707         }
708       }
709       else if (key == SELECTED_ROW_KEYS_KEY)
710       {
711         if (!_retrievingSelectedRows && value instanceof RowKeySet)
712         {
713           // Ensure that when we are retrieving and setting the collection model, this property
714           // is not asked for which would create an infinite loop
715           _retrievingSelectedRows = true;
716 
717           try
718           {
719             RowKeySet rowKeys = (RowKeySet) value;
720             // row key sets need the most recent collection model, but there is no one common entry
721             // point to set this on the set besides when code asks for the value from the bean
722             __flushCachedModel();  //insist that we populate with the very lastest instance of the collection model
723             rowKeys.setCollectionModel(getCollectionModel());
724           }
725           finally
726           {
727             _retrievingSelectedRows = false;
728           }
729         }
730       }
731 
732       return value;
733     }
734 
735     @Override
736     public Object saveState(FacesContext context)
737     {
738       RowKeySet rowKeys = (RowKeySet)super.getProperty(DISCLOSED_ROW_KEYS_KEY);
739       if (rowKeys != null)
740       {
741         // make sure the set does not pin the model in memory
742         rowKeys.setCollectionModel(null);
743       }
744       rowKeys = (RowKeySet)super.getProperty(SELECTED_ROW_KEYS_KEY);
745       if (rowKeys != null)
746       {
747         // make sure the set does not pin the model in memory
748         rowKeys.setCollectionModel(null);
749       }
750       return super.saveState(context);
751     }
752   }
753 
754   @Override
755   public void setRowKey(Object rowKey)
756   {
757     _preCurrencyChange(rowKey == null);
758     try
759     {
760       super.setRowKey(rowKey);
761     }
762     finally
763     {
764       _postCurrencyChange();
765     }
766   }
767 
768   @Override
769   public void setRowIndex(int rowIndex)
770   {
771     _preCurrencyChange(rowIndex == -1);
772     try
773     {
774       super.setRowIndex(rowIndex);
775     }
776     finally
777     {
778       _postCurrencyChange();
779     }
780   }
781   
782   /**
783    * When the currency changes keep track of the fact that we are ready to start iterating the stamps vs setting
784    * currency to process headers etc. If the new curency is not null but the old one was, 
785    * we are ready to start processing the stamps.
786    * 
787    * Similary keep track of the fact that we are done processing the our stamps. In this case the currency moves back to
788    * null after being set previously
789    */
790   private void _preCurrencyChange(boolean isNewCurrencyNull)
791   {
792     Object currencyObj = getRowKey();    
793     if(currencyObj == null && !isNewCurrencyNull)
794     {
795       _movingToNonNullCurrency = true;
796     }
797     
798     if(currencyObj != null && isNewCurrencyNull)
799     {
800       _movingBackToNullCurrency = true;
801     }
802   }
803   
804   /**
805    * Clean up variables setup during _preCurrencyChange.
806    */
807   private void _postCurrencyChange()
808   {
809     _movingToNonNullCurrency = false;
810     
811     if(_movingBackToNullCurrency)
812     {
813       _iteratorStampMap = null;
814     }
815     _movingBackToNullCurrency = false;
816   }
817   
818   transient private List<SortCriterion> _sortCriteria = null;
819   // cache of child components inside this table header/footer facets and column header/footer
820   // facets
821   transient private IdentityHashMap<UIComponent, Boolean> _containerClientIdCache = null;
822   
823   // transient variables used to track when are going to start processing the stamps and when we are done.
824   transient private boolean _movingToNonNullCurrency = false;
825   transient private boolean _movingBackToNullCurrency = false;
826   
827   // map used to support iterator stamping of columns
828   transient private Map<String, Object> _iteratorStampMap = null;
829 
830   /**
831    * the component to stamp below every row which is disclosed. Adding a
832    * detail facet will automatically cause the detail column to be displayed.
833    */
834   final public UIComponent getDetailStamp()
835   {
836     return getFacet(DETAIL_STAMP_FACET);
837   }
838 
839   /**
840    * the component to stamp below every row which is disclosed. Adding a
841    * detail facet will automatically cause the detail column to be displayed.
842    */
843   @SuppressWarnings("unchecked")
844   final public void setDetailStamp(UIComponent detailStampFacet)
845   {
846     getFacets().put(DETAIL_STAMP_FACET, detailStampFacet);
847   }
848 
849   /**
850    * Gets a method reference to an ExpansionListener
851    *
852    * @return  the new rowDisclosureListener value
853    */
854   final public MethodExpression getRowDisclosureListener()
855   {
856     return (MethodExpression)getProperty(ROW_DISCLOSURE_LISTENER_KEY);
857   }
858 
859   /**
860    * Sets a method reference to an ExpansionListener
861    * 
862    * @param rowDisclosureListener  the new rowDisclosureListener value
863    */
864   final public void setRowDisclosureListener(MethodExpression rowDisclosureListener)
865   {
866     setProperty(ROW_DISCLOSURE_LISTENER_KEY, (rowDisclosureListener));
867   }
868 
869   /**
870    * Gets the set of disclosed rows for this component.
871    * Each entry in the set is a rowKey.
872    *
873    * @return  the new disclosedRowKeys value
874    */
875   final public RowKeySet getDisclosedRowKeys()
876   {
877     return (RowKeySet)getProperty(DISCLOSED_ROW_KEYS_KEY);
878   }
879 
880   /**
881    * Sets the set of disclosed rows for this component.
882    * Each entry in the set is a rowKey.
883    * 
884    * @param disclosedRowKeys  the new disclosedRowKeys value
885    */
886   final public void setDisclosedRowKeys(RowKeySet disclosedRowKeys)
887   {
888     setProperty(DISCLOSED_ROW_KEYS_KEY, (disclosedRowKeys));
889   }
890 
891   /**
892    * Gets a method reference to a selection listener
893    *
894    * @return  the new selectionListener value
895    */
896   final public MethodExpression getSelectionListener()
897   {
898     return (MethodExpression)getProperty(SELECTION_LISTENER_KEY);
899   }
900 
901   /**
902    * Sets a method reference to a selection listener
903    * 
904    * @param selectionListener  the new selectionListener value
905    */
906   final public void setSelectionListener(MethodExpression selectionListener)
907   {
908     setProperty(SELECTION_LISTENER_KEY, (selectionListener));
909   }
910 
911   /**
912    * Gets the selection state for this component.
913    *
914    * @return  the new selectedRowKeys value
915    */
916   final public RowKeySet getSelectedRowKeys()
917   {
918     return (RowKeySet)getProperty(SELECTED_ROW_KEYS_KEY);
919   }
920 
921   /**
922    * Sets the selection state for this component.
923    * 
924    * @param selectedRowKeys  the new selectedRowKeys value
925    */
926   final public void setSelectedRowKeys(RowKeySet selectedRowKeys)
927   {
928     setProperty(SELECTED_ROW_KEYS_KEY, (selectedRowKeys));
929   }
930 
931   /**
932    * Gets whether or not data validation - client-side or
933    *             server-side -
934    *           should take place when
935    *           events are generated by this component.
936    * 
937    *           When immediate is true, the default ActionListener
938    *           provided by the JavaServer Faces implementation
939    *           should be executed during Apply Request Values phase
940    *           of the request processing lifecycle, rather than
941    *           waiting until the Invoke Application phase.
942    *
943    * @return  the new immediate value
944    */
945   final public boolean isImmediate()
946   {
947     return ComponentUtils.resolveBoolean(getProperty(IMMEDIATE_KEY), false);
948   }
949 
950   /**
951    * Sets whether or not data validation - client-side or
952    *             server-side -
953    *           should take place when
954    *           events are generated by this component.
955    * 
956    *           When immediate is true, the default ActionListener
957    *           provided by the JavaServer Faces implementation
958    *           should be executed during Apply Request Values phase
959    *           of the request processing lifecycle, rather than
960    *           waiting until the Invoke Application phase.
961    * 
962    * @param immediate  the new immediate value
963    */
964   final public void setImmediate(boolean immediate)
965   {
966     setProperty(IMMEDIATE_KEY, immediate ? Boolean.TRUE : Boolean.FALSE);
967   }
968 
969   /**
970    * Gets a method reference to a sort listener
971    *
972    * @return  the new sortListener value
973    */
974   final public MethodExpression getSortListener()
975   {
976     return (MethodExpression)getProperty(SORT_LISTENER_KEY);
977   }
978 
979   /**
980    * Sets a method reference to a sort listener
981    * 
982    * @param sortListener  the new sortListener value
983    */
984   final public void setSortListener(MethodExpression sortListener)
985   {
986     setProperty(SORT_LISTENER_KEY, (sortListener));
987   }
988 
989   /**
990    * Gets a method reference to a rangeChange listener that
991    *          will be called when a new range is selected.
992    *
993    * @return  the new rangeChangeListener value
994    */
995   final public MethodExpression getRangeChangeListener()
996   {
997     return (MethodExpression)getProperty(RANGE_CHANGE_LISTENER_KEY);
998   }
999 
1000   /**
1001    * Sets a method reference to a rangeChange listener that
1002    *          will be called when a new range is selected.
1003    * 
1004    * @param rangeChangeListener  the new rangeChangeListener value
1005    */
1006   final public void setRangeChangeListener(MethodExpression rangeChangeListener)
1007   {
1008     setProperty(RANGE_CHANGE_LISTENER_KEY, (rangeChangeListener));
1009   }
1010 
1011   /**
1012    * Gets whether the "Show All" option is selected. The "Show All" option
1013    *         is available if there are less than 30 options and the row count in
1014    *         the data model is known.
1015    *
1016    * @return  the new showAll value
1017    */
1018   final public boolean isShowAll()
1019   {
1020     return ComponentUtils.resolveBoolean(getProperty(SHOW_ALL_KEY), false);
1021   }
1022 
1023   /**
1024    * Sets whether the "Show All" option is selected. The "Show All" option
1025    *         is available if there are less than 30 options and the row count in
1026    *         the data model is known.
1027    * 
1028    * @param showAll  the new showAll value
1029    */
1030   final public void setShowAll(boolean showAll)
1031   {
1032     setProperty(SHOW_ALL_KEY, showAll ? Boolean.TRUE : Boolean.FALSE);
1033   }
1034 
1035   /**
1036    * Adds a rowDisclosure listener.
1037    *
1038    * @param listener  the rowDisclosure listener to add
1039    */
1040   final public void addRowDisclosureListener(
1041     RowDisclosureListener listener)
1042   {
1043     addFacesListener(listener);
1044   }
1045 
1046   /**
1047    * Removes a rowDisclosure listener.
1048    *
1049    * @param listener  the rowDisclosure listener to remove
1050    */
1051   final public void removeRowDisclosureListener(
1052     RowDisclosureListener listener)
1053   {
1054     removeFacesListener(listener);
1055   }
1056 
1057   /**
1058    * Returns an array of attached rowDisclosure listeners.
1059    *
1060    * @return  an array of attached rowDisclosure listeners.
1061    */
1062   final public RowDisclosureListener[] getRowDisclosureListeners()
1063   {
1064     return (RowDisclosureListener[])getFacesListeners(RowDisclosureListener.class);
1065   }
1066 
1067   /**
1068    * Adds a selection listener.
1069    *
1070    * @param listener  the selection listener to add
1071    */
1072   final public void addSelectionListener(
1073     SelectionListener listener)
1074   {
1075     addFacesListener(listener);
1076   }
1077 
1078   /**
1079    * Removes a selection listener.
1080    *
1081    * @param listener  the selection listener to remove
1082    */
1083   final public void removeSelectionListener(
1084     SelectionListener listener)
1085   {
1086     removeFacesListener(listener);
1087   }
1088 
1089   /**
1090    * Returns an array of attached selection listeners.
1091    *
1092    * @return  an array of attached selection listeners.
1093    */
1094   final public SelectionListener[] getSelectionListeners()
1095   {
1096     return (SelectionListener[])getFacesListeners(SelectionListener.class);
1097   }
1098 
1099   /**
1100    * Adds a rangeChange listener.
1101    *
1102    * @param listener  the rangeChange listener to add
1103    */
1104   final public void addRangeChangeListener(
1105     RangeChangeListener listener)
1106   {
1107     addFacesListener(listener);
1108   }
1109 
1110   /**
1111    * Removes a rangeChange listener.
1112    *
1113    * @param listener  the rangeChange listener to remove
1114    */
1115   final public void removeRangeChangeListener(
1116     RangeChangeListener listener)
1117   {
1118     removeFacesListener(listener);
1119   }
1120 
1121   /**
1122    * Returns an array of attached rangeChange listeners.
1123    *
1124    * @return  an array of attached rangeChange listeners.
1125    */
1126   final public RangeChangeListener[] getRangeChangeListeners()
1127   {
1128     return (RangeChangeListener[])getFacesListeners(RangeChangeListener.class);
1129   }
1130 
1131   /**
1132    * Adds a sort listener.
1133    *
1134    * @param listener  the sort listener to add
1135    */
1136   final public void addSortListener(
1137     SortListener listener)
1138   {
1139     addFacesListener(listener);
1140   }
1141 
1142   /**
1143    * Removes a sort listener.
1144    *
1145    * @param listener  the sort listener to remove
1146    */
1147   final public void removeSortListener(
1148     SortListener listener)
1149   {
1150     removeFacesListener(listener);
1151   }
1152 
1153   /**
1154    * Returns an array of attached sort listeners.
1155    *
1156    * @return  an array of attached sort listeners.
1157    */
1158   final public SortListener[] getSortListeners()
1159   {
1160     return (SortListener[])getFacesListeners(SortListener.class);
1161   }
1162 
1163   @Override
1164   public String getFamily()
1165   {
1166     return COMPONENT_FAMILY;
1167   }
1168 
1169   @Override
1170   protected FacesBean.Type getBeanType()
1171   {
1172     return TYPE;
1173   }
1174 
1175   /**
1176    * Construct an instance of the UIXTable.
1177    */
1178   protected UIXTable(
1179     String rendererType
1180     )
1181   {
1182     super(rendererType);
1183   }
1184 
1185   static
1186   {
1187     TYPE.lockAndRegister("org.apache.myfaces.trinidad.Table","org.apache.myfaces.trinidad.Table");
1188   }
1189 }