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             
699             // make sure the state is properly initiated first by calling getCollectionModel().
700             getCollectionModel();
701 
702             // row key sets need the most recent collection model, but there is no one common entry
703             // point to set this on the set besides when code asks for the value from the bean
704             __flushCachedModel();  //insist that we populate with the very lastest instance of the collection model
705             rowKeys.setCollectionModel(getCollectionModel());
706           }
707           finally
708           {
709             _retrievingDisclosedRows = false;
710           }
711         }
712       }
713       else if (key == SELECTED_ROW_KEYS_KEY)
714       {
715         if (!_retrievingSelectedRows && value instanceof RowKeySet)
716         {
717           // Ensure that when we are retrieving and setting the collection model, this property
718           // is not asked for which would create an infinite loop
719           _retrievingSelectedRows = true;
720 
721           try
722           {
723             RowKeySet rowKeys = (RowKeySet) value;
724 
725             // make sure the state is properly initiated first by calling getCollectionModel().
726             getCollectionModel();
727 
728             // row key sets need the most recent collection model, but there is no one common entry
729             // point to set this on the set besides when code asks for the value from the bean
730             __flushCachedModel();  //insist that we populate with the very lastest instance of the collection model
731             rowKeys.setCollectionModel(getCollectionModel());
732           }
733           finally
734           {
735             _retrievingSelectedRows = false;
736           }
737         }
738       }
739 
740       return value;
741     }
742 
743     @Override
744     public Object saveState(FacesContext context)
745     {
746       RowKeySet rowKeys = (RowKeySet)super.getProperty(DISCLOSED_ROW_KEYS_KEY);
747       if (rowKeys != null)
748       {
749         // make sure the set does not pin the model in memory
750         rowKeys.setCollectionModel(null);
751       }
752       rowKeys = (RowKeySet)super.getProperty(SELECTED_ROW_KEYS_KEY);
753       if (rowKeys != null)
754       {
755         // make sure the set does not pin the model in memory
756         rowKeys.setCollectionModel(null);
757       }
758       return super.saveState(context);
759     }
760   }
761 
762   @Override
763   public void setRowKey(Object rowKey)
764   {
765     _preCurrencyChange(rowKey == null);
766     try
767     {
768       super.setRowKey(rowKey);
769     }
770     finally
771     {
772       _postCurrencyChange();
773     }
774   }
775 
776   @Override
777   public void setRowIndex(int rowIndex)
778   {
779     _preCurrencyChange(rowIndex == -1);
780     try
781     {
782       super.setRowIndex(rowIndex);
783     }
784     finally
785     {
786       _postCurrencyChange();
787     }
788   }
789   
790   /**
791    * When the currency changes keep track of the fact that we are ready to start iterating the stamps vs setting
792    * currency to process headers etc. If the new curency is not null but the old one was, 
793    * we are ready to start processing the stamps.
794    * 
795    * Similary keep track of the fact that we are done processing the our stamps. In this case the currency moves back to
796    * null after being set previously
797    */
798   private void _preCurrencyChange(boolean isNewCurrencyNull)
799   {
800     Object currencyObj = getRowKey();    
801     if(currencyObj == null && !isNewCurrencyNull)
802     {
803       _movingToNonNullCurrency = true;
804     }
805     
806     if(currencyObj != null && isNewCurrencyNull)
807     {
808       _movingBackToNullCurrency = true;
809     }
810   }
811   
812   /**
813    * Clean up variables setup during _preCurrencyChange.
814    */
815   private void _postCurrencyChange()
816   {
817     _movingToNonNullCurrency = false;
818     
819     if(_movingBackToNullCurrency)
820     {
821       _iteratorStampMap = null;
822     }
823     _movingBackToNullCurrency = false;
824   }
825   
826   transient private List<SortCriterion> _sortCriteria = null;
827   // cache of child components inside this table header/footer facets and column header/footer
828   // facets
829   transient private IdentityHashMap<UIComponent, Boolean> _containerClientIdCache = null;
830   
831   // transient variables used to track when are going to start processing the stamps and when we are done.
832   transient private boolean _movingToNonNullCurrency = false;
833   transient private boolean _movingBackToNullCurrency = false;
834   
835   // map used to support iterator stamping of columns
836   transient private Map<String, Object> _iteratorStampMap = null;
837 
838   /**
839    * the component to stamp below every row which is disclosed. Adding a
840    * detail facet will automatically cause the detail column to be displayed.
841    */
842   final public UIComponent getDetailStamp()
843   {
844     return getFacet(DETAIL_STAMP_FACET);
845   }
846 
847   /**
848    * the component to stamp below every row which is disclosed. Adding a
849    * detail facet will automatically cause the detail column to be displayed.
850    */
851   @SuppressWarnings("unchecked")
852   final public void setDetailStamp(UIComponent detailStampFacet)
853   {
854     getFacets().put(DETAIL_STAMP_FACET, detailStampFacet);
855   }
856 
857   /**
858    * Gets a method reference to an ExpansionListener
859    *
860    * @return  the new rowDisclosureListener value
861    */
862   final public MethodExpression getRowDisclosureListener()
863   {
864     return (MethodExpression)getProperty(ROW_DISCLOSURE_LISTENER_KEY);
865   }
866 
867   /**
868    * Sets a method reference to an ExpansionListener
869    * 
870    * @param rowDisclosureListener  the new rowDisclosureListener value
871    */
872   final public void setRowDisclosureListener(MethodExpression rowDisclosureListener)
873   {
874     setProperty(ROW_DISCLOSURE_LISTENER_KEY, (rowDisclosureListener));
875   }
876 
877   /**
878    * Gets the set of disclosed rows for this component.
879    * Each entry in the set is a rowKey.
880    *
881    * @return  the new disclosedRowKeys value
882    */
883   final public RowKeySet getDisclosedRowKeys()
884   {
885     return (RowKeySet)getProperty(DISCLOSED_ROW_KEYS_KEY);
886   }
887 
888   /**
889    * Sets the set of disclosed rows for this component.
890    * Each entry in the set is a rowKey.
891    * 
892    * @param disclosedRowKeys  the new disclosedRowKeys value
893    */
894   final public void setDisclosedRowKeys(RowKeySet disclosedRowKeys)
895   {
896     setProperty(DISCLOSED_ROW_KEYS_KEY, (disclosedRowKeys));
897   }
898 
899   /**
900    * Gets a method reference to a selection listener
901    *
902    * @return  the new selectionListener value
903    */
904   final public MethodExpression getSelectionListener()
905   {
906     return (MethodExpression)getProperty(SELECTION_LISTENER_KEY);
907   }
908 
909   /**
910    * Sets a method reference to a selection listener
911    * 
912    * @param selectionListener  the new selectionListener value
913    */
914   final public void setSelectionListener(MethodExpression selectionListener)
915   {
916     setProperty(SELECTION_LISTENER_KEY, (selectionListener));
917   }
918 
919   /**
920    * Gets the selection state for this component.
921    *
922    * @return  the new selectedRowKeys value
923    */
924   final public RowKeySet getSelectedRowKeys()
925   {
926     return (RowKeySet)getProperty(SELECTED_ROW_KEYS_KEY);
927   }
928 
929   /**
930    * Sets the selection state for this component.
931    * 
932    * @param selectedRowKeys  the new selectedRowKeys value
933    */
934   final public void setSelectedRowKeys(RowKeySet selectedRowKeys)
935   {
936     setProperty(SELECTED_ROW_KEYS_KEY, (selectedRowKeys));
937   }
938 
939   /**
940    * Gets whether or not data validation - client-side or
941    *             server-side -
942    *           should take place when
943    *           events are generated by this component.
944    * 
945    *           When immediate is true, the default ActionListener
946    *           provided by the JavaServer Faces implementation
947    *           should be executed during Apply Request Values phase
948    *           of the request processing lifecycle, rather than
949    *           waiting until the Invoke Application phase.
950    *
951    * @return  the new immediate value
952    */
953   final public boolean isImmediate()
954   {
955     return ComponentUtils.resolveBoolean(getProperty(IMMEDIATE_KEY), false);
956   }
957 
958   /**
959    * Sets whether or not data validation - client-side or
960    *             server-side -
961    *           should take place when
962    *           events are generated by this component.
963    * 
964    *           When immediate is true, the default ActionListener
965    *           provided by the JavaServer Faces implementation
966    *           should be executed during Apply Request Values phase
967    *           of the request processing lifecycle, rather than
968    *           waiting until the Invoke Application phase.
969    * 
970    * @param immediate  the new immediate value
971    */
972   final public void setImmediate(boolean immediate)
973   {
974     setProperty(IMMEDIATE_KEY, immediate ? Boolean.TRUE : Boolean.FALSE);
975   }
976 
977   /**
978    * Gets a method reference to a sort listener
979    *
980    * @return  the new sortListener value
981    */
982   final public MethodExpression getSortListener()
983   {
984     return (MethodExpression)getProperty(SORT_LISTENER_KEY);
985   }
986 
987   /**
988    * Sets a method reference to a sort listener
989    * 
990    * @param sortListener  the new sortListener value
991    */
992   final public void setSortListener(MethodExpression sortListener)
993   {
994     setProperty(SORT_LISTENER_KEY, (sortListener));
995   }
996 
997   /**
998    * Gets a method reference to a rangeChange listener that
999    *          will be called when a new range is selected.
1000    *
1001    * @return  the new rangeChangeListener value
1002    */
1003   final public MethodExpression getRangeChangeListener()
1004   {
1005     return (MethodExpression)getProperty(RANGE_CHANGE_LISTENER_KEY);
1006   }
1007 
1008   /**
1009    * Sets a method reference to a rangeChange listener that
1010    *          will be called when a new range is selected.
1011    * 
1012    * @param rangeChangeListener  the new rangeChangeListener value
1013    */
1014   final public void setRangeChangeListener(MethodExpression rangeChangeListener)
1015   {
1016     setProperty(RANGE_CHANGE_LISTENER_KEY, (rangeChangeListener));
1017   }
1018 
1019   /**
1020    * Gets whether the "Show All" option is selected. The "Show All" option
1021    *         is available if there are less than 30 options and the row count in
1022    *         the data model is known.
1023    *
1024    * @return  the new showAll value
1025    */
1026   final public boolean isShowAll()
1027   {
1028     return ComponentUtils.resolveBoolean(getProperty(SHOW_ALL_KEY), false);
1029   }
1030 
1031   /**
1032    * Sets whether the "Show All" option is selected. The "Show All" option
1033    *         is available if there are less than 30 options and the row count in
1034    *         the data model is known.
1035    * 
1036    * @param showAll  the new showAll value
1037    */
1038   final public void setShowAll(boolean showAll)
1039   {
1040     setProperty(SHOW_ALL_KEY, showAll ? Boolean.TRUE : Boolean.FALSE);
1041   }
1042 
1043   /**
1044    * Adds a rowDisclosure listener.
1045    *
1046    * @param listener  the rowDisclosure listener to add
1047    */
1048   final public void addRowDisclosureListener(
1049     RowDisclosureListener listener)
1050   {
1051     addFacesListener(listener);
1052   }
1053 
1054   /**
1055    * Removes a rowDisclosure listener.
1056    *
1057    * @param listener  the rowDisclosure listener to remove
1058    */
1059   final public void removeRowDisclosureListener(
1060     RowDisclosureListener listener)
1061   {
1062     removeFacesListener(listener);
1063   }
1064 
1065   /**
1066    * Returns an array of attached rowDisclosure listeners.
1067    *
1068    * @return  an array of attached rowDisclosure listeners.
1069    */
1070   final public RowDisclosureListener[] getRowDisclosureListeners()
1071   {
1072     return (RowDisclosureListener[])getFacesListeners(RowDisclosureListener.class);
1073   }
1074 
1075   /**
1076    * Adds a selection listener.
1077    *
1078    * @param listener  the selection listener to add
1079    */
1080   final public void addSelectionListener(
1081     SelectionListener listener)
1082   {
1083     addFacesListener(listener);
1084   }
1085 
1086   /**
1087    * Removes a selection listener.
1088    *
1089    * @param listener  the selection listener to remove
1090    */
1091   final public void removeSelectionListener(
1092     SelectionListener listener)
1093   {
1094     removeFacesListener(listener);
1095   }
1096 
1097   /**
1098    * Returns an array of attached selection listeners.
1099    *
1100    * @return  an array of attached selection listeners.
1101    */
1102   final public SelectionListener[] getSelectionListeners()
1103   {
1104     return (SelectionListener[])getFacesListeners(SelectionListener.class);
1105   }
1106 
1107   /**
1108    * Adds a rangeChange listener.
1109    *
1110    * @param listener  the rangeChange listener to add
1111    */
1112   final public void addRangeChangeListener(
1113     RangeChangeListener listener)
1114   {
1115     addFacesListener(listener);
1116   }
1117 
1118   /**
1119    * Removes a rangeChange listener.
1120    *
1121    * @param listener  the rangeChange listener to remove
1122    */
1123   final public void removeRangeChangeListener(
1124     RangeChangeListener listener)
1125   {
1126     removeFacesListener(listener);
1127   }
1128 
1129   /**
1130    * Returns an array of attached rangeChange listeners.
1131    *
1132    * @return  an array of attached rangeChange listeners.
1133    */
1134   final public RangeChangeListener[] getRangeChangeListeners()
1135   {
1136     return (RangeChangeListener[])getFacesListeners(RangeChangeListener.class);
1137   }
1138 
1139   /**
1140    * Adds a sort listener.
1141    *
1142    * @param listener  the sort listener to add
1143    */
1144   final public void addSortListener(
1145     SortListener listener)
1146   {
1147     addFacesListener(listener);
1148   }
1149 
1150   /**
1151    * Removes a sort listener.
1152    *
1153    * @param listener  the sort listener to remove
1154    */
1155   final public void removeSortListener(
1156     SortListener listener)
1157   {
1158     removeFacesListener(listener);
1159   }
1160 
1161   /**
1162    * Returns an array of attached sort listeners.
1163    *
1164    * @return  an array of attached sort listeners.
1165    */
1166   final public SortListener[] getSortListeners()
1167   {
1168     return (SortListener[])getFacesListeners(SortListener.class);
1169   }
1170 
1171   @Override
1172   public String getFamily()
1173   {
1174     return COMPONENT_FAMILY;
1175   }
1176 
1177   @Override
1178   protected FacesBean.Type getBeanType()
1179   {
1180     return TYPE;
1181   }
1182 
1183   /**
1184    * Construct an instance of the UIXTable.
1185    */
1186   protected UIXTable(
1187     String rendererType
1188     )
1189   {
1190     super(rendererType);
1191   }
1192 
1193   static
1194   {
1195     TYPE.lockAndRegister("org.apache.myfaces.trinidad.Table","org.apache.myfaces.trinidad.Table");
1196   }
1197 }