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