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