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.component.NamingContainer;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  import javax.faces.event.AbortProcessingException;
35  import javax.faces.event.FacesEvent;
36  import javax.faces.event.PhaseId;
37  import javax.faces.render.Renderer;
38  
39  import org.apache.myfaces.trinidad.bean.FacesBean;
40  import org.apache.myfaces.trinidad.bean.PropertyKey;
41  import org.apache.myfaces.trinidad.event.SelectionEvent;
42  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
43  import org.apache.myfaces.trinidad.model.CollectionModel;
44  import org.apache.myfaces.trinidad.model.SortCriterion;
45  import org.apache.myfaces.trinidad.render.ClientRowKeyManager;
46  import org.apache.myfaces.trinidad.render.ClientRowKeyManagerFactory;
47  import org.apache.myfaces.trinidad.util.ComponentUtils;
48  
49  
50  /**
51   * Base class for components that do stamping.
52   * This class set the EL 'var' variable correctly when the rowData changes.
53   * And it wraps events that are queued, so that the correct rowData can be
54   * restored on this component when the event is broadcast.
55   */
56  public abstract class UIXCollection extends UIXComponentBase
57    implements NamingContainer
58  {
59    static public final FacesBean.Type TYPE = new FacesBean.Type(
60      UIXComponentBase.TYPE);
61    static public final PropertyKey VAR_KEY =
62      TYPE.registerKey("var", String.class, PropertyKey.CAP_NOT_BOUND);
63  
64    protected UIXCollection(String rendererType)
65    {
66      super(rendererType);
67    }
68  
69    protected UIXCollection()
70    {
71      this(null);
72    }
73  
74    /**
75     * Gets the name of the EL variable used to reference each element of
76     * this collection.  Once this component has completed rendering, this
77     * variable is removed (or reverted back to its previous value).
78     */
79    final public String getVar()
80    {
81      return ComponentUtils.resolveString(getProperty(VAR_KEY));
82    }
83  
84    /**
85     * Sets the name of the EL variable used to reference each element of
86     * this collection.  Once this component has completed rendering, this
87     * variable is removed (or reverted back to its previous value).
88     */
89    final public void setVar(String var)
90    {
91      setProperty(VAR_KEY, (var));
92      InternalState iState = _getInternalState(false);
93      if (iState != null)
94      {
95        iState._var = var;
96      }
97    }
98  
99    /**
100    * Queues an event. If there is a currency set on this table, then
101    * the event will be wrapped so that when it is finally delivered, the correct
102    * currency will be restored.
103    * @param event a FacesEvent
104    */
105   @Override
106   public void queueEvent(FacesEvent event)
107   {
108     if (event.getSource() == this)
109     {
110       // Remember non-SelectionEvents on ourselves.  This
111       // is a hack to support validation in tableSelectXyz.
112       if (!(event instanceof SelectionEvent))
113       {
114         InternalState iState = _getInternalState(true);
115         iState._hasEvent = true;
116       }
117     }
118 
119     // we want to wrap up
120     // the event so we can execute it in the correct context (with the correct
121     // rowKey/rowData):
122     Object currencyKey = getRowKey();
123     event = new TableRowEvent(this, event, currencyKey);
124     super.queueEvent(event);
125   }
126 
127   /**
128    * Delivers a wrapped event to the appropriate component.
129    * If the event is a special wrapped event, it is unwrapped.
130    * @param event a FacesEvent
131    * @throws javax.faces.event.AbortProcessingException
132    */
133   @Override
134   public void broadcast(FacesEvent event)
135     throws AbortProcessingException
136   {
137     // For "TableRowEvents", set up the data before firing the
138     // event to the actual component.
139     if (event instanceof TableRowEvent)
140     {
141       TableRowEvent rowEvent = (TableRowEvent) event;
142       Object old = getRowKey();
143       setRowKey(rowEvent.getCurrencyKey());
144       FacesEvent wrapped = rowEvent.getEvent();
145       wrapped.getComponent().broadcast(wrapped);
146       setRowKey(old);
147     }
148     else
149     {
150       super.broadcast(event);
151     }
152   }
153 
154   /**
155    * Decodes this component before decoding the facets.
156    * Decodes the children as many times as they are stamped.
157    * @param context the FacesContext
158    */
159   @Override
160   public final void processDecodes(FacesContext context)
161   {
162     if (context == null)
163       throw new NullPointerException();
164 
165     _init();
166 
167     InternalState iState = _getInternalState(true);
168     iState._isFirstRender = false;
169 
170     if (!isRendered())
171       return;
172 
173     _flushCachedModel();
174 
175     // Make sure _hasEvent is false.
176     iState._hasEvent = false;
177 
178     // =-=AEW Because I'm getting the state in decode(), I need
179     // to do it before iterating over the children - otherwise,
180     // they'll be working off the wrong startIndex.  When state
181     // management is integrated, I can likely put this back in the
182     // usual order
183 
184     // Process this component itself
185     decode(context);
186 
187     // Process all facets and children of this component
188     decodeChildren(context);
189   }
190 
191   @Override
192   protected void decodeChildrenImpl(FacesContext context)
193   {
194     processFacetsAndChildren(context, PhaseId.APPLY_REQUEST_VALUES);
195   }
196 
197   @Override
198   protected void validateChildrenImpl(FacesContext context)
199   {
200     processFacetsAndChildren(context, PhaseId.PROCESS_VALIDATIONS);
201   }
202 
203   @Override
204   protected void updateChildrenImpl(FacesContext context)
205   {
206     processFacetsAndChildren(context, PhaseId.UPDATE_MODEL_VALUES);
207   }
208 
209   /**
210    * Resets this component's stamps to their
211    * uninitialized state. This is useful when the user wants to
212    * undo any edits made to an editable table.
213    */
214   public void resetStampState()
215   {
216     InternalState iState = _getInternalState(true);
217     // TODO: this is over kill. for eg, It clears out any toggled showDetails.
218     Object initKey = _getCurrencyKeyForInitialStampState();
219     // do not clear the initial stamp state: a subtle bug could
220     // result where the initial state of each component is gone, so we
221     // fail to roll back to the initial default values
222     if (iState._stampState != null)
223       iState._stampState.clear(initKey);
224   }
225 
226   @Override
227   public Object processSaveState(FacesContext context)
228   {
229     // If we saved state in the middle of processing a row,
230     // then make sure that we revert to a "null" rowKey while
231     // saving state;  this is necessary to ensure that the
232     // current row's state is properly preserved, and that
233     // the children are reset to their default state.
234     Object currencyKey = _getCurrencyKey();
235     Object initKey = _getCurrencyKeyForInitialStampState();
236     if (currencyKey != initKey) // beware of null currencyKeys if equals() is used
237     {
238       setRowKey(initKey);
239     }
240 
241     Object savedState = super.processSaveState(context);
242 
243     if (currencyKey != initKey) // beware of null currencyKeys if equals() is used
244     {
245       setRowKey(currencyKey);
246     }
247 
248     return savedState;
249   }
250 
251   @Override
252   public Object saveState(FacesContext context)
253   {
254     // _stampState is stored as an instance variable, so it isn't
255     // automatically saved
256     Object superState = super.saveState(context);
257     final Object stampState, clientKeyMgr;
258 
259     // becareful not to create the internal state too early:
260     // otherwise, the internal state will be shared between
261     // nested table stamps:
262     InternalState iState = _getInternalState(false);
263     if (iState != null)
264     {
265       stampState = iState._stampState;
266       clientKeyMgr = iState._clientKeyMgr;
267     }
268     else
269     {
270       stampState = null;
271       clientKeyMgr = null;
272     }
273 
274     if ((superState != null) || (stampState != null) || (clientKeyMgr != null))
275       return new Object[]{superState, stampState, clientKeyMgr};
276     return null;
277   }
278 
279 
280   @SuppressWarnings("unchecked")
281   @Override
282   public void restoreState(FacesContext context, Object state)
283   {
284     final Object superState, stampState, clientKeyMgr;
285     Object[] array = (Object[]) state;
286     if (array != null)
287     {
288       superState = array[0];
289       stampState = array[1];
290       clientKeyMgr = array[2];
291     }
292     else
293     {
294       superState = stampState = clientKeyMgr = null;
295     }
296     super.restoreState(context, superState);
297 
298     if ((stampState != null) || (clientKeyMgr != null))
299     {
300       InternalState iState = _getInternalState(true);
301       iState._stampState = (StampState) stampState;
302       iState._clientKeyMgr = (ClientRowKeyManager) clientKeyMgr;
303     }
304     else
305     {
306       // becareful not to force creation of the internal state
307       // too early:
308       InternalState iState = _getInternalState(false);
309       if (iState != null)
310       {
311         iState._stampState = null;
312         iState._clientKeyMgr = null;
313       }
314     }
315   }
316 
317   /**
318    * Checks to see if the current row is available. This is useful when the
319    * total number of rows is not known.
320    * @see CollectionModel#isRowAvailable
321    * @return true iff the current row is available.
322    */
323   public final boolean isRowAvailable()
324   {
325     return getCollectionModel().isRowAvailable();
326   }
327 
328   /**
329    * Gets the total number of rows in this table.
330    * @see CollectionModel#getRowCount
331    * @return -1 if the total number is not known.
332    */
333   public final int getRowCount()
334   {
335     return getCollectionModel().getRowCount();
336   }
337 
338   /**
339    * Gets the index of the current row.
340    * @see CollectionModel#getRowIndex
341    * @return -1 if the current row is unavailable.
342    */
343   public final int getRowIndex()
344   {
345     return getCollectionModel().getRowIndex();
346   }
347 
348   /**
349    * Gets the rowKey of the current row.
350    * @see CollectionModel#getRowKey
351    * @return null if the current row is unavailable.
352    */
353   public final Object getRowKey()
354   {
355     InternalState iState = _getInternalState(true);
356     if (iState._currentRowKey == _NULL)
357     {
358       // See bug 4534104.
359       // Sometimes the rowKey for a particular row changes during update model
360       // (this happens in ADFM if you edit the primary key of a row).
361       // It is bad if the rowKey changes after _restoreStampState() and
362       // before _saveStampState(). Therefore, we cache it:
363       iState._currentRowKey = getCollectionModel().getRowKey();
364     }
365 
366     return iState._currentRowKey;
367   }
368 
369   /**
370    * Gets the data for the current row.
371    * @see CollectionModel#getRowData
372    * @return null if the current row is unavailable
373    */
374   public final Object getRowData()
375   {
376     CollectionModel model = getCollectionModel();
377     // we need to call isRowAvailable() here because the 1.0 sun RI was
378     // throwing exceptions when getRowData() was called with rowIndex=-1
379     return model.isRowAvailable() ? model.getRowData() : null;
380   }
381 
382   /**
383    * Checks to see if the row at the given index is available.
384    * @see CollectionModel#isRowAvailable(int)
385    * @param rowIndex the index of the row to check.
386    * @return true if data for the row exists.
387    */
388   public boolean isRowAvailable(int rowIndex)
389   {
390     return getCollectionModel().isRowAvailable(rowIndex);
391   }
392 
393   /**
394    * Gets the rowData at the given index.
395    * @see CollectionModel#getRowData(int)
396    * @param rowIndex the index of the row to get data from.
397    * @return the data for the given row.
398    */
399   public Object getRowData(int rowIndex)
400   {
401     return getCollectionModel().getRowData(rowIndex);
402   }
403 
404   /**
405    * Gets the EL variable name to use to expose the varStatusMap.
406    * @see #createVarStatusMap()
407    */
408   public abstract String getVarStatus();
409 
410   /**
411    * Makes a row current.
412    * This method calls {@link #preRowDataChange} and
413    * {@link #postRowDataChange} as appropriate.
414    * @see CollectionModel#setRowKey
415    * @param rowKey The rowKey of the row that should be made current. Use null
416    * to clear the current row.
417    */
418   public void setRowKey(Object rowKey)
419   {
420     preRowDataChange();
421     getCollectionModel().setRowKey(rowKey);
422     postRowDataChange();
423     if (_LOG.isFine() && (rowKey != null) && (!isRowAvailable()))
424       _LOG.fine("no row available for rowKey:"+rowKey);
425   }
426 
427   /**
428    * Makes a row current.
429    * This method calls {@link #preRowDataChange} and
430    * {@link #postRowDataChange} as appropriate.
431    * @see CollectionModel#setRowIndex
432    * @param rowIndex The rowIndex of the row that should be made current. Use -1
433    * to clear the current row.
434    */
435   public void setRowIndex(int rowIndex)
436   {
437     preRowDataChange();
438     getCollectionModel().setRowIndex(rowIndex);
439     postRowDataChange();
440     if (_LOG.isFine() && (rowIndex != -1) && (!isRowAvailable()))
441       _LOG.fine("no row available for rowIndex:"+rowIndex);
442   }
443 
444   /**
445    * @param property a property name in the model
446    * @return  true if the model is sortable by the given property.
447    * @see CollectionModel#isSortable
448    */
449   public final boolean isSortable(String property)
450   {
451     return getCollectionModel().isSortable(property);
452   }
453 
454   /**
455    * Sorts this collection by the given criteria.
456    * @param criteria Each element in this List must be of type SortCriterion.
457    * @see org.apache.myfaces.trinidad.model.SortCriterion
458    * @see CollectionModel#setSortCriteria
459    */
460   public void setSortCriteria(List<SortCriterion> criteria)
461   {
462     getCollectionModel().setSortCriteria(criteria);
463   }
464 
465   /**
466    * Gets the criteria that this collection is sorted by.
467    * @return each element in this List is of type SortCriterion.
468    * An empty list is returned if this collection is not sorted.
469    * @see org.apache.myfaces.trinidad.model.SortCriterion
470    * @see CollectionModel#getSortCriteria
471    */
472   public final List<SortCriterion> getSortCriteria()
473   {
474     return getCollectionModel().getSortCriteria();
475   }
476 
477   /**
478    * Clear the rowKey-to-currencyString cache.
479    * The cache is not cleared immediately; instead it will be cleared
480    * when {@link #encodeBegin} is called.
481    * @deprecated Have your Renderer implement {@link ClientRowKeyManagerFactory}
482    * and create your own {@link ClientRowKeyManager} instances. Then you can
483    * manage the lifecycle of each key inside your ClientRowKeyManager.
484    */
485   @Deprecated
486   protected void clearCurrencyStringCache()
487   {
488     _getInternalState(true)._clearTokenCache = true;
489   }
490 
491   /**
492    * Clears all the currency strings.
493    */
494   @Override
495   public final void encodeBegin(FacesContext context) throws IOException
496   {
497     _init();
498 
499     InternalState istate = _getInternalState(true);
500     // we must not clear the currency cache everytime. only clear
501     // it in response to specific events: bug 4773659
502 
503     // TODO all this code should be removed and moved into the renderer:
504     if (istate._clearTokenCache)
505     {
506       istate._clearTokenCache = false;
507       ClientRowKeyManager keyMgr = getClientRowKeyManager();
508       if (keyMgr instanceof DefaultClientKeyManager)
509         ((DefaultClientKeyManager) keyMgr).clear();
510     }
511     _flushCachedModel();
512 
513     Object assertKey = null;
514     assert ((assertKey = getRowKey()) != null) || true;
515     __encodeBegin(context);
516     // make sure that the rendering code preserves the currency:
517     assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";
518   }
519 
520   @Override
521   public void encodeEnd(FacesContext context) throws IOException
522   {
523     Object assertKey = null;
524     assert ((assertKey = getRowKey()) != null) || true;
525     super.encodeEnd(context);
526     // make sure that the rendering code preserves the currency:
527     assert _assertKeyPreserved(assertKey) : "CurrencyKey not preserved";
528   }
529 
530   private boolean _assertKeyPreserved(Object oldKey)
531   {
532     Object newKey = getRowKey();
533     return (oldKey != null) ? oldKey.equals(newKey) : (newKey == null);
534   }
535 
536   void __encodeBegin(FacesContext context) throws IOException
537   {
538     super.encodeBegin(context);
539   }
540 
541   /**
542    * Checks to see if processDecodes was called. If this returns true
543    * then this is the initial request, and processDecodes has not been called.
544    */
545   boolean __isFirstRender()
546   {
547     InternalState iState = _getInternalState(true);
548     return iState._isFirstRender;
549   }
550 
551   /**
552    * @deprecated use getClientRowKey
553    * @see #getClientRowKey
554    */
555   @Deprecated
556   public String getCurrencyString()
557   {
558     return getClientRowKey();
559   }
560 
561   /**
562    * @deprecated use setClientRowKey
563    * @see #setClientRowKey
564    */
565   @Deprecated
566   public void setCurrencyString(String currency)
567   {
568     setClientRowKey(currency);
569   }
570 
571 
572   /**
573    * Gets a String version of the current rowkey.
574    * The contents of the String are controlled by the current
575    * {@link ClientRowKeyManager}.
576    * This String can be passed into the {@link #setClientRowKey} method
577    * to restore the current rowData.
578    * The lifetime of this String is short and it may not be valid during
579    * future requests; however, it is guaranteed to be valid
580    * for the next subsequent request.
581    * @see UIXCollection#setClientRowKey(java.lang.String)
582    * @see UIXCollection#getClientRowKeyManager()
583    * @see ClientRowKeyManager#getClientRowKey
584    */
585   public String getClientRowKey()
586   {
587     // only call getCurrencyKey if we already have a dataModel.
588     // otherwise behave as though no currency was set.
589     // we need to do this because we don't want dataModel created for components
590     // that are not rendered. The faces RI calls getClientId even on components
591     // that are not rendered and this in turn was calling this method:
592     Object currencyObject = _getCurrencyKey();
593     if (currencyObject == null)
594       return null;
595 
596     Object initKey = _getCurrencyKeyForInitialStampState();
597     if (_equals(currencyObject, initKey))
598       return null;
599 
600     FacesContext fc = FacesContext.getCurrentInstance();
601     String key = getClientRowKeyManager().getClientRowKey(fc, this, currencyObject);
602     return key;
603   }
604 
605 
606 
607 
608   /**
609    * This is a safe way of getting currency keys and not accidentally forcing
610    * the model to execute. When rendered="false" we should never execute the model.
611    * However, the JSF engine calls certain methods when rendered="false" such as
612    * processSaveState and getClientId.
613    * Those methods, in turn, get the CurrencyKey.
614    */
615   private Object _getCurrencyKey()
616   {
617     // use false so that we don't create an internal state.
618     // if the internal state is created too soon, then the same internal
619     // state will get shared across all nested table instances.
620     // this was causing bug 4616844:
621     InternalState iState = _getInternalState(false);
622     if (iState == null)
623       return null;
624 
625     return (iState._model != null)
626       ? getRowKey()
627       : _getCurrencyKeyForInitialStampState();
628   }
629 
630   /**
631      * Restores this component's rowData to be what it was when the given
632      * client rowKey string was created.
633      * @see UIXCollection#getClientRowKey()
634      */
635   public void setClientRowKey(String clientRowKey)
636   {
637     if (clientRowKey == null)
638     {
639       setRowKey(_getCurrencyKeyForInitialStampState());
640       return;
641     }
642 
643     FacesContext fc = FacesContext.getCurrentInstance();
644     Object rowkey = getClientRowKeyManager().getRowKey(fc, this, clientRowKey);
645 
646     if (rowkey == null)
647     {
648       _LOG.severe("CANNOT_FIND_ROWKEY",clientRowKey);
649     }
650     else
651       setRowKey(rowkey);
652   }
653 
654   /**
655      * Gets the client-id of this component, without any NamingContainers.
656      * This id changes depending on the currency Object.
657      * Because this implementation uses currency strings, the local client ID is
658      * not stable for very long. Its lifetime is the same as that of a
659      * currency string.
660      * @see UIXCollection#getClientRowKey()
661      * @return the local clientId
662      */
663   @Override
664   protected final String getLocalClientId()
665   {
666     String id = super.getLocalClientId();
667     String key = getClientRowKey();
668     if (key != null)
669     {
670       StringBuilder bld = __getSharedStringBuilder();
671       bld.append(id).append(NamingContainer.SEPARATOR_CHAR).append(key);
672       id = bld.toString();
673     }
674 
675     return id;
676   }
677 
678   /**
679    * Prepares this component for a change in the rowData.
680    * This method should be called right before the rowData changes.
681    * It saves the internal states of all the stamps of this component
682    * so that they can be restored when the rowData is reverted.
683    */
684   protected final void preRowDataChange()
685   {
686     _saveStampState();
687     InternalState iState = _getInternalState(true);
688     // mark the cached rowKey as invalid:
689     iState._currentRowKey = _NULL;
690   }
691 
692   /**
693    * Sets up this component to use the new rowData.
694    * This method should be called right after the rowData changes.
695    * It sets up the var EL variable to be the current rowData.
696    * It also sets up the internal states of all the stamps of this component
697    * to match this new rowData.
698    */
699   protected final void postRowDataChange()
700   {
701     Object rowData = getRowData();
702     if (_LOG.isFinest() && (rowData == null))
703     {
704       _LOG.finest("rowData is null at rowIndex:"+getRowIndex()+
705                   " and currencyKey:"+getRowKey());
706     }
707 
708     InternalState iState = _getInternalState(true);
709     if (rowData == null)
710     {
711       // if the rowData is null, then we will restore the EL 'var' variable
712       // to be whatever the value was, before this component started rendering:
713       if (iState._prevVarValue != _NULL)
714       {
715         _setELVar(iState._var, iState._prevVarValue);
716         iState._prevVarValue = _NULL;
717       }
718       if (iState._prevVarStatus != _NULL)
719       {
720         _setELVar(iState._varStatus, iState._prevVarStatus);
721         iState._prevVarStatus = _NULL;
722       }
723     }
724     else
725     {
726       if (iState._var != null)
727       {
728         Object oldData = _setELVar(iState._var, rowData);
729         if (iState._prevVarValue == _NULL)
730           iState._prevVarValue = oldData;
731       }
732 
733       // varStatus is not set per row. It is only set once.
734       // if _PrevVarStatus has not been assigned, then we have not set the
735       // varStatus yet:
736       if ((iState._varStatus != null) && (iState._prevVarStatus == _NULL))
737       {
738         Map<String, Object> varStatusMap = createVarStatusMap();
739         iState._prevVarStatus = _setELVar(iState._varStatus, varStatusMap);
740       }
741     }
742 
743     _restoreStampState();
744   }
745 
746   /**
747    * Gets the UIComponents that are considered stamps.
748    * This implementation simply returns the children of this component.
749    * @return each element must be of type UIComponent.
750    */
751   @SuppressWarnings("unchecked")
752   protected List<UIComponent> getStamps()
753   {
754     return getChildren();
755   }
756 
757   /**
758    * Gets the currencyObject to setup the rowData to use to build initial
759    * stamp state.
760    */
761   private Object _getCurrencyKeyForInitialStampState()
762   {
763     InternalState iState = _getInternalState(false);
764     if (iState == null)
765       return null;
766 
767     Object rowKey = iState._initialStampStateKey;
768     return (rowKey == _NULL) ? null : rowKey;
769   }
770 
771   /**
772    * Saves the state of a stamp. This method is called when the currency of this
773    * component is changed so that the state of this stamp can be preserved, before
774    * the stamp is updated with the state corresponding to the new currency.
775    * This method recurses for the children and facets of the stamp.
776    * @return this object must be Serializable if client-side state saving is
777    * used.
778    */
779   @SuppressWarnings("unchecked")
780   protected Object saveStampState(FacesContext context, UIComponent stamp)
781   {
782     if (stamp.isTransient())
783       return Transient.TRUE;
784 
785     // The structure we will use is:
786     //   0: state of the stamp
787     //   1: state of the children (an array)
788     //   2: state of the facets (an array of name-key pairs)
789     // If there is no facet state, we have a two-element array
790     // If there is no facet state or child state, we have a one-elment array
791     // If there is no state at all, we return null
792 
793     Object stampState = StampState.saveStampState(context, stamp);
794 
795     // StampState can never EVER be an Object array, as if we do,
796     // we have no possible way of identifying the difference between
797     // just having stamp state, and having stamp state + child/facet state
798     assert(!(stampState instanceof Object[]));
799 
800     int facetCount = _getFacetCount(stamp);
801     int childCount = stamp.getChildCount();
802 
803     Object[] state = null;
804 
805     if (facetCount > 0)
806     {
807       boolean facetStateIsEmpty = true;
808       Object[] facetState = null;
809 
810       Map<String, UIComponent> facetMap = stamp.getFacets();
811 
812       int i = 0;
813       for(Map.Entry<String, UIComponent> entry : facetMap.entrySet())
814       {
815         Object singleFacetState = saveStampState(context, entry.getValue());
816         if ((singleFacetState == null) ||
817             (singleFacetState == Transient.TRUE))
818           continue;
819 
820         // Don't bother allocating anything until we have some non-null
821         // and non-transient facet state
822         if (facetStateIsEmpty)
823         {
824           facetStateIsEmpty = false;
825           facetState = new Object[facetCount * 2];
826         }
827 
828         int base = i * 2;
829         assert(facetState != null);
830         facetState[base] = entry.getKey();
831         facetState[base + 1] = singleFacetState;
832         i++;
833       }
834 
835       // OK, we had something:  allocate the state array to three
836       // entries, and insert the facet state at position 2
837       if (!facetStateIsEmpty)
838       {
839         // trim the facetState array if necessary
840         if(i < facetCount)
841         {
842           Object[] trimmed = new Object[i*2];
843           System.arraycopy(facetState, 0, trimmed, 0, i*2);
844           facetState = trimmed;
845         }
846         state = new Object[3];
847         state[2] = facetState;
848       }
849     }
850 
851     // If we have any children, iterate through the array,
852     // saving state
853     Object childState = StampState.saveChildStampState(context,
854                                                        stamp,
855                                                        this);
856     if (childState != null)
857     {
858       // If the state hasn't been allocated yet, we only
859       // need a two-element array
860       if (state == null)
861         state = new Object[2];
862       state[1] = childState;
863     }
864 
865     // If we don't have an array, just return the stamp
866     // state
867     if (state == null)
868       return stampState;
869 
870     // Otherwise, store the stamp state at index 0, and return
871     state[0] = stampState;
872     return state;
873   }
874 
875   /**
876    * Restores the state of a stamp. This method is called after the currency of this
877    * component is changed so that the state of this stamp can be changed
878    * to match the new currency.
879    * This method recurses for the children and facets of the stamp.
880    */
881   @SuppressWarnings("unchecked")
882   protected void restoreStampState(FacesContext context, UIComponent stamp,
883                                    Object stampState)
884   {
885     // Just a transient component - return
886     if ((stampState == Transient.TRUE) || (stampState == null))
887       return;
888 
889     // If this isn't an Object array, then it's a component with state
890     // of its own, but no child/facet state - so restore and be done
891     if (!(stampState instanceof Object[]))
892     {
893       StampState.restoreStampState(context, stamp, stampState);
894       // NOTE early return
895       return;
896     }
897 
898     Object[] state = (Object[]) stampState;
899     int stateSize = state.length;
900     // We always have at least one element if we get to here
901     assert(stateSize >= 1);
902 
903     StampState.restoreStampState(context, stamp, state[0]);
904 
905 
906     // If there's any facet state, restore it
907     if (stateSize >= 3)
908     {
909       Object[] facetStateArray = (Object[]) state[2];
910       // This had better be non-null, otherwise we never
911       // should have allocated a three-element array!
912       assert(facetStateArray != null);
913 
914       for(int i=0; i<facetStateArray.length; i+=2)
915       {
916         String facetName = (String) facetStateArray[i];
917         Object facetState = facetStateArray[i + 1];
918         if (facetState != Transient.TRUE)
919           restoreStampState(context, stamp.getFacet(facetName), facetState);
920       }
921     }
922 
923     // If there's any child state, restore it
924     if (stateSize >= 2)
925     {
926       StampState.restoreChildStampState(context,
927                                         stamp,
928                                         this,
929                                         state[1]);
930     }
931   }
932 
933   /**
934    * Process a component.
935    * This method calls {@link #processDecodes},
936    * {@link #processValidators} or
937    * {@link #processUpdates}
938    * depending on the {#link PhaseId}.
939    */
940   protected final void processComponent(
941     FacesContext context,
942     UIComponent  component,
943     PhaseId      phaseId)
944   {
945     if (component != null)
946     {
947       if (phaseId == PhaseId.APPLY_REQUEST_VALUES)
948         component.processDecodes(context);
949       else if (phaseId == PhaseId.PROCESS_VALIDATIONS)
950         component.processValidators(context);
951       else if (phaseId == PhaseId.UPDATE_MODEL_VALUES)
952         component.processUpdates(context);
953       else
954         throw new IllegalArgumentException(_LOG.getMessage(
955           "BAD_PHASEID",phaseId));
956     }
957   }
958 
959   /**
960    * Process this component's facets and children.
961    * This method should call {@link #processComponent}
962    * as many times as necessary for each facet and child.
963    * {@link #processComponent}
964    * may be called repeatedly for the same child if that child is
965    * being stamped.
966    */
967   protected abstract void processFacetsAndChildren(
968     FacesContext context,
969     PhaseId phaseId);
970 
971   /**
972    * Gets the CollectionModel to use with this component.
973    */
974   protected final CollectionModel getCollectionModel()
975   {
976     return getCollectionModel(true);
977   }
978 
979   /**
980    * Gets the ClientRowKeyManager that is used to handle the
981    * {@link #getClientRowKey} and
982    * {@link #setClientRowKey} methods.
983    * If the manager does not already exist a new one is created.
984    * In order to create your own manager, your Renderer (for this component)
985    * must implement
986    * {@link ClientRowKeyManagerFactory}
987    */
988   public final ClientRowKeyManager getClientRowKeyManager()
989   {
990     // this method must be public, because specific renderers
991     // need access to the ClientRowKeyManager so that they might prune it.
992 
993     InternalState iState = _getInternalState(true);
994     if (iState._clientKeyMgr == null)
995     {
996       FacesContext fc = FacesContext.getCurrentInstance();
997       Renderer r = getRenderer(fc);
998       iState._clientKeyMgr = (r instanceof ClientRowKeyManagerFactory)
999         ? ((ClientRowKeyManagerFactory) r).createClientRowKeyManager(fc, this)
1000         : new DefaultClientKeyManager();
1001     }
1002     return iState._clientKeyMgr;
1003   }
1004 
1005   /**
1006    * Gets the CollectionModel to use with this component.
1007    *
1008    * @param createIfNull  creates the collection model if necessary
1009    */
1010   protected final CollectionModel getCollectionModel(
1011     boolean createIfNull)
1012   {
1013     InternalState iState = _getInternalState(true);
1014     if (iState._model == null && createIfNull)
1015     {
1016       //  _init() is usually called from either processDecodes or encodeBegin.
1017       //  Sometimes both processDecodes and encodeBegin may not be called,
1018       //  but processSaveState is called (this happens when
1019       //  component's rendered attr is set to false). We need to make sure that
1020       //  _init() is called in that case as well. Otherwise we get nasty NPEs.
1021       //  safest place is to call it here:
1022       _init();
1023 
1024       iState._value = getValue();
1025       iState._model = createCollectionModel(null, iState._value);
1026       assert iState._model != null;
1027     }
1028     // model might not have been created if createIfNull is false:
1029     if ((iState._initialStampStateKey == _NULL) &&
1030         (iState._model != null))
1031     {
1032       // if we have not already initialized the initialStampStateKey
1033       // that means that we don't have any stamp-state to use as the default
1034       // state for rows that we have not seen yet. So...
1035       // we will use any stamp-state for the initial rowKey on the model
1036       // as the default stamp-state for all rows:
1037       iState._initialStampStateKey = iState._model.getRowKey();
1038     }
1039     return iState._model;
1040   }
1041 
1042   /**
1043    * Creates the CollectionModel to use with this component.
1044    * @param current the current CollectionModel, or null if there is none.
1045    * @param value this is the value returned from {@link #getValue()}
1046    */
1047   protected abstract CollectionModel createCollectionModel(
1048     CollectionModel current,
1049     Object value);
1050 
1051   /**
1052    * Gets the value that must be converted into a CollectionModel
1053    */
1054   protected abstract Object getValue();
1055 
1056   /**
1057    * Gets the Map to use as the "varStatus".
1058    * This implementation supports the following keys:<ul>
1059    * <li>model - returns the CollectionModel
1060    * <li>index - returns the current rowIndex
1061    * <li>rowKey - returns the current rowKey
1062    * <li>current - returns the current rowData
1063    * </ul>
1064    */
1065   protected Map<String, Object> createVarStatusMap()
1066   {
1067     return new AbstractMap<String, Object>()
1068     {
1069       @Override
1070       public Object get(Object key)
1071       {
1072         // some of these keys are from <c:forEach>, ie:
1073         // javax.servlet.jsp.jstl.core.LoopTagStatus
1074         if ("model".equals(key))
1075           return getCollectionModel();
1076         if ("rowKey".equals(key))
1077           return getRowKey();
1078         if ("index".equals(key)) // from jstl
1079           return Integer.valueOf(getRowIndex());
1080         if ("current".equals(key)) // from jstl
1081           return getRowData();
1082         return null;
1083       }
1084 
1085       @Override
1086       public Set<Map.Entry<String, Object>> entrySet()
1087       {
1088         return Collections.emptySet();
1089       }
1090     };
1091   }
1092 
1093   /**
1094    * override this method to place initialization code that must run
1095    * once this component is created and the jsp engine has finished setting
1096    * attributes on it.
1097    */
1098   void __init()
1099   {
1100     InternalState iState = _getInternalState(true);
1101     iState._var = getVar();
1102     if (_LOG.isFine() && (iState._var == null))
1103     {
1104       _LOG.fine("'var' attribute is null.");
1105     }
1106     iState._varStatus = getVarStatus();
1107     if (_LOG.isFinest() && (iState._varStatus == null))
1108     {
1109       _LOG.finest("'varStatus' attribute is null.");
1110     }
1111  }
1112 
1113   private void _init()
1114   {
1115     InternalState iState = _getInternalState(true);
1116     if (!iState._isInitialized)
1117     {
1118       assert iState._model == null;
1119       iState._isInitialized = true;
1120       __init();
1121     }
1122   }
1123 
1124   private void _flushCachedModel()
1125   {
1126     InternalState iState = _getInternalState(true);
1127     Object value = getValue();
1128     if (iState._value != value)
1129     {
1130       iState._value = value;
1131       iState._model = createCollectionModel(iState._model, value);
1132     }
1133   }
1134 
1135   /**
1136    * Gets the internal state of this component.
1137    * This is to support table within table.
1138    */
1139   Object __getMyStampState()
1140   {
1141     return _state;
1142   }
1143 
1144   /**
1145    * Sets the internal state of this component.
1146    * This is to support table within table.
1147    * @param stampState the internal state is obtained from this object.
1148    */
1149   void __setMyStampState(Object stampState)
1150   {
1151     InternalState iState = (InternalState) stampState;
1152     _state = iState;
1153   }
1154 
1155   /**
1156    * Returns true if an event (other than a selection event)
1157    * has been queued for this component.  This is a hack
1158    * to support validation in the tableSelectXyz components.
1159    */
1160   boolean __hasEvent()
1161   {
1162     InternalState iState = _getInternalState(true);
1163     return iState._hasEvent;
1164   }
1165 
1166   /**
1167    * Saves the state of all the stamps of this component.
1168    * This method should be called before the rowData of this component
1169    * changes. This method gets all the stamps using {@link #getStamps} and
1170    * saves their states by calling {@link #saveStampState}.
1171    */
1172   private void _saveStampState()
1173   {
1174     // Never read and created by _getStampState
1175     //InternalState iState = _getInternalState(true);
1176 
1177     StampState stampState = _getStampState();
1178     FacesContext context = getFacesContext();
1179     Object currencyObj = getRowKey();
1180     int position = 0;
1181     for(UIComponent stamp : getStamps())
1182     {
1183       Object state = saveStampState(context, stamp);
1184 //      String stampId = stamp.getId();
1185       // TODO
1186       // temporarily use position. later we need to use ID's to access
1187       // stamp state everywhere, and special case NamingContainers:
1188       String stampId = String.valueOf(position++);
1189       stampState.put(currencyObj, stampId, state);
1190       if (_LOG.isFinest())
1191         _LOG.finest("saving stamp state for currencyObject:"+currencyObj+
1192           " and stampId:"+stampId);
1193     }
1194   }
1195 
1196 
1197   /**
1198    * Restores the state of all the stamps of this component.
1199    * This method should be called after the currency of this component
1200    * changes. This method gets all the stamps using {@link #getStamps} and
1201    * restores their states by calling
1202    * {@link #restoreStampState}.
1203    */
1204   private void _restoreStampState()
1205   {
1206     StampState stampState = _getStampState();
1207     FacesContext context = getFacesContext();
1208     Object currencyObj = getRowKey();
1209     int position = 0;
1210     for(UIComponent stamp : getStamps())
1211     {
1212 //      String stampId = stamp.getId();
1213       // TODO
1214       // temporarily use position. later we need to use ID's to access
1215       // stamp state everywhere, and special case NamingContainers:
1216       String stampId = String.valueOf(position++);
1217       Object state = stampState.get(currencyObj, stampId);
1218       if (state == null)
1219       {
1220         Object iniStateObj = _getCurrencyKeyForInitialStampState();
1221         state = stampState.get(iniStateObj, stampId);
1222         /*
1223         if (state==null)
1224         {
1225           _LOG.severe("NO_INITIAL_STAMP_STATE", new Object[]{currencyObj,iniStateObj,stampId});
1226           continue;
1227         }*/
1228       }
1229       restoreStampState(context, stamp, state);
1230     }
1231   }
1232 
1233   private InternalState _getInternalState(boolean create)
1234   {
1235     if ((_state == null) && create)
1236     {
1237       _state = new InternalState();
1238     }
1239     return _state;
1240   }
1241 
1242   private StampState _getStampState()
1243   {
1244     InternalState iState = _getInternalState(true);
1245     if (iState._stampState == null)
1246       iState._stampState = new StampState();
1247 
1248     return iState._stampState;
1249   }
1250 
1251   /**
1252    * sets an EL variable.
1253    * @param varName the name of the variable
1254    * @param newData the value of the variable
1255    * @return the old value of the variable, or null if there was no old value.
1256    */
1257   private Object _setELVar(String varName, Object newData)
1258   {
1259     if (varName == null)
1260       return null;
1261 
1262     // we need to place each row at an EL reachable place so that it
1263     // can be accessed via the 'var' variable. Let's place it on the
1264     // requestMap:
1265     return TableUtils.setupELVariable(getFacesContext(), varName, newData);
1266   }
1267 
1268   private static boolean _equals(Object a, Object b)
1269   {
1270     if (b == null)
1271       return (a == null);
1272 
1273     return b.equals(a);
1274   }
1275 
1276   //
1277   // Optimized path that avoids creating the Facet map
1278   // unless necessary
1279   //
1280   private int _getFacetCount(UIComponent component)
1281   {
1282     if (component instanceof UIXComponent)
1283       return ((UIXComponent) component).getFacetCount();
1284 
1285     return component.getFacets().size();
1286   }
1287 
1288   private static final class DefaultClientKeyManager extends ClientRowKeyManager
1289   {
1290 
1291     public void clear()
1292     {
1293       _currencyCache.clear();
1294     }
1295 
1296     /**
1297      * {@inheritDoc}
1298      */
1299     @Override
1300     public Object getRowKey(FacesContext context, UIComponent component, String clientRowKey)
1301     {
1302       if (_isOptimizedKey(clientRowKey))
1303         return clientRowKey;
1304 
1305       ValueMap<Object,String> currencyCache = _currencyCache;
1306       Object rowkey = currencyCache.getKey(clientRowKey);
1307       return rowkey;
1308     }
1309 
1310     /**
1311      * {@inheritDoc}
1312      */
1313     @Override
1314     public String getClientRowKey(FacesContext context, UIComponent component, Object rowKey)
1315     {
1316       assert rowKey != null;
1317 
1318       ValueMap<Object,String> currencyCache = _currencyCache;
1319       String key = currencyCache.get(rowKey);
1320       // check to see if we already have a string key:
1321       if (key == null)
1322       {
1323         // we don't have a string-key, so create a new one.
1324 
1325         // first check to see if the rowkey itself can be used as the string-key:
1326         if (rowKey instanceof String)
1327         {
1328           // TODO: make sure that this string is suitable for use as
1329           // NamingContainer ids:
1330           key = rowKey.toString();
1331           if (_isOptimizedKey(key))
1332           {
1333             // no need to add to the token map:
1334             return key;
1335           }
1336         }
1337 
1338         key = _createToken(currencyCache);
1339 
1340         if (_LOG.isFiner())
1341           _LOG.finer("Storing token:"+key+
1342                      " for rowKey:"+rowKey);
1343 
1344         currencyCache.put(rowKey, key);
1345       }
1346       return key;
1347     }
1348 
1349     private static boolean _isOptimizedKey(String key)
1350     {
1351       // if a key could be a number, then it might conflict with our
1352       // internal representation of tokens. Therefore, if a key could be
1353       // a number, then use the token cache.
1354       // if there is no way this key can be a number, then it can
1355       // be treated as an optimized key and can bypass the token cache
1356       // system:
1357       return ((key.length() > 0) && (!Character.isDigit(key.charAt(0))));
1358     }
1359 
1360     private static String _createToken(ValueMap<Object,String> currencyCache)
1361     {
1362       String key = String.valueOf(currencyCache.size());
1363       return key;
1364     }
1365 
1366     private ValueMap<Object,String> _currencyCache = new ValueMap<Object,String>();
1367     private static final long serialVersionUID = 1L;
1368   }
1369 
1370   // this component's internal state is stored in an inner class
1371   // rather than in individual fields, because we want to make it
1372   // easy to quickly suck out or restore its internal state,
1373   // when this component is itself used as a stamp inside some other
1374   // stamping container, eg: nested tables.
1375   private static final class InternalState implements Serializable
1376   {
1377     private transient boolean _hasEvent = false;
1378     private transient Object _prevVarValue = _NULL;
1379     private transient Object _prevVarStatus = _NULL;
1380     private transient String _var = null;
1381     private transient String _varStatus = null;
1382     private transient Object _value = null;
1383     private transient CollectionModel _model = null;
1384     private tr