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.trinidad.component;
20  
21  import java.io.IOException;
22  import java.io.ObjectInputStream;
23  import java.io.Serializable;
24  
25  import java.util.AbstractMap;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import javax.faces.FacesException;
33  import javax.faces.component.ContextCallback;
34  import javax.faces.component.NamingContainer;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.visit.VisitCallback;
37  import javax.faces.component.visit.VisitContext;
38  import javax.faces.component.visit.VisitContextWrapper;
39  import javax.faces.component.visit.VisitResult;
40  import javax.faces.context.FacesContext;
41  import javax.faces.event.AbortProcessingException;
42  import javax.faces.event.ComponentSystemEvent;
43  import javax.faces.event.FacesEvent;
44  import javax.faces.event.PhaseId;
45  import javax.faces.render.Renderer;
46  
47  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
48  import org.apache.myfaces.trinidad.bean.FacesBean;
49  import org.apache.myfaces.trinidad.bean.PropertyKey;
50  import org.apache.myfaces.trinidad.context.ComponentContextChange;
51  import org.apache.myfaces.trinidad.context.ComponentContextManager;
52  import org.apache.myfaces.trinidad.context.RequestContext;
53  import org.apache.myfaces.trinidad.event.SelectionEvent;
54  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
55  import org.apache.myfaces.trinidad.model.CollectionModel;
56  import org.apache.myfaces.trinidad.model.LocalRowKeyIndex;
57  import org.apache.myfaces.trinidad.model.RowKeyChangeEvent;
58  import org.apache.myfaces.trinidad.model.RowKeyChangeListener;
59  import org.apache.myfaces.trinidad.model.SortCriterion;
60  import org.apache.myfaces.trinidad.render.ClientRowKeyManager;
61  import org.apache.myfaces.trinidad.render.ClientRowKeyManagerFactory;
62  import org.apache.myfaces.trinidad.util.Args;
63  import org.apache.myfaces.trinidad.util.ComponentUtils;
64  
65  
66  /**
67   * Base class for components that do stamping.
68   * This class set the EL 'var' variable correctly when the rowData changes.
69   * And it wraps events that are queued, so that the correct rowData can be
70   * restored on this component when the event is broadcast.
71   */
72  @JSFComponent
73  public abstract class UIXCollection extends UIXComponentBase
74    implements NamingContainer
75  {
76    static public final FacesBean.Type TYPE = new FacesBean.Type(
77      UIXComponentBase.TYPE);
78    static public final PropertyKey VAR_KEY =
79      TYPE.registerKey("var", String.class, PropertyKey.CAP_NOT_BOUND);
80  
81    protected UIXCollection(String rendererType)
82    {
83      super(rendererType);
84    }
85  
86    protected UIXCollection()
87    {
88      this(null);
89    }
90  
91    /**
92     * Gets the name of the EL variable used to reference each element of
93     * this collection.  Once this component has completed rendering, this
94     * variable is removed (or reverted back to its previous value).
95     */
96    final public String getVar()
97    {
98      return ComponentUtils.resolveString(getProperty(VAR_KEY));
99    }
100 
101   /**
102    * Sets the name of the EL variable used to reference each element of
103    * this collection.  Once this component has completed rendering, this
104    * variable is removed (or reverted back to its previous value).
105    */
106   final public void setVar(String var)
107   {
108     setProperty(VAR_KEY, (var));
109     InternalState iState = _getInternalState(false);
110     if (iState != null)
111     {
112       iState._var = var;
113     }
114   }
115 
116   /**
117    * Queues an event. If there is a currency set on this table, then
118    * the event will be wrapped so that when it is finally delivered, the correct
119    * currency will be restored.
120    * @param event a FacesEvent
121    */
122   @Override
123   public void queueEvent(FacesEvent event)
124   {
125     if (event.getSource() == this)
126     {
127       // Remember non-SelectionEvents on ourselves.  This
128       // is a hack to support validation in tableSelectXyz.
129       if (!(event instanceof SelectionEvent))
130       {
131         InternalState iState = _getInternalState(true);
132         iState._hasEvent = true;
133       }
134     }
135 
136     // we want to wrap up
137     // the event so we can execute it in the correct context (with the correct
138     // rowKey/rowData):
139     Object currencyKey = getRowKey();
140     event = new TableRowEvent(this, event, currencyKey);
141 
142     // Queue a CollectionContextEvent in order to allow this class to setup the component change
143     // before sub-classes attempt to process the table row event instance.
144     super.queueEvent(new CollectionContextEvent(this, event));
145   }
146 
147   /**
148    * Delivers a wrapped event to the appropriate component.
149    * If the event is a special wrapped event, it is unwrapped.
150    * @param event a FacesEvent
151    * @throws javax.faces.event.AbortProcessingException
152    */
153   @Override
154   public void broadcast(FacesEvent event)
155     throws AbortProcessingException
156   {
157     FacesContext context = getFacesContext();
158 
159     // Unwrap CollectionContextEvent events so that the original event is broadcast
160     // within a component change event context.
161     if (event instanceof CollectionContextEvent)
162     {
163       boolean inContextAtMethodInvocation = _inContext;
164       if (!inContextAtMethodInvocation)
165       {
166         _setupContextChange();
167       }
168 
169       try
170       {
171         CollectionContextEvent wrapperEvent = (CollectionContextEvent) event;
172         wrapperEvent.broadcastWrappedEvent(context);
173       }
174       finally
175       {
176         if (!inContextAtMethodInvocation)
177         {
178           _tearDownContextChange();
179         }
180       }
181     }
182     else
183     {
184       // For "TableRowEvents", set up the data before firing the
185       // event to the actual component.
186       if (event instanceof TableRowEvent)
187       {
188         TableRowEvent rowEvent = (TableRowEvent) event;
189         Object old = getRowKey();
190         setRowKey(rowEvent.getCurrencyKey());
191         rowEvent.broadcastWrappedEvent(context);
192         setRowKey(old);
193       }
194       else
195       {
196         super.broadcast(event);
197       }
198     }
199   }
200 
201   /**
202    * Decodes this component before decoding the facets.
203    * Decodes the children as many times as they are stamped.
204    * @param context the FacesContext
205    */
206   @Override
207   public final void processDecodes(FacesContext context)
208   {
209     if (context == null)
210       throw new NullPointerException();
211 
212     boolean inContextAtMethodInvocation = _inContext;
213     if (!inContextAtMethodInvocation)
214     {
215       _setupContextChange();
216     }
217 
218     try
219     {
220       _init();
221 
222       InternalState iState = _getInternalState(true);
223       iState._isFirstRender = false;
224 
225       if (!isRendered())
226         return;
227 
228       __flushCachedModel();
229 
230       // Make sure _hasEvent is false.
231       iState._hasEvent = false;
232 
233       // =-=AEW Because I'm getting the state in decode(), I need
234       // to do it before iterating over the children - otherwise,
235       // they'll be working off the wrong startIndex.  When state
236       // management is integrated, I can likely put this back in the
237       // usual order
238 
239       // Process this component itself
240       decode(context);
241 
242       // Process all facets and children of this component
243       decodeChildren(context);
244     }
245     finally
246     {
247       if (!inContextAtMethodInvocation)
248       {
249         _tearDownContextChange();
250       }
251     }
252   }
253 
254   @Override
255   protected void decodeChildrenImpl(FacesContext context)
256   {
257     processFacetsAndChildren(context, PhaseId.APPLY_REQUEST_VALUES);
258   }
259 
260   @Override
261   protected void validateChildrenImpl(FacesContext context)
262   {
263     processFacetsAndChildren(context, PhaseId.PROCESS_VALIDATIONS);
264   }
265 
266   @Override
267   protected void updateChildrenImpl(FacesContext context)
268   {
269     processFacetsAndChildren(context, PhaseId.UPDATE_MODEL_VALUES);
270   }
271 
272   /**
273    * Resets this component's stamps to their
274    * uninitialized state. This is useful when the user wants to
275    * undo any edits made to an editable table.
276    */
277   public void resetStampState()
278   {
279     InternalState iState = _getInternalState(true);
280     // TODO: this is over kill. for eg, It clears out any toggled showDetails.
281     Object initKey = _getCurrencyKeyForInitialStampState();
282     // do not clear the initial stamp state: a subtle bug could
283     // result where the initial state of each component is gone, so we
284     // fail to roll back to the initial default values
285     if (iState._stampState != null)
286       iState._stampState.clear(initKey);
287   }
288 
289   @Override
290   public Object processSaveState(FacesContext context)
291   {
292     boolean inContextAtMethodInvocation = _inContext;
293     if (!inContextAtMethodInvocation)
294     {
295       _setupContextChange();
296     }
297 
298     try
299     {
300       _stateSavingCurrencyKey = _resetCurrencyKeyForStateSaving(context);
301 
302       Object savedState = super.processSaveState(context);
303 
304       _restoreCurrencyKeyForStateSaving(_stateSavingCurrencyKey);
305       _resetInternalState();
306 
307       return savedState;
308     }
309     finally
310     {
311       if (!inContextAtMethodInvocation)
312       {
313         _tearDownContextChange();
314       }
315     }
316   }
317 
318   @Override
319   public Object saveState(FacesContext context)
320   {
321     // _stampState is stored as an instance variable, so it isn't
322     // automatically saved
323     Object superState = super.saveState(context);
324     final Object stampState, clientKeyMgr, idToIndexMap;
325 
326     // becareful not to create the internal state too early:
327     // otherwise, the internal state will be shared between
328     // nested table stamps:
329     InternalState iState = _getInternalState(false);
330     if (iState != null)
331     {
332       stampState = iState._stampState;
333       clientKeyMgr = iState._clientKeyMgr;
334       idToIndexMap = iState._idToIndexMap;
335     }
336     else
337     {
338       stampState = null;
339       clientKeyMgr = null;
340       idToIndexMap = null;
341     }
342 
343     if ((superState != null) || (stampState != null) ||
344         (clientKeyMgr != null) || (idToIndexMap != null))
345       return new Object[]{superState, stampState, clientKeyMgr, idToIndexMap};
346     return null;
347   }
348 
349 
350   @SuppressWarnings("unchecked")
351   @Override
352   public void restoreState(FacesContext context, Object state)
353   {
354     final Object superState, stampState, clientKeyMgr, idToIndexMap;
355     Object[] array = (Object[]) state;
356     if (array != null)
357     {
358       superState = array[0];
359       stampState = array[1];
360       clientKeyMgr = array[2];
361       idToIndexMap = array[3];
362     }
363     else
364     {
365       superState = null;
366       stampState = null;
367       clientKeyMgr = null;
368       idToIndexMap = null;
369     }
370     super.restoreState(context, superState);
371 
372     if ((stampState != null) || (clientKeyMgr != null) || (idToIndexMap != null))
373     {
374       InternalState iState = _getInternalState(true);
375       iState._stampState = (StampState) stampState;
376       iState._clientKeyMgr = (ClientRowKeyManager) clientKeyMgr;
377       iState._idToIndexMap = (Map<String, String>) idToIndexMap;
378     }
379     else
380     {
381       // becareful not to force creation of the internal state
382       // too early:
383       InternalState iState = _getInternalState(false);
384       if (iState != null)
385       {
386         iState._stampState = null;
387         iState._clientKeyMgr = null;
388         iState._idToIndexMap = null;
389       }
390     }
391   }
392 
393   /**
394    * Checks to see if the current row is available. This is useful when the
395    * total number of rows is not known.
396    * @see CollectionModel#isRowAvailable()
397    * @return true iff the current row is available.
398    */
399   public final boolean isRowAvailable()
400   {
401     return getCollectionModel().isRowAvailable();
402   }
403 
404   /**
405    * Check for an available row by row key.
406    * @param rowKey the row key for the row to check.
407    * @return true if a value exists; false otherwise.
408    */
409   public final boolean isRowAvailable(Object rowKey)
410   {
411     return getCollectionModel().isRowAvailable(rowKey);
412   }
413 
414   /**
415    * Get row data by row key.
416    * @param rowKey the row key for the row to get data.
417    * @return row data
418    */
419   public final Object getRowData(Object rowKey)
420   {
421     return getCollectionModel().getRowData(rowKey);
422   }
423 
424   /**
425    * Check if a range of rows is available starting from the current position
426    * @param rowCount number of rows to check
427    * @return true if all rows in range are available
428    */
429   public final boolean areRowsAvailable(int rowCount)
430   {
431     return getCollectionModel().areRowsAvailable(rowCount);
432   }
433 
434   /**
435    * Check if a range of rows is available from a starting index without
436    * requiring the client to iterate over the rows
437    * @param startIndex the starting index for the range
438    * @param rowCount number of rows to check
439    * @return true if all rows in range are available
440    */
441   public final boolean areRowsAvailable(int startIndex, int rowCount)
442   {
443     return getCollectionModel().areRowsAvailable(startIndex, rowCount);
444   }
445 
446   /**
447    * Check if a range of rows is available from a starting row key without
448    * requiring the client to iterate over the rows
449    * @param startRowKey the starting row key for the range
450    * @param rowCount number of rows to check
451    * @return true if all rows in range are available
452    */
453   public final boolean areRowsAvailable(Object startRowKey, int rowCount)
454   {
455     return getCollectionModel().areRowsAvailable(startRowKey, rowCount);
456   }
457 
458 
459 
460   /**
461    * Gets the total number of rows in this table.
462    * @see CollectionModel#getRowCount
463    * @return -1 if the total number is not known.
464    */
465   public final int getRowCount()
466   {
467     return getCollectionModel().getRowCount();
468   }
469 
470   /**
471    * Gets the index of the current row.
472    * @see CollectionModel#getRowIndex
473    * @return -1 if the current row is unavailable.
474    */
475   public final int getRowIndex()
476   {
477     return getCollectionModel().getRowIndex();
478   }
479 
480   /**
481    * Gets the rowKey of the current row.
482    * @see CollectionModel#getRowKey
483    * @return null if the current row is unavailable.
484    */
485   public final Object getRowKey()
486   {
487     InternalState iState = _getInternalState(true);
488     if (iState._currentRowKey == _NULL)
489     {
490       // See bug 4534104.
491       // Sometimes the rowKey for a particular row changes during update model
492       // (this happens in ADFM if you edit the primary key of a row).
493       // It is bad if the rowKey changes after _restoreStampState() and
494       // before _saveStampState(). Therefore, we cache it:
495       iState._currentRowKey = getCollectionModel().getRowKey();
496       iState._model.addRowKeyChangeListener(iState);
497     }
498 
499     return iState._currentRowKey;
500   }
501 
502   /**
503    * Gets the data for the current row.
504    * @see CollectionModel#getRowData(int)
505    * @return null if the current row is unavailable
506    */
507   public final Object getRowData()
508   {
509     CollectionModel model = getCollectionModel();
510     // we need to call isRowAvailable() here because the 1.0 sun RI was
511     // throwing exceptions when getRowData() was called with rowIndex=-1
512     return model.isRowAvailable() ? model.getRowData() : null;
513   }
514 
515   /**
516    * Checks to see if the row at the given index is available.
517    * @see CollectionModel#isRowAvailable(int)
518    * @param rowIndex the index of the row to check.
519    * @return true if data for the row exists.
520    */
521   public boolean isRowAvailable(int rowIndex)
522   {
523     return getCollectionModel().isRowAvailable(rowIndex);
524   }
525 
526   /**
527    * Gets the rowData at the given index.
528    * @see CollectionModel#getRowData(int)
529    * @param rowIndex the index of the row to get data from.
530    * @return the data for the given row.
531    */
532   public Object getRowData(int rowIndex)
533   {
534     return getCollectionModel().getRowData(rowIndex);
535   }
536 
537   /**
538    * Gets the EL variable name to use to expose the varStatusMap.
539    * @see #createVarStatusMap()
540    */
541   public abstract String getVarStatus();
542 
543   /**
544    * Makes a row current.
545    * This method calls {@link #preRowDataChange} and
546    * {@link #postRowDataChange} as appropriate.
547    * @see CollectionModel#setRowKey
548    * @param rowKey The rowKey of the row that should be made current. Use null
549    * to clear the current row.
550    */
551   public void setRowKey(Object rowKey)
552   {
553     _verifyComponentInContext();
554 
555     preRowDataChange();
556     getCollectionModel().setRowKey(rowKey);
557     postRowDataChange();
558     if (_LOG.isFine() && (rowKey != null) && (!isRowAvailable()))
559       _LOG.fine("no row available for rowKey:"+rowKey);
560   }
561 
562   /**
563    * Makes a row current.
564    * This method calls {@link #preRowDataChange} and
565    * {@link #postRowDataChange} as appropriate.
566    * @see CollectionModel#setRowIndex
567    * @param rowIndex The rowIndex of the row that should be made current. Use -1
568    * to clear the current row.
569    */
570   public void setRowIndex(int rowIndex)
571   {
572     _verifyComponentInContext();
573 
574     preRowDataChange();
575     getCollectionModel().setRowIndex(rowIndex);
576     postRowDataChange();
577     if (_LOG.isFine() && (rowIndex != -1) && (!isRowAvailable()))
578       _LOG.fine("no row available for rowIndex:"+rowIndex);
579   }
580 
581   /**
582    * @param property a property name in the model
583    * @return  true if the model is sortable by the given property.
584    * @see CollectionModel#isSortable
585    */
586   public final boolean isSortable(String property)
587   {
588     return getCollectionModel().isSortable(property);
589   }
590 
591   /**
592    * Sorts this collection by the given criteria.
593    * @param criteria Each element in this List must be of type SortCriterion.
594    * @see org.apache.myfaces.trinidad.model.SortCriterion
595    * @see CollectionModel#setSortCriteria
596    */
597   public void setSortCriteria(List<SortCriterion> criteria)
598   {
599     getCollectionModel().setSortCriteria(criteria);
600   }
601 
602   /**
603    * Gets the criteria that this collection is sorted by.
604    * @return each element in this List is of type SortCriterion.
605    * An empty list is returned if this collection is not sorted.
606    * @see org.apache.myfaces.trinidad.model.SortCriterion
607    * @see CollectionModel#getSortCriteria
608    */
609   public final List<SortCriterion> getSortCriteria()
610   {
611     return getCollectionModel().getSortCriteria();
612   }
613 
614   /**
615    * Clear the rowKey-to-currencyString cache.
616    * The cache is not cleared immediately; instead it will be cleared
617    * when {@link #encodeBegin(FacesContext)} is called.
618    * @deprecated Have your Renderer implement {@link ClientRowKeyManagerFactory}
619    * and create your own {@link ClientRowKeyManager} instances. Then you can
620    * manage the lifecycle of each key inside your ClientRowKeyManager.
621    */
622   @Deprecated
623   protected void clearCurrencyStringCache()
624   {
625     _getInternalState(true)._clearTokenCache = true;
626   }
627 
628   /**
629    * Clears all the currency strings.
630    */
631   @Override
632   public final void encodeBegin(FacesContext context) throws IOException
633   {
634     _setupContextChange();
635     boolean teardown = true;
636     try
637     {
638       _init();
639 
640       InternalState istate = _getInternalState(true);
641       // we must not clear the currency cache everytime. only clear
642       // it in response to specific events: bug 4773659
643 
644       // TODO all this code should be removed and moved into the renderer:
645       if (istate._clearTokenCache)
646       {
647         istate._clearTokenCache = false;
648         ClientRowKeyManager keyMgr = getClientRowKeyManager();
649         if (keyMgr instanceof DefaultClientKeyManager)
650           ((DefaultClientKeyManager) keyMgr).clear();
651       }
652       __flushCachedModel();
653 
654       Object assertKey = null;
655       assert ((assertKey = getRowKey()) != null) || true;
656       __encodeBegin(context);
657       // make sure that the rendering code preserves the currency:
658       assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";
659 
660       teardown = false;
661     }
662     finally
663     {
664       if (teardown)
665       {
666         // Tear down on errors & exceptions
667         _tearDownContextChange();
668       }
669     }
670   }
671 
672   @Override
673   public void encodeEnd(FacesContext context) throws IOException
674   {
675     try
676     {
677       Object assertKey = null;
678       assert ((assertKey = getRowKey()) != null) || true;
679       super.encodeEnd(context);
680       // make sure that the rendering code preserves the currency:
681       assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";
682     }
683     finally
684     {
685       _tearDownContextChange();
686     }
687   }
688 
689   @Override
690   protected void setupVisitingContext(FacesContext context)
691   {
692     super.setupVisitingContext(context);
693     _setupContextChange();
694 
695     if (Boolean.TRUE.equals(context.getAttributes().get("javax.faces.IS_SAVING_STATE")))
696     {
697       _stateSavingCurrencyKey = _resetCurrencyKeyForStateSaving(context);
698     }
699   }
700 
701   @Override
702   protected void tearDownVisitingContext(FacesContext context)
703   {
704     if (Boolean.TRUE.equals(context.getAttributes().get("javax.faces.IS_SAVING_STATE")))
705     {
706       _restoreCurrencyKeyForStateSaving(_stateSavingCurrencyKey);
707       _resetInternalState();
708     }
709 
710     _tearDownContextChange();
711     super.tearDownVisitingContext(context);
712   }
713 
714   private boolean _assertKeyPreserved(Object oldKey)
715   {
716     Object newKey = getRowKey();
717     return (oldKey != null) ? oldKey.equals(newKey) : (newKey == null);
718   }
719 
720   void __encodeBegin(FacesContext context) throws IOException
721   {
722     super.encodeBegin(context);
723   }
724 
725   /**
726    * Checks to see if processDecodes was called. If this returns true
727    * then this is the initial request, and processDecodes has not been called.
728    */
729   boolean __isFirstRender()
730   {
731     InternalState iState = _getInternalState(true);
732     return iState._isFirstRender;
733   }
734 
735   /**
736    * @deprecated use getClientRowKey
737    * @see #getClientRowKey
738    */
739   @Deprecated
740   public String getCurrencyString()
741   {
742     return getClientRowKey();
743   }
744 
745   /**
746    * @deprecated use setClientRowKey
747    * @see #setClientRowKey
748    */
749   @Deprecated
750   public void setCurrencyString(String currency)
751   {
752     setClientRowKey(currency);
753   }
754 
755 
756   /**
757    * Gets a String version of the current rowkey.
758    * The contents of the String are controlled by the current
759    * {@link ClientRowKeyManager}.
760    * This String can be passed into the {@link #setClientRowKey} method
761    * to restore the current rowData.
762    * The lifetime of this String is short and it may not be valid during
763    * future requests; however, it is guaranteed to be valid
764    * for the next subsequent request.
765    * @see UIXCollection#setClientRowKey(java.lang.String)
766    * @see UIXCollection#getClientRowKeyManager()
767    * @see ClientRowKeyManager#getClientRowKey
768    */
769   public String getClientRowKey()
770   {
771     // only call getCurrencyKey if we already have a dataModel.
772     // otherwise behave as though no currency was set.
773     // we need to do this because we don't want dataModel created for components
774     // that are not rendered. The faces RI calls getClientId even on components
775     // that are not rendered and this in turn was calling this method:
776     Object currencyObject = _getCurrencyKey();
777     if (currencyObject == null)
778       return null;
779 
780     Object initKey = _getCurrencyKeyForInitialStampState();
781     if (_equals(currencyObject, initKey))
782       return null;
783 
784     FacesContext fc = FacesContext.getCurrentInstance();
785     String key = getClientRowKeyManager().getClientRowKey(fc, this, currencyObject);
786     return key;
787   }
788 
789   /**
790    * This is a safe way of getting currency keys and not accidentally forcing
791    * the model to execute. When rendered="false" we should never execute the model.
792    * However, the JSF engine calls certain methods when rendered="false" such as
793    * processSaveState and getClientId.
794    * Those methods, in turn, get the CurrencyKey.
795    */
796   private Object _getCurrencyKey()
797   {
798     // use false so that we don't create an internal state.
799     // if the internal state is created too soon, then the same internal
800     // state will get shared across all nested table instances.
801     // this was causing bug 4616844:
802     InternalState iState = _getInternalState(false);
803     if (iState == null)
804       return null;
805 
806     return (iState._model != null)
807       ? getRowKey()
808       : _getCurrencyKeyForInitialStampState();
809   }
810 
811   /**
812      * Restores this component's rowData to be what it was when the given
813      * client rowKey string was created.
814      * @see UIXCollection#getClientRowKey()
815      */
816   public void setClientRowKey(String clientRowKey)
817   {
818     if (clientRowKey == null)
819     {
820       setRowKey(_getCurrencyKeyForInitialStampState());
821       return;
822     }
823 
824     FacesContext fc = FacesContext.getCurrentInstance();
825     Object rowkey = getClientRowKeyManager().getRowKey(fc, this, clientRowKey);
826 
827     if (rowkey == null)
828     {
829       _LOG.severe("CANNOT_FIND_ROWKEY",clientRowKey);
830     }
831     else
832       setRowKey(rowkey);
833   }
834 
835   public void processRestoreState(
836     FacesContext context,
837     Object       state)
838   {
839     boolean inContextAtMethodInvocation = _inContext;
840     if (!inContextAtMethodInvocation)
841     {
842       _setupContextChange();
843     }
844 
845     try
846     {
847       super.processRestoreState(context, state);
848     }
849     finally
850     {
851       if (!inContextAtMethodInvocation)
852       {
853         _tearDownContextChange();
854       }
855     }
856   }
857 
858   public void processUpdates(FacesContext context)
859   {
860     boolean inContextAtMethodInvocation = _inContext;
861     if (!inContextAtMethodInvocation)
862     {
863       _setupContextChange();
864     }
865 
866     try
867     {
868       super.processUpdates(context);
869     }
870     finally
871     {
872       if (!inContextAtMethodInvocation)
873       {
874         _tearDownContextChange();
875       }
876     }
877   }
878 
879   public void processValidators(FacesContext context)
880   {
881     boolean inContextAtMethodInvocation = _inContext;
882     if (!inContextAtMethodInvocation)
883     {
884       _setupContextChange();
885     }
886 
887     try
888     {
889       super.processValidators(context);
890     }
891     finally
892     {
893       if (!inContextAtMethodInvocation)
894       {
895         _tearDownContextChange();
896       }
897     }
898   }
899 
900   public void processEvent(ComponentSystemEvent event)
901     throws AbortProcessingException
902   {
903     boolean inContextAtMethodInvocation = _inContext;
904     if (!inContextAtMethodInvocation)
905     {
906       _setupContextChange();
907     }
908 
909     try
910     {
911       super.processEvent(event);
912     }
913     finally
914     {
915       if (!inContextAtMethodInvocation)
916       {
917         _tearDownContextChange();
918       }
919     }
920   }
921 
922   /**
923      * Gets the client-id of this component, without any NamingContainers.
924      * This id changes depending on the currency Object.
925      * Because this implementation uses currency strings, the local client ID is
926      * not stable for very long. Its lifetime is the same as that of a
927      * currency string.
928      * @see UIXCollection#getClientRowKey()
929      * @return the local clientId
930      */
931   @Override
932   public final String getContainerClientId(FacesContext context)
933   {
934     String id = getClientId(context);
935     String key = getClientRowKey();
936     if (key != null)
937     {
938       StringBuilder bld = __getSharedStringBuilder();
939       bld.append(id).append(NamingContainer.SEPARATOR_CHAR).append(key);
940       id = bld.toString();
941     }
942 
943     return id;
944   }
945 
946   /**
947    * Prepares this component for a change in the rowData.
948    * This method should be called right before the rowData changes.
949    * It saves the internal states of all the stamps of this component
950    * so that they can be restored when the rowData is reverted.
951    */
952   protected final void preRowDataChange()
953   {
954     _saveStampState();
955     InternalState iState = _getInternalState(true);
956     // mark the cached rowKey as invalid:
957     iState._currentRowKey = _NULL;
958     // we don't have cached rowkey, thus remove rowkeychangelistener
959     if (iState._model != null)
960       iState._model.removeRowKeyChangeListener(iState);
961   }
962 
963   /**
964    * Sets up this component to use the new rowData.
965    * This method should be called right after the rowData changes.
966    * It sets up the var EL variable to be the current rowData.
967    * It also sets up the internal states of all the stamps of this component
968    * to match this new rowData.
969    */
970   protected final void postRowDataChange()
971   {
972     Object rowData = getRowData();
973     if (_LOG.isFinest() && (rowData == null))
974     {
975       _LOG.finest("rowData is null at rowIndex:"+getRowIndex()+
976                   " and currencyKey:"+getRowKey());
977     }
978 
979     InternalState iState = _getInternalState(true);
980     if (rowData == null)
981     {
982       // if the rowData is null, then we will restore the EL 'var' variable
983       // to be whatever the value was, before this component started rendering:
984       if (iState._prevVarValue != _NULL)
985       {
986         _setELVar(iState._var, iState._prevVarValue);
987         iState._prevVarValue = _NULL;
988       }
989       if (iState._prevVarStatus != _NULL)
990       {
991         _setELVar(iState._varStatus, iState._prevVarStatus);
992         iState._prevVarStatus = _NULL;
993       }
994     }
995     else
996     {
997       if (iState._var != null)
998       {
999         Object oldData = _setELVar(iState._var, rowData);
1000         if (iState._prevVarValue == _NULL)
1001           iState._prevVarValue = oldData;
1002       }
1003 
1004       // varStatus is not set per row. It is only set once.
1005       // if _PrevVarStatus has not been assigned, then we have not set the
1006       // varStatus yet:
1007       if ((iState._varStatus != null) && (iState._prevVarStatus == _NULL))
1008       {
1009         Map<String, Object> varStatusMap = createVarStatusMap();
1010         iState._prevVarStatus = _setELVar(iState._varStatus, varStatusMap);
1011       }
1012     }
1013 
1014     _restoreStampState();
1015 
1016     // ensure the client IDs are reset on the component, otherwise they will not get the
1017     // proper stamped IDs. This mirrors the behavior in UIData and follows the JSF specification
1018     // on when client IDs are allowed to be cached and when they must be reset
1019     List<UIComponent> stamps = getStamps();
1020 
1021     for (UIComponent stamp : stamps)
1022       UIXComponent.clearCachedClientIds(stamp);
1023   }
1024 
1025   /**
1026    * Gets the UIComponents that are considered stamps.
1027    * This implementation simply returns the children of this component.
1028    * @return each element must be of type UIComponent.
1029    */
1030   @SuppressWarnings("unchecked")
1031   protected List<UIComponent> getStamps()
1032   {
1033     return getChildren();
1034   }
1035 
1036   /**
1037    * Gets the currencyObject to setup the rowData to use to build initial
1038    * stamp state.
1039    * <p>
1040    *   This allows the collection model to have an initial row key outside of the UIComponent.
1041    *   Should the model be at a row that is not the first row, the component will restore the row
1042    *   back to the initial row key instead of a null row key once stamping is done.
1043    * </p>
1044    */
1045   private Object _getCurrencyKeyForInitialStampState()
1046   {
1047     InternalState iState = _getInternalState(false);
1048     if (iState == null)
1049       return null;
1050 
1051     Object rowKey = iState._initialStampStateKey;
1052     return (rowKey == _NULL) ? null : rowKey;
1053   }
1054 
1055   /**
1056    * Saves the state of a stamp. This method is called when the currency of this
1057    * component is changed so that the state of this stamp can be preserved, before
1058    * the stamp is updated with the state corresponding to the new currency.
1059    * This method recurses for the children and facets of the stamp.
1060    * @return this object must be Serializable if client-side state saving is
1061    * used.
1062    */
1063   @SuppressWarnings("unchecked")
1064   protected Object saveStampState(FacesContext context, UIComponent stamp)
1065   {
1066     if (stamp.isTransient())
1067       return null;
1068 
1069     boolean needsTearDownContext = false;
1070 
1071     if(stamp instanceof FlattenedComponent && stamp instanceof UIXComponent)
1072     {
1073       ((UIXComponent)stamp).setupVisitingContext(context);
1074       needsTearDownContext = true;
1075     }
1076 
1077     Object[] state = null;
1078 
1079     try
1080     {
1081       // The structure we will use is:
1082       //   0: state of the stamp
1083       //   1: state of the children (a map from child's id to its state)
1084       //   2: state of the facets (a map from facet name to its state)
1085       // If there is no facet state, we have a two-element array
1086       // If there is no facet state or child state, we have a one-elment array
1087       // If there is no state at all, we return null
1088 
1089       Object stampState = StampState.saveStampState(context, stamp);
1090 
1091       // StampState can never EVER be an Object array, as if we do,
1092       // we have no possible way of identifying the difference between
1093       // just having stamp state, and having stamp state + child/facet state
1094       assert(!(stampState instanceof Object[]));
1095 
1096       int facetCount = stamp.getFacetCount();
1097 
1098       if (facetCount > 0)
1099       {
1100         Map<String, Object> facetState = null;
1101 
1102         Map<String, UIComponent> facetMap = stamp.getFacets();
1103 
1104         for(Map.Entry<String, UIComponent> entry : facetMap.entrySet())
1105         {
1106           Object singleFacetState = saveStampState(context, entry.getValue());
1107           if (singleFacetState == null)
1108             continue;
1109 
1110           // Don't bother allocating anything until we have some non-null
1111           // facet state
1112           if (facetState == null)
1113           {
1114             facetState = new HashMap<String, Object>(facetCount);
1115           }
1116 
1117           facetState.put(entry.getKey(), singleFacetState);
1118         }
1119 
1120         // OK, we had something:  allocate the state array to three
1121         // entries, and insert the facet state at position 2
1122         if (facetState != null)
1123         {
1124           state = new Object[3];
1125           state[2] = facetState;
1126         }
1127       }
1128 
1129       // If we have any children, iterate through the array,
1130       // saving state
1131       Object childState = StampState.saveChildStampState(context,
1132                                                          stamp,
1133                                                          this);
1134       if (childState != null)
1135       {
1136         // If the state hasn't been allocated yet, we only
1137         // need a two-element array
1138         if (state == null)
1139           state = new Object[2];
1140         state[1] = childState;
1141       }
1142 
1143       // If we don't have an array, just return the stamp
1144       // state
1145       if (state == null)
1146         return stampState;
1147 
1148       // Otherwise, store the stamp state at index 0, and return
1149       state[0] = stampState;
1150     }
1151     finally
1152     {
1153       if(needsTearDownContext)
1154         ((UIXComponent)stamp).tearDownVisitingContext(context);
1155     }
1156     return state;
1157   }
1158 
1159   /**
1160    * Restores the state of a stamp. This method is called after the currency of this
1161    * component is changed so that the state of this stamp can be changed
1162    * to match the new currency.
1163    * This method recurses for the children and facets of the stamp.
1164    */
1165   @SuppressWarnings("unchecked")
1166   protected void restoreStampState(FacesContext context, UIComponent stamp,
1167                                    Object stampState)
1168   {
1169     // No state for the component - return
1170     if (stampState == null)
1171     {
1172       return;
1173     }
1174 
1175     // If this isn't an Object array, then it's a component with state
1176     // of its own, but no child/facet state - so restore and be done
1177     if (!(stampState instanceof Object[]))
1178     {
1179       StampState.restoreStampState(context, stamp, stampState);
1180       // NOTE early return
1181       return;
1182     }
1183 
1184     Object[] state = (Object[]) stampState;
1185     int stateSize = state.length;
1186     // We always have at least one element if we get to here
1187     assert(stateSize >= 1);
1188 
1189     StampState.restoreStampState(context, stamp, state[0]);
1190 
1191 
1192     // If there's any facet state, restore it
1193     if (stateSize >= 3 && (state[2] instanceof Map))
1194     {
1195       Map<String, Object> facetStateMap = (Map<String, Object>) state[2];
1196       // This had better be non-null, otherwise we never
1197       // should have allocated a three-element map!
1198       assert(facetStateMap != null);
1199 
1200       for (String facetName : facetStateMap.keySet())
1201       {
1202         Object facetState = facetStateMap.get(facetName);
1203         if (facetState != null)
1204           restoreStampState(context, stamp.getFacet(facetName), facetState);
1205       }
1206     }
1207 
1208     // If there's any child state, restore it
1209     if (stateSize >= 2)
1210     {
1211       StampState.restoreChildStampState(context,
1212                                         stamp,
1213                                         this,
1214                                         state[1]);
1215     }
1216   }
1217 
1218   /**
1219    * Process a component.
1220    * This method calls {@link #processDecodes(FacesContext)},
1221    * {@link #processValidators} or
1222    * {@link #processUpdates}
1223    * depending on the {#link PhaseId}.
1224    */
1225   protected final void processComponent(
1226     FacesContext context,
1227     UIComponent  component,
1228     PhaseId      phaseId)
1229   {
1230     if (component != null)
1231     {
1232       if (phaseId == PhaseId.APPLY_REQUEST_VALUES)
1233         component.processDecodes(context);
1234       else if (phaseId == PhaseId.PROCESS_VALIDATIONS)
1235         component.processValidators(context);
1236       else if (phaseId == PhaseId.UPDATE_MODEL_VALUES)
1237         component.processUpdates(context);
1238       else
1239         throw new IllegalArgumentException(_LOG.getMessage(
1240           "BAD_PHASEID",phaseId));
1241     }
1242   }
1243 
1244   /**
1245    * Process this component's facets and children.
1246    * This method should call {@link #processComponent}
1247    * as many times as necessary for each facet and child.
1248    * {@link #processComponent}
1249    * may be called repeatedly for the same child if that child is
1250    * being stamped.
1251    */
1252   protected abstract void processFacetsAndChildren(
1253     FacesContext context,
1254     PhaseId phaseId);
1255 
1256   /**
1257    * Gets the CollectionModel to use with this component.
1258    */
1259   protected final CollectionModel getCollectionModel()
1260   {
1261     return getCollectionModel(true);
1262   }
1263 
1264   /**
1265    * Gets the ClientRowKeyManager that is used to handle the
1266    * {@link #getClientRowKey} and
1267    * {@link #setClientRowKey} methods.
1268    * If the manager does not already exist a new one is created.
1269    * In order to create your own manager, your Renderer (for this component)
1270    * must implement
1271    * {@link ClientRowKeyManagerFactory}
1272    */
1273   public final ClientRowKeyManager getClientRowKeyManager()
1274   {
1275     // this method must be public, because specific renderers
1276     // need access to the ClientRowKeyManager so that they might prune it.
1277 
1278     InternalState iState = _getInternalState(true);
1279     if (iState._clientKeyMgr == null)
1280     {
1281       FacesContext fc = FacesContext.getCurrentInstance();
1282       Renderer r = getRenderer(fc);
1283       iState._clientKeyMgr = (r instanceof ClientRowKeyManagerFactory)
1284         ? ((ClientRowKeyManagerFactory) r).createClientRowKeyManager(fc, this)
1285         : new DefaultClientKeyManager();
1286     }
1287     return iState._clientKeyMgr;
1288   }
1289 
1290   public boolean invokeOnComponent(FacesContext context,
1291                                    String clientId,
1292                                    ContextCallback callback)
1293     throws FacesException
1294   {
1295     boolean invokedComponent;
1296     setupVisitingContext(context);
1297 
1298     try
1299     {
1300       String thisClientId = getClientId(context);
1301       if (clientId.equals(thisClientId))
1302       {
1303         if (!_getAndMarkFirstInvokeForRequest(context, clientId))
1304         {
1305           // Call _init() since __flushCachedModel() assumes that
1306           // selectedRowKeys and disclosedRowKeys are initialized to be non-null
1307           _init();
1308 
1309           __flushCachedModel();
1310         }
1311 
1312         RequestContext requestContext = RequestContext.getCurrentInstance();
1313         requestContext.pushCurrentComponent(context, this);
1314         pushComponentToEL(context, null);
1315 
1316         try
1317         {
1318           callback.invokeContextCallback(context, this);
1319         }
1320         finally
1321         {
1322           popComponentFromEL(context);
1323           requestContext.popCurrentComponent(context, this);
1324         }
1325 
1326         invokedComponent = true;
1327       }
1328       else
1329       {
1330         // If we're on a row, set the currency, and invoke
1331         // inside
1332         int thisClientIdLength = thisClientId.length();
1333         if (clientId.startsWith(thisClientId) &&
1334             (clientId.charAt(thisClientIdLength) == NamingContainer.SEPARATOR_CHAR))
1335         {
1336           if (!_getAndMarkFirstInvokeForRequest(context, thisClientId))
1337           {
1338             // Call _init() since __flushCachedModel() assumes that
1339             // selectedRowKeys and disclosedRowKeys are initialized to be non-null
1340             _init();
1341 
1342             __flushCachedModel();
1343           }
1344 
1345           String postId = clientId.substring(thisClientIdLength + 1);
1346           int sepIndex = postId.indexOf(NamingContainer.SEPARATOR_CHAR);
1347           // If there's no separator character afterwards, then this
1348           // isn't a row key
1349           if (sepIndex < 0)
1350             return invokeOnChildrenComponents(context, clientId, callback);
1351           else
1352           {
1353             String currencyString = postId.substring(0, sepIndex);
1354             Object rowKey = getClientRowKeyManager().getRowKey(context, this, currencyString);
1355 
1356             // A non-null rowKey here means we are on a row and we should set currency,  otherwise
1357             // the client id is for a non-stamped child component in the table/column header/footer.
1358             if (rowKey != null)
1359             {
1360               Object oldRowKey = getRowKey();
1361               try
1362               {
1363                 setRowKey(rowKey);
1364                 invokedComponent = invokeOnChildrenComponents(context, clientId, callback);
1365               }
1366               finally
1367               {
1368                 // And restore the currency
1369                 setRowKey(oldRowKey);
1370               }
1371             }
1372             else
1373             {
1374               invokedComponent = invokeOnChildrenComponents(context, clientId, callback);
1375             }
1376           }
1377         }
1378         else
1379         {
1380           // clientId isn't in this subtree
1381           invokedComponent = false;
1382         }
1383       }
1384     }
1385     finally
1386     {
1387       tearDownVisitingContext(context);
1388     }
1389 
1390     return invokedComponent;
1391   }
1392 
1393   /**
1394    * <p>
1395    * Override default children visiting code to visit the facets and facets of the columns
1396    * before delegating to the <code>visitData</code> to visit the individual rows of data.
1397    * </p><p>
1398    * Subclasses should override this method if they wish to change the way in which the non-stamped
1399    * children are visited.  If they wish to change the wash the the stamped children are visited,
1400    * they should override <code>visitData</code> instead.
1401    * </p>
1402    * @param visitContext
1403    * @param callback
1404    * @return <code>true</code> if all of the children to visit have been visited
1405    * @see #visitData
1406    */
1407   @Override
1408   protected boolean visitChildren(
1409     VisitContext  visitContext,
1410     VisitCallback callback)
1411   {
1412     return defaultVisitChildren(visitContext, callback);
1413   }
1414 
1415   /**
1416    * Performs a non-iterating visit of the children.  The default implementation visits all
1417    * of the children.  If the UIXCollection subclass doesn't visit some of its children in
1418    * certain cases, it needs to override this method.
1419    * @param visitContext
1420    * @param callback
1421    * @return
1422    */
1423   protected boolean visitChildrenWithoutIterating(
1424     VisitContext  visitContext,
1425     VisitCallback callback)
1426   {
1427     return visitAllChildren(visitContext, callback);
1428   }
1429 
1430   /**
1431    * Default implementation of child visiting of UIXCollection subclasses for cases where a
1432    * UIXCollection subclass wants to restore the default implementation that one of its
1433    * superclasses have overridden.
1434    * @param visitContext
1435    * @param callback
1436    * @return
1437    */
1438   protected final boolean defaultVisitChildren(
1439     VisitContext  visitContext,
1440     VisitCallback callback)
1441   {
1442     if (ComponentUtils.isSkipIterationVisit(visitContext))
1443     {
1444       return visitChildrenWithoutIterating(visitContext, callback);
1445     }
1446     else
1447     {
1448       boolean doneVisiting;
1449 
1450       // Clear out the row index if one is set so that
1451       // we start from a clean slate.
1452       int oldRowIndex = getRowIndex();
1453       setRowIndex(-1);
1454 
1455       try
1456       {
1457         // visit the unstamped children
1458         doneVisiting = visitUnstampedFacets(visitContext, callback);
1459 
1460         if (!doneVisiting)
1461         {
1462           doneVisiting = _visitStampedColumnFacets(visitContext, callback);
1463 
1464           // visit the stamped children
1465           if (!doneVisiting)
1466           {
1467             doneVisiting = visitData(visitContext, callback);
1468           }
1469         }
1470       }
1471       finally
1472       {
1473         // restore the original rowIndex
1474         setRowIndex(oldRowIndex);
1475       }
1476 
1477       return doneVisiting;
1478     }
1479   }
1480 
1481   /**
1482    * Hook method for subclasses to override to change the behavior
1483    * of how unstamped facets of the UIXCollection are visited.  The
1484    * Default implementation visits all of the facets of the
1485    * UIXCollection.
1486    */
1487   protected boolean visitUnstampedFacets(
1488     VisitContext  visitContext,
1489     VisitCallback callback)
1490   {
1491     // Visit the facets with no row
1492     if (getFacetCount() > 0)
1493     {
1494       for (UIComponent facet : getFacets().values())
1495       {
1496         if (UIXComponent.visitTree(visitContext, facet, callback))
1497         {
1498           return true;
1499         }
1500       }
1501     }
1502 
1503     return false;
1504   }
1505 
1506 
1507   /**
1508    * VistiContext that visits the facets of the UIXColumn children, including
1509    * nested UIXColumn childrem
1510    */
1511   private static class ColumnFacetsOnlyVisitContext extends VisitContextWrapper
1512   {
1513     public ColumnFacetsOnlyVisitContext(VisitContext wrappedContext)
1514     {
1515       _wrapped = wrappedContext;
1516     }
1517 
1518     @Override
1519     public VisitContext getWrapped()
1520     {
1521       return _wrapped;
1522     }
1523 
1524     @Override
1525     public VisitResult invokeVisitCallback(UIComponent component, VisitCallback callback)
1526     {
1527       if (component instanceof UIXColumn)
1528       {
1529         if (component.getFacetCount() > 0)
1530         {
1531           // visit the facet children without filtering for just UIXColumn children
1532           for (UIComponent facetChild : component.getFacets().values())
1533           {
1534             if (UIXComponent.visitTree(getWrapped(), facetChild, callback))
1535               return VisitResult.COMPLETE;
1536           }
1537 
1538           // visit the indexed children, recursively looking for more columns
1539           for (UIComponent child : component.getChildren())
1540           {
1541             if (UIXComponent.visitTree(this, child, callback))
1542               return VisitResult.COMPLETE;
1543           }
1544         }
1545       }
1546 
1547       // at this point, we either have already manually processed the UIXColumn's children, or
1548       // the component wasn't a UIXColumn and shouldn't be processed
1549       return VisitResult.REJECT;
1550     }
1551 
1552     private final VisitContext _wrapped;
1553   }
1554 
1555   /**
1556    * VisitContext implementation that doesn't visit any of the Facets of
1557    * UIXColumn children.  This is used when stamping children
1558    */
1559   protected static final class NoColumnFacetsVisitContext extends VisitContextWrapper
1560   {
1561     NoColumnFacetsVisitContext(VisitContext wrapped)
1562     {
1563       _wrapped = wrapped;
1564     }
1565 
1566     @Override
1567     public VisitContext getWrapped()
1568     {
1569       return _wrapped;
1570     }
1571 
1572     @Override
1573     public VisitResult invokeVisitCallback(UIComponent component, VisitCallback callback)
1574     {
1575       if (component instanceof UIXColumn)
1576       {
1577         if (component.getChildCount() > 0)
1578         {
1579           // visit only the indexed children of the columns
1580           for (UIComponent child : component.getChildren())
1581           {
1582             if (UIXComponent.visitTree(this, child, callback))
1583               return VisitResult.COMPLETE;
1584           }
1585         }
1586 
1587         return VisitResult.REJECT;
1588       }
1589       else
1590       {
1591         // Components do not expect to be visited twice, in fact with UIXComponent, it is illegal.
1592         // This is due to the fact that UIXComponent has setup and tearDown methods for visiting.
1593         // In order to avoid having the setup method called for the current visit context and
1594         // the wrapped context we invoke the visit on the component and then separately on the
1595         // children of the component
1596         VisitContext wrappedContext = getWrapped();
1597         VisitResult visitResult = wrappedContext.invokeVisitCallback(component, callback);
1598 
1599         if (visitResult == VisitResult.ACCEPT)
1600         {
1601           // Let the visitation continue with the wrapped context
1602           return (UIXComponent.visitChildren(wrappedContext, component, callback)) ?
1603             VisitResult.COMPLETE : VisitResult.REJECT;
1604         }
1605         else
1606         {
1607             return visitResult;
1608         }
1609       }
1610     }
1611 
1612     private final VisitContext _wrapped;
1613   }
1614 
1615   /**
1616    * Implementation used to visit each stamped row
1617    */
1618   private boolean _visitStampedColumnFacets(
1619     VisitContext      visitContext,
1620     VisitCallback     callback)
1621   {
1622     // visit the facets of the stamped columns
1623     List<UIComponent> stamps = getStamps();
1624 
1625     if (!stamps.isEmpty())
1626     {
1627       VisitContext columnVisitingContext = new ColumnFacetsOnlyVisitContext(visitContext);
1628 
1629       for (UIComponent stamp : stamps)
1630       {
1631         if (UIXComponent.visitTree(columnVisitingContext, stamp, callback))
1632         {
1633           return true;
1634         }
1635       }
1636     }
1637 
1638     return false;
1639   }
1640 
1641 
1642   /**
1643    * Visit the rows and children of the columns of the collection per row-index. This should
1644    * not visit row index -1 (it will be perfomed in the visitTree method). The columns
1645    * themselves should not be visited, only their children in this function.
1646    *
1647    * @param visitContext The visiting context
1648    * @param callback The visit callback
1649    * @return true if the visiting should stop
1650    * @see #visitChildren(VisitContext, VisitCallback)
1651    */
1652   protected abstract boolean visitData(
1653     VisitContext  visitContext,
1654     VisitCallback callback);
1655 
1656   /**
1657    * Gets the CollectionModel to use with this component.
1658    *
1659    * @param createIfNull  creates the collection model if necessary
1660    */
1661   protected final CollectionModel getCollectionModel(
1662     boolean createIfNull)
1663   {
1664     InternalState iState = _getInternalState(true);
1665     if (iState._model == null && createIfNull)
1666     {
1667       //  _init() is usually called from either processDecodes or encodeBegin.
1668       //  Sometimes both processDecodes and encodeBegin may not be called,
1669       //  but processSaveState is called (this happens when
1670       //  component's rendered attr is set to false). We need to make sure that
1671       //  _init() is called in that case as well. Otherwise we get nasty NPEs.
1672       //  safest place is to call it here:
1673       _init();
1674 
1675       iState._value = getValue();
1676       iState._model = createCollectionModel(null, iState._value);
1677       postCreateCollectionModel(iState._model);
1678       assert iState._model != null;
1679     }
1680     // model might not have been created if createIfNull is false:
1681     if ((iState._initialStampStateKey == _NULL) &&
1682         (iState._model != null))
1683     {
1684       // if we have not already initialized the initialStampStateKey
1685       // that means that we don't have any stamp-state to use as the default
1686       // state for rows that we have not seen yet. So...
1687       // we will use any stamp-state for the initial rowKey on the model
1688       // as the default stamp-state for all rows:
1689       iState._initialStampStateKey = iState._model.getRowKey();
1690     }
1691     return iState._model;
1692   }
1693 
1694   /**
1695    * Creates the CollectionModel to use with this component.
1696    * The state of the UIComponent with the new model instance is not fully initialized until
1697    * after this method returns. As a result,  other component attributes that need
1698    * a fully initialized model should not be initialized in this method.  Instead,
1699    * model-dependent initialization should be done in <code>postCreateCollectionModel</code>
1700    * @see #postCreateCollectionModel
1701    * @param current the current CollectionModel, or null if there is none.
1702    * @param value this is the value returned from {@link #getValue()}
1703    */
1704   protected abstract CollectionModel createCollectionModel(
1705     CollectionModel current,
1706     Object value);
1707 
1708   /**
1709     * Hook called with the result of <code>createCollectionModel</code>.
1710     * Subclasses can use this method to perform initialization after the CollectionModel
1711     * is fully initialized.
1712     * Subclassers should call super before accessing any component state to ensure
1713     * that superclass initialization has been performed.
1714     * @see #createCollectionModel
1715     * @param model The model instance returned by<code><createCollectionModel</code>
1716     */
1717   protected void postCreateCollectionModel(CollectionModel model)
1718   {
1719     // do nothing
1720   }
1721 
1722 
1723   /**
1724    * Gets the value that must be converted into a CollectionModel
1725    */
1726   protected abstract Object getValue();
1727 
1728   /**
1729    * Gets the Map to use as the "varStatus".
1730    * This implementation supports the following keys:<ul>
1731    * <li>model - returns the CollectionModel
1732    * <li>index - returns the current rowIndex
1733    * <li>rowKey - returns the current rowKey
1734    * <li>current - returns the current rowData
1735    * <li>"hierarchicalIndex" - returns an array containing zero based row index.</li>
1736    * <li>"hierarchicalLabel" - returns a string label representing 1 based index of this row.</li>
1737    * </ul>
1738    */
1739   protected Map<String, Object> createVarStatusMap()
1740   {
1741     return new AbstractMap<String, Object>()
1742     {
1743       @Override
1744       public Object get(Object key)
1745       {
1746         // some of these keys are from <c:forEach>, ie:
1747         // javax.servlet.jsp.jstl.core.LoopTagStatus
1748         if ("model".equals(key))
1749           return getCollectionModel();
1750         if ("rowKey".equals(key))
1751           return getRowKey();
1752         if ("index".equals(key)) // from jstl
1753           return Integer.valueOf(getRowIndex());
1754         if("hierarchicalIndex".equals(key))
1755         {
1756           int rowIndex = getRowIndex();
1757           return rowIndex>=0 ? new Integer[]{rowIndex}: new Integer[]{};
1758         }
1759         if("hierarchicalLabel".equals(key))
1760         {
1761           int rowIndex = getRowIndex();
1762           return rowIndex>=0 ? Integer.toString(rowIndex+1): "";
1763         }
1764         if ("current".equals(key)) // from jstl
1765           return getRowData();
1766         return null;
1767       }
1768 
1769       @Override
1770       public Set<Map.Entry<String, Object>> entrySet()
1771       {
1772         return Collections.emptySet();
1773       }
1774     };
1775   }
1776 
1777 
1778   //
1779   // LocalRowKeyIndex implementation
1780   //
1781 
1782   /**
1783    * Given a row index, check if a row is locally available
1784    * @param rowIndex index of row to check
1785    * @return true if row is locally available
1786    */
1787   public boolean isRowLocallyAvailable(int rowIndex)
1788   {
1789     return getCollectionModel().isRowLocallyAvailable(rowIndex);
1790   }
1791 
1792   /**
1793    * Given a row key, check if a row is locally available
1794    * @param rowKey row key for the row to check
1795    * @return true if row is locally available
1796    */
1797   public boolean isRowLocallyAvailable(Object rowKey)
1798   {
1799     return getCollectionModel().isRowLocallyAvailable(rowKey);
1800   }
1801 
1802   /**
1803    * Check if a range of rows is locally available starting from current position
1804    * @param rowCount number of rows in the range
1805    * @return true if range of rows is locally available
1806    */
1807   public boolean areRowsLocallyAvailable(int rowCount)
1808   {
1809     return getCollectionModel().areRowsLocallyAvailable(rowCount);
1810   }
1811 
1812   /**
1813    * Check if a range of rows is locally available starting from a row index
1814    * @param startIndex staring index for the range
1815    * @param rowCount number of rows in the range
1816    * @return true if range of rows is locally available
1817    */
1818   public boolean areRowsLocallyAvailable(int startIndex, int rowCount)
1819   {
1820     return getCollectionModel().areRowsLocallyAvailable(startIndex, rowCount);
1821   }
1822 
1823   /**
1824    * Check if a range of rows is locally available starting from a row key
1825    * @param startRowKey staring row key for the range
1826    * @param rowCount number of rows in the range
1827    * @return true if range of rows is locally available
1828    */
1829   public boolean areRowsLocallyAvailable(Object startRowKey, int rowCount)
1830   {
1831     return getCollectionModel().areRowsLocallyAvailable(startRowKey, rowCount);
1832   }
1833 
1834   /**
1835    * Convenient API to return a row count estimate.  This method can be optimized
1836    * to avoid a data fetch which may be required to return an exact row count
1837    * @return estimated row count
1838    */
1839   public int getEstimatedRowCount()
1840   {
1841     return getCollectionModel().getEstimatedRowCount();
1842   }
1843 
1844 
1845   /**
1846    * Helper API to determine if the row count returned from {@link #getEstimatedRowCount}
1847    * is EXACT, or an ESTIMATE
1848    */
1849   public LocalRowKeyIndex.Confidence getEstimatedRowCountConfidence()
1850   {
1851     return getCollectionModel().getEstimatedRowCountConfidence();
1852   }
1853 
1854   /**
1855    * clear all rows from the local cache
1856    */
1857   public void clearLocalCache()
1858   {
1859     getCollectionModel().clearLocalCache();
1860   }
1861 
1862   /**
1863    * Clear the requested range of rows from the local cache
1864    * @param startingIndex starting row index for the range to clear
1865    * @param rowsToClear number of rows to clear from the cache
1866    */
1867   public void clearCachedRows(int startingIndex,  int rowsToClear)
1868   {
1869     getCollectionModel().clearCachedRows(startingIndex, rowsToClear);
1870   }
1871 
1872   /**
1873    * Clear the requested range of rows from the local cache
1874    * @param startingRowKey starting row key for the range to clear
1875    * @param rowsToClear number of rows to clear from the cache
1876    */
1877   public void clearCachedRows(Object startingRowKey, int rowsToClear)
1878   {
1879     getCollectionModel().clearCachedRows(startingRowKey, rowsToClear);
1880   }
1881 
1882   /**
1883    * Clear a row from the local cache by row index
1884    * @param index row index for the row to clear from the cache
1885    */
1886   public void clearCachedRow(int index)
1887   {
1888     getCollectionModel().clearCachedRow(index);
1889   }
1890 
1891   /**
1892    * Clear a row from the local cache by row key
1893    * @param rowKey row key for the row to clear from the cache
1894    */
1895   public void clearCachedRow(Object rowKey)
1896   {
1897     getCollectionModel().clearCachedRow(rowKey);
1898   }
1899 
1900   /**
1901    * Indicates the caching strategy supported by the model
1902    * @see LocalRowKeyIndex.LocalCachingStrategy
1903    * @return caching strategy supported by the model
1904    */
1905   public LocalRowKeyIndex.LocalCachingStrategy getCachingStrategy()
1906   {
1907     return getCollectionModel().getCachingStrategy();
1908   }
1909 
1910   /**
1911    * Ensure that the model has at least rowCount number of rows.
1912    *
1913    * @param rowCount the number of rows the model should hold.
1914    */
1915   public void ensureRowsAvailable(int rowCount)
1916   {
1917     getCollectionModel().ensureRowsAvailable(rowCount);
1918   }
1919 
1920   /**
1921    * override this method to place initialization code that must run
1922    * once this component is created and the jsp engine has finished setting
1923    * attributes on it.
1924    */
1925   void __init()
1926   {
1927     InternalState iState = _getInternalState(true);
1928     iState._var = getVar();
1929     if (_LOG.isFine() && (iState._var == null))
1930     {
1931       _LOG.fine("'var' attribute is null.");
1932     }
1933     iState._varStatus = getVarStatus();
1934     if (_LOG.isFinest() && (iState._varStatus == null))
1935     {
1936       _LOG.finest("'varStatus' attribute is null.");
1937     }
1938  }
1939 
1940   /**
1941    * Hook for subclasses like UIXIterator to initialize and flush the cache when visting flattened
1942    * children when parented by a renderer that needs to use
1943    * UIXComponent.processFlattenedChildren().
1944    * This is to mimic what happens in the non flattening case where similar logic is invoked
1945    * during encodeBegin().
1946    */
1947   protected void processFlattenedChildrenBegin(ComponentProcessingContext cpContext)
1948   {
1949     // Call _init() since __flushCachedModel() assumes that
1950     // selectedRowKeys and disclosedRowKeys are initialized to be non-null.
1951     _init();
1952     __flushCachedModel();
1953   }
1954 
1955   private void _init()
1956   {
1957     InternalState iState = _getInternalState(true);
1958     if (!iState._isInitialized)
1959     {
1960       assert iState._model == null;
1961       iState._isInitialized = true;
1962       __init();
1963     }
1964   }
1965 
1966   void __flushCachedModel()
1967   {
1968     InternalState iState = _getInternalState(true);
1969     Object value = getValue();
1970     if (iState._value != value)
1971     {
1972       CollectionModel oldModel = iState._model;
1973       iState._value = value;
1974       iState._model = createCollectionModel(iState._model, value);
1975       postCreateCollectionModel(iState._model);
1976 
1977       // if the underlying model is changed, we need to remove 
1978       // the listener from the old model. And if we still have cached
1979       // rowkey, we need to add the listener back to the new model.
1980 
1981       if (oldModel != iState._model)
1982       {
1983         if (oldModel != null)
1984         {
1985           oldModel.removeRowKeyChangeListener(iState);
1986         }
1987 
1988         if (iState._currentRowKey != _NULL)
1989         {
1990           iState._model.addRowKeyChangeListener(iState);
1991         }
1992       }
1993     }
1994   }
1995 
1996   //
1997   // Returns true if this is the first request to invokeOnComponent()
1998   //
1999   static private boolean _getAndMarkFirstInvokeForRequest(
2000     FacesContext context, String clientId)
2001   {
2002     // See if the request contains a marker that we've hit this
2003     // method already for this clientId
2004     Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
2005     String key = _INVOKE_KEY + clientId;
2006     // Yep, we have, so return true
2007     if (requestMap.containsKey(key))
2008       return true;
2009 
2010     // Stash TRUE for next time, and return false
2011     requestMap.put(key, Boolean.TRUE);
2012     return false;
2013   }
2014 
2015   /**
2016    * Gets the internal state of this component.
2017    * This is to support table within table.
2018    */
2019   Object __getMyStampState()
2020   {
2021     return _state;
2022   }
2023 
2024   /**
2025    * Sets the internal state of this component.
2026    * This is to support table within table.
2027    * @param stampState the internal state is obtained from this object.
2028    */
2029   void __setMyStampState(Object stampState)
2030   {
2031     InternalState iState = (InternalState) stampState;
2032     _state = iState;
2033   }
2034 
2035   /**
2036    * reset the stamp state to pristine state. This pristine state when saved to the outer collection for null currency
2037    * will allow stamp state for UIXCollection with individual rows to be created
2038    *
2039    * This is to support iteration of children(column stamping) within the table.
2040    */
2041   void __resetMyStampState()
2042   {
2043     _state = null;
2044   }
2045 
2046   /**
2047    * Returns true if an event (other than a selection event)
2048    * has been queued for this component.  This is a hack
2049    * to support validation in the tableSelectXyz components.
2050    */
2051   boolean __hasEvent()
2052   {
2053     InternalState iState = _getInternalState(true);
2054     return iState._hasEvent;
2055   }
2056 
2057   /**
2058    * Saves the state of all the stamps of this component.
2059    * This method should be called before the rowData of this component
2060    * changes. This method gets all the stamps using {@link #getStamps} and
2061    * saves their states by calling {@link #saveStampState}.
2062    */
2063   private void _saveStampState()
2064   {
2065     // Never read and created by _getStampState
2066     //InternalState iState = _getInternalState(true);
2067 
2068     StampState stampState = _getStampState();
2069     Map<String, String> idToIndexMap = _getIdToIndexMap();
2070     FacesContext context = getFacesContext();
2071     Object currencyObj = getRowKey();
2072 
2073     // Note: even though the currencyObj may be null, we still need to save the state. The reason
2074     // is that the code does not clear out the state when it is saved, instead, the un-stamped
2075     // state is saved. Once the row key is set back to null, this un-stamped state is restored
2076     // onto the children components. This restoration allows editable value holders, show detail
2077     // items and nested UIXCollections to clear their state.
2078     // For nested UIXCollections, this un-stamped state is required to set the nested collection's
2079     // _state (internal state containing the stamp state) to null when not on a row key. Without
2080     // that call, the nested UIXCollection components would end up sharing the same stamp state
2081     // across parent rows.
2082 
2083     for (UIComponent stamp : getStamps())
2084     {
2085       Object state = saveStampState(context, stamp);
2086 //      String stampId = stamp.getId();
2087       // TODO
2088       // temporarily use position. later we need to use ID's to access
2089       // stamp state everywhere, and special case NamingContainers:
2090       String compId = stamp.getId();
2091       String stampId = idToIndexMap.get(compId);
2092       if (stampId == null)
2093       {
2094         stampId = String.valueOf(idToIndexMap.size());
2095         idToIndexMap.put(compId, stampId);
2096       }
2097       stampState.put(currencyObj, stampId, state);
2098       if (_LOG.isFinest())
2099         _LOG.finest("saving stamp state for currencyObject:"+currencyObj+
2100           " and stampId:"+stampId);
2101     }
2102   }
2103 
2104 
2105   /**
2106    * Restores the state of all the stamps of this component.
2107    * This method should be called after the currency of this component
2108    * changes. This method gets all the stamps using {@link #getStamps} and
2109    * restores their states by calling
2110    * {@link #restoreStampState}.
2111    */
2112   private void _restoreStampState()
2113   {
2114     StampState stampState = _getStampState();
2115     Map<String, String> idToIndexMap = _getIdToIndexMap();
2116     FacesContext context = getFacesContext();
2117     Object currencyObj = getRowKey();
2118     for(UIComponent stamp : getStamps())
2119     {
2120 //      String stampId = stamp.getId();
2121       // TODO
2122       // temporarily use position. later we need to use ID's to access
2123       // stamp state everywhere, and special case NamingContainers:
2124       String compId = stamp.getId();
2125       String stampId = idToIndexMap.get(compId);
2126       Object state = stampState.get(currencyObj, stampId);
2127       if (state == null)
2128       {
2129         Object iniStateObj = _getCurrencyKeyForInitialStampState();
2130         state = stampState.get(iniStateObj, stampId);
2131         /*
2132         if (state==null)
2133         {
2134           _LOG.severe("NO_INITIAL_STAMP_STATE", new Object[]{currencyObj,iniStateObj,stampId});
2135           continue;
2136         }*/
2137       }
2138       restoreStampState(context, stamp, state);
2139     }
2140   }
2141 
2142   private Map<String, String> _getIdToIndexMap()
2143   {
2144     InternalState iState = _getInternalState(true);
2145     if (iState._idToIndexMap == null)
2146       iState._idToIndexMap = new HashMap<String, String>();
2147     return iState._idToIndexMap;
2148   }
2149 
2150   private InternalState _getInternalState(boolean create)
2151   {
2152     if ((_state == null) && create)
2153     {
2154       _state = new InternalState();
2155     }
2156     return _state;
2157   }
2158 
2159   private StampState _getStampState()
2160   {
2161     InternalState iState = _getInternalState(true);
2162     if (iState._stampState == null)
2163       iState._stampState = new StampState();
2164 
2165     return iState._stampState;
2166   }
2167 
2168   /**
2169    * sets an EL variable.
2170    * @param varName the name of the variable
2171    * @param newData the value of the variable
2172    * @return the old value of the variable, or null if there was no old value.
2173    */
2174   private Object _setELVar(String varName, Object newData)
2175   {
2176     if (varName == null)
2177       return null;
2178 
2179     // we need to place each row at an EL reachable place so that it
2180     // can be accessed via the 'var' variable. Let's place it on the
2181     // requestMap:
2182     return setupELVariable(getFacesContext(), varName, newData);
2183   }
2184 
2185   /**
2186    * Called by UIXCollection to set values for the "var" and
2187    * "varStatus" EL variables.
2188    *
2189    * @param context the FacesContext for the current request
2190    * @param name the non-null name of the EL variable
2191    * @param value the value of the EL variable
2192    * @return the previous value of the EL variable, or null if
2193    *         the value was not previously set.
2194    */
2195   protected Object setupELVariable(
2196     FacesContext context,
2197     String       name,
2198     Object       value
2199     )
2200   {
2201     Args.notNull(name, "name");
2202     return TableUtils.setupELVariable(context, name, value);
2203   }
2204 
2205   private static boolean _equals(Object a, Object b)
2206   {
2207     if (b == null)
2208       return (a == null);
2209 
2210     return b.equals(a);
2211   }
2212 
2213   private void _setupContextChange()
2214   {
2215     if (_inSuspendOrResume)
2216     {
2217       // This situation will occur when the CollectionComponentChange is currently setting the
2218       // row key.
2219       return;
2220     }
2221 
2222     ComponentContextManager compCtxMgr =
2223       RequestContext.getCurrentInstance().getComponentContextManager();
2224 
2225     compCtxMgr.pushChange(new CollectionComponentChange(this));
2226 
2227     _inContext = true;
2228   }
2229 
2230   private void _tearDownContextChange()
2231   {
2232     if (_inSuspendOrResume)
2233     {
2234       // This situation will occur when the CollectionComponentChange is currently setting the
2235       // row key.
2236       return;
2237     }
2238 
2239     try
2240     {
2241       ComponentContextManager compCtxMgr =
2242         RequestContext.getCurrentInstance().getComponentContextManager();
2243       ComponentContextChange change = compCtxMgr.peekChange();
2244 
2245       if (change instanceof CollectionComponentChange &&
2246           ((CollectionComponentChange)change)._component == this)
2247       {
2248         // Remove the component context change if one was added
2249         compCtxMgr.popChange();
2250       }
2251       else
2252       {
2253         _LOG.severe("COLLECTION_CHANGE_TEARDOWN", new Object[] { getId(), change });
2254       }
2255     }
2256     catch (RuntimeException re)
2257     {
2258       _LOG.severe(re);
2259     }
2260     finally
2261     {
2262       _inContext = false;
2263     }
2264   }
2265 
2266   private void _verifyComponentInContext()
2267   {
2268     if (_inSuspendOrResume)
2269     {
2270       return;
2271     }
2272 
2273     if (!_inContext)
2274     {
2275       if (_LOG.isWarning())
2276       {
2277         _LOG.warning("COLLECTION_NOT_IN_CONTEXT",
2278           new Object[] { getParent() == null ? getId() : getClientId() });
2279         if (_LOG.isFine())
2280         {
2281           Thread.currentThread().dumpStack();
2282         }
2283       }
2284     }
2285   }
2286 
2287   /**
2288    * during state saving, we want to reset the currency to null, but we want to
2289    * remember the current currency, so that after state saving, we can set it back
2290    *
2291    * @param context faces context
2292    * @return the currency key
2293    */
2294   private Object _resetCurrencyKeyForStateSaving(FacesContext context)
2295   {
2296     // If we saved state in the middle of processing a row,
2297     // then make sure that we revert to a "null" rowKey while
2298     // saving state;  this is necessary to ensure that the
2299     // current row's state is properly preserved, and that
2300     // the children are reset to their default state.
2301     Object currencyKey = _getCurrencyKey();
2302 
2303     // since this is the end of the request, we expect the row currency to be reset back to null
2304     // setting it and leaving it there might introduce multiple issues, so log a warning here
2305     if (currencyKey != null)
2306     {
2307       if (_LOG.isWarning())
2308       {
2309         String scopedId = ComponentUtils.getScopedIdForComponent(this, context.getViewRoot());
2310         String viewId = context.getViewRoot() == null? null: context.getViewRoot().getViewId();
2311         _LOG.warning("ROWKEY_NOT_RESET", new Object[]
2312             { scopedId, viewId });
2313       }
2314     }
2315 
2316     Object initKey = _getCurrencyKeyForInitialStampState();
2317     if (currencyKey != initKey) // beware of null currencyKeys if equals() is used
2318     {
2319       setRowKey(initKey);
2320     }
2321 
2322     return currencyKey;
2323   }
2324 
2325   /**
2326    * restore the currency key after state saving
2327    *
2328    * @param key the currency key
2329    */
2330   private void _restoreCurrencyKeyForStateSaving(Object key)
2331   {
2332     Object currencyKey = key;
2333     Object initKey = _getCurrencyKeyForInitialStampState();
2334 
2335     if (currencyKey != initKey) // beware of null currencyKeys if equals() is used
2336     {
2337       setRowKey(currencyKey);
2338     }
2339   }
2340 
2341   /**
2342    * clean up any internal model state that we might be holding on to.
2343    */
2344   private void _resetInternalState()
2345   {
2346     InternalState iState = _getInternalState(false);
2347     if (iState != null)
2348     {
2349       iState._value = null;
2350 
2351       if (iState._model != null)
2352       {
2353         iState._model.removeRowKeyChangeListener(iState);
2354         iState._model = null;
2355       }
2356     }
2357   }
2358 
2359   private static final class DefaultClientKeyManager extends ClientRowKeyManager
2360   {
2361     public void clear()
2362     {
2363       _currencyCache.clear();
2364     }
2365 
2366     /**
2367      * {@inheritDoc}
2368      */
2369     @Override
2370     public Object getRowKey(FacesContext context, UIComponent component, String clientRowKey)
2371     {
2372       ValueMap<Object,String> currencyCache = _currencyCache;
2373       Object rowkey = currencyCache.getKey(clientRowKey);
2374       return rowkey;
2375     }
2376 
2377     /**
2378      * {@inheritDoc}
2379      */
2380     @Override
2381     public String getClientRowKey(FacesContext context, UIComponent component, Object rowKey)
2382     {
2383       assert rowKey != null;
2384 
2385       ValueMap<Object,String> currencyCache = _currencyCache;
2386       String key = currencyCache.get(rowKey);
2387       // check to see if we already have a string key:
2388       if (key == null)
2389       {
2390         // we don't have a string-key, so create a new one.
2391         key = _createToken(currencyCache);
2392 
2393         if (_LOG.isFiner())
2394           _LOG.finer("Storing token:"+key+
2395                      " for rowKey:"+rowKey);
2396 
2397         currencyCache.put(rowKey, key);
2398       }
2399       return key;
2400     }
2401 
2402     /**
2403      * {@inheritDoc}
2404      */
2405     @Override
2406     public boolean replaceRowKey(FacesContext context, UIComponent component, Object oldRowKey, Object newRowKey)
2407     {
2408       assert oldRowKey != null && newRowKey != null;
2409 
2410       ValueMap<Object,String> currencyCache = _currencyCache;
2411       String key = currencyCache.remove(oldRowKey);
2412       // check to see if we already have a string key:
2413       if (key != null)
2414       {
2415         currencyCache.put(newRowKey, key);
2416       }
2417       return key != null;
2418     }
2419 
2420 
2421 
2422     private static String _createToken(ValueMap<Object,String> currencyCache)
2423     {
2424       String key = String.valueOf(currencyCache.size());
2425       return key;
2426     }
2427 
2428     private ValueMap<Object,String> _currencyCache = new ValueMap<Object,String>();
2429     private static final long serialVersionUID = 1L;
2430   }
2431 
2432   // this component's internal state is stored in an inner class
2433   // rather than in individual fields, because we want to make it
2434   // easy to quickly suck out or restore its internal state,
2435   // when this component is itself used as a stamp inside some other
2436   // stamping container, eg: nested tables.
2437   private static final class InternalState implements RowKeyChangeListener, Serializable
2438   {
2439     private transient boolean _hasEvent = false;
2440     private transient Object _prevVarValue = _NULL;
2441     private transient Object _prevVarStatus = _NULL;
2442     private transient String _var = null;
2443     private transient String _varStatus = null;
2444     private transient Object _value = null;
2445     private transient CollectionModel _model = null;
2446     private transient Object _currentRowKey = _NULL;
2447     private transient boolean _clearTokenCache = false;
2448     // this is true if this is the first request for this viewID and processDecodes
2449     // was not called:
2450     private transient boolean _isFirstRender = true;
2451     private transient boolean _isInitialized = false;
2452     // this is the rowKey used to retrieve the default stamp-state for all rows:
2453     private transient Object _initialStampStateKey = _NULL;
2454 
2455     private ClientRowKeyManager _clientKeyMgr = null;
2456     private StampState _stampState = null;
2457 
2458     // map from column id to the index within the collection. The index is used to
2459     // save/look up for column's stamp state. The long term goal is to use id as key
2460     // to the stamp state map, but for this release, we add this id-to-index map
2461     // to indirectly loop up a stamp state for a column, so if the position of the column
2462     // changes in the middle, we'll still be able to find the right stamp state.
2463     private Map<String, String> _idToIndexMap = null;
2464 
2465     public void onRowKeyChange(RowKeyChangeEvent rowKeyChangeEvent)
2466     {
2467       Object newKey = rowKeyChangeEvent.getNewRowKey();
2468       Object oldKey = rowKeyChangeEvent.getOldRowKey();
2469 
2470       if (newKey != null && oldKey != null && !newKey.equals(oldKey))
2471       {
2472         // first replace the cached row key
2473         if (oldKey.equals(_currentRowKey))
2474           _currentRowKey = newKey;
2475 
2476         // then update stamp state for the affected entries.
2477         if (_stampState == null || _idToIndexMap == null)
2478           return;
2479 
2480         int stampCompCount = _idToIndexMap.size();
2481 
2482         for (int index = 0; index < stampCompCount; index++)
2483         {
2484           String stampId = String.valueOf(index);
2485           Object state = _stampState.get(oldKey, stampId);
2486           if (state == null)
2487             continue;
2488           _stampState.put(oldKey, stampId, null);
2489           _stampState.put(newKey, stampId, state);
2490         }
2491       }
2492     }
2493 
2494     private void readObject(ObjectInputStream in)
2495        throws IOException, ClassNotFoundException
2496     {
2497       in.defaultReadObject();
2498       // Set values of all transients to their defaults
2499       _prevVarValue = _NULL;
2500       _prevVarStatus = _NULL;
2501       _currentRowKey = _NULL;
2502       _initialStampStateKey = _NULL;
2503       // However, leave _isFirstRender set to false - since that's
2504       // necessarily the state we'd be in if we're reconstituting this
2505       _isFirstRender = false;
2506     }
2507 
2508     private static final long serialVersionUID = 1L;
2509   }
2510 
2511   /**
2512    * Class to be able to suspend the context of the collection.
2513    * <p>Current implementation removes the var and varStatus from the request while the
2514    * collection is suspended.</p>
2515    */
2516   private static class CollectionComponentChange
2517     extends ComponentContextChange
2518   {
2519     private CollectionComponentChange(
2520       UIXCollection component)
2521     {
2522       _component = component;
2523     }
2524 
2525     public void suspend(
2526       FacesContext facesContext)
2527     {
2528       _component._inSuspendOrResume = true;
2529 
2530       try
2531       {
2532         InternalState iState = _component._getInternalState(false);
2533         if (iState == null || iState._model == null || iState._currentRowKey == _NULL)
2534         {
2535           // If we were to try to call getRowKey() here, this would call getCollectionModel().
2536           // The get collection model may result in EL being evaluated, which is undesirable
2537           // and will cause bugs when called while we are suspending. This is because evaluating
2538           // EL may need to suspend or resume other component context changes, and we do not want
2539           // re-entrant calls to the component context stack while we are already suspending.
2540 
2541           // Note that this code will fail if someone has set the _model to null while on a rowKey
2542           // (Should not happen, would be considered a bug if that were to be done).
2543           _rowKey = null;
2544         }
2545         else
2546         {
2547           _rowKey = _component.getRowKey();
2548 
2549           // Set the row key back to null to force the collection into the un-stamped state. This
2550           // will ensure that the collection is not in a row key while the component context is
2551           // not setup. Only do this if the row key is not already on the null row key.
2552           if (_rowKey != null)
2553           {
2554             _component.setRowKey(null);
2555           }
2556         }
2557 
2558         _component._inContext = false;
2559       }
2560       finally
2561       {
2562         _component._inSuspendOrResume = false;
2563       }
2564     }
2565 
2566     public void resume(
2567       FacesContext facesContext)
2568     {
2569       _component._inSuspendOrResume = true;
2570       try
2571       {
2572         // Only set the row key if one was stored during the suspend.
2573         if (_rowKey != null)
2574         {
2575           _component.setRowKey(_rowKey);
2576         }
2577 
2578         _component._inContext = true;
2579       }
2580       finally
2581       {
2582         _component._inSuspendOrResume = false;
2583       }
2584     }
2585 
2586     @Override
2587     public String toString()
2588     {
2589       String className = _component.getClass().getName();
2590       String componentId = _component.getId();
2591       return new StringBuilder(58 + className.length() + componentId.length())
2592         .append("UIXCollection.CollectionComponentChange[Component class: ")
2593         .append(className)
2594         .append(", component ID: ")
2595         .append(componentId)
2596         .append("]")
2597         .toString();
2598     }
2599 
2600     private final UIXCollection _component;
2601     private CollectionModel _collectionModel;
2602     private Object _rowKey;
2603   }
2604 
2605   private static class CollectionContextEvent
2606     extends WrapperEvent
2607   {
2608     public CollectionContextEvent(
2609       UIComponent source,
2610       FacesEvent  event)
2611     {
2612       super(source, event);
2613     }
2614 
2615     @SuppressWarnings("compatibility:-7639429485707197863")
2616     private static final long serialVersionUID = 1L;
2617   }
2618 
2619   // do not assign a non-null value. values should be assigned lazily. this is
2620   // because this value is preserved by stampStateSaving, when table-within-table
2621   // is used. And if a non-null value is used, then all nested tables will
2622   // end up sharing this stampState. see bug 4279735:
2623   private InternalState _state = null;
2624   private boolean _inSuspendOrResume = false;
2625   private boolean _inContext = false;
2626 
2627   // use this key to indicate uninitialized state.
2628   // all the variables that use this are transient so this object need not
2629   // be Serializable:
2630   private static final Object _NULL = new Object();
2631   private static final String _INVOKE_KEY =
2632     UIXCollection.class.getName() + ".INVOKE";
2633 
2634   private transient Object _stateSavingCurrencyKey = null;
2635 
2636   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(UIXCollection.class);
2637 
2638   // An enum to throw into state-saving so that we get a nice
2639   // instance-equality to test against for noticing transient components
2640   // (and better serialization results)
2641   // We need this instead of just using null - because transient components
2642   // are specially handled, since they may or may not actually still
2643   // be there when you go to restore state later (e.g., on the next request!)
2644   enum Transient { TRUE };
2645 }