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 javax.faces.component.behavior;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.el.ValueExpression;
30  import javax.faces.component.StateHelper;
31  import javax.faces.component.StateHolder;
32  import javax.faces.component.UIComponentBase;
33  import javax.faces.context.FacesContext;
34  
35  /**
36   * A delta enabled state holder implementing the StateHolder Interface. 
37   * <p>
38   * Components implementing the PartalStateHolder interface have an initial state
39   * and delta states, the initial state is the one holding all root values
40   * and deltas store differences to the initial states
41   * </p>
42   * <p>
43   * For components not implementing partial state saving only the initial states are
44   * of importance, everything is stored and restored continously there
45   * </p> 
46   * <p>
47   * The state helper seems to have three internal storage mechanisms:
48   * one being a list which stores plain values,
49   * one being a key value pair which stores key values in maps
50   * add serves the plain list type while put serves the 
51   * key value type, 
52   * the third is the value which has to be stored plainly as is!
53   * </p>
54   * In other words, this map can be seen as a composite map. It has two maps: 
55   * initial state map and delta map.
56   * <p> 
57   * If delta map is used (method component.initialStateMarked() ), 
58   * base or initial state map cannot be changed, since all changes 
59   * should be tracked on delta map.
60   * </p>
61   * <p> 
62   * The intention of this class is just hold property values
63   * and do a clean separation between initial state and delta.
64   * </p>
65   * <p>
66   * The code from this class comes from a refactor of 
67   * org.apache.myfaces.trinidad.bean.util.PropertyHashMap
68   * </p>
69   * <p>
70   * The context from this class comes and that should be taken into account
71   * is this:
72   * </p>
73   * <p> 
74   * First request:
75   * </p>
76   * <ul>
77   *   <li> A new template is created (using 
78   *   javax.faces.view.ViewDeclarationLanguage.buildView method)
79   *   and component.markInitialState is called from its related TagHandler classes 
80   *  (see javax.faces.view.facelets.ComponentHandler ).
81   *   When this method is executed, the component tree was populated from the values
82   *   set in the facelet abstract syntax tree (or in other words composition of 
83   *   facelets templates). </li>
84   *   <li> From this point all updates on the variables are considered "delta". </li>
85   *   <li> SaveState, if initialStateMarked is true, only delta is saved. </li>
86   * </ul>
87   * <p>
88   * Second request (and next ones)
89   * </p>
90   * <ul>
91   *   <li> A new template is created and component.markInitialState is called from
92   *   its related TagHandler classes again. In this way, components like c:forEach 
93   *   or c:if, that add or remove components could notify about this and handle 
94   *   them properly (see javax.faces.view.StateManagementStrategy). Note that a 
95   *   component restored using this method is no different as the same component 
96   *   at the first request at the same time. </li>
97   *   <li> A call for restoreState is done, passing the delta as object value. If no 
98   *   delta, the state is complete and no call is triggered. </li>
99   *   <li> Lifecycle occur, changing the necessary stuff. </li>
100  *   <li> SaveState, if initialStateMarked is true, only delta is saved. </li>
101  * </ul>
102  * <p>
103  * From the previous analysis, the following conclusions arise:
104  * <ul>
105  *   <li>This class only needs to keep track of delta changes, so when 
106  *   restoreState/saveState is called, the right objects are passed.</li>
107  *   <li>UIComponent.clearInitialState is used to reset the partial
108  *   state holder to a non delta state, so the state to be saved by
109  *   saveState is no longer a delta instead is a full state. If a call
110  *   to clearInitialState occur it is not expected a call for 
111  *   UIComponent.markInitialState occur on the current request.</li>
112  *   <li>The state is handled in the same way on UIData, so components
113  *   inside UIData share its state on all rows. There is no way to save 
114  *   delta per row.</li>
115  *   <li>The map backed by method put(Serializable,String,Object) is
116  *   a replacement of UIComponentBase.attributesMap and UIComponent.bindings map.
117  *   Note that on jsf 1.2, instances saved on attributesMap should not be
118  *   StateHolder, but on jsf 2.0 it is possible to have it. PartialStateHolder
119  *   instances are not handled in this map, or in other words delta state is not
120  *   handled in this classes (markInitialState and clearInitialState is not propagated).</li>
121  *   <li>The list backed by method add(Serializable,Object) should be (is not) a 
122  *   replacement of UIComponentBase.facesListeners, but note that StateHelper
123  *   does not implement PartialStateHolder, and facesListener could have instances
124  *   of that class that needs to be notified when UIComponent.markInitialState or
125  *   UIComponent.clearInitialState is called, or in other words facesListeners
126  *   should deal with PartialStateHolder instances.</li>
127  *   <li>The list backed by method add(Serializable,Object) is 
128  *   a replacement of UIViewRoot.phaseListeners list. Note that instances of
129  *   PhaseListener are not expected to implement StateHolder or PartialStateHolder.</li>
130  * </ul>
131  * </p>
132  * <p>
133  * NOTE: The current implementation of StateHelper on RI does not handle
134  * stateHolder values internally. To prevent problems when developers create
135  * custom components we should do this too. But anyway, the code that 
136  * handle this case should be let here as comment, if some day this feature
137  * is provided. Note than stateHolder aware properties like converter,
138  * validator or listeners should deal with StateHolder or PartialStateHolder
139  * on component classes. 
140  * 
141  * </p>
142  *
143  * @author Werner Punz
144  * @author Leonardo Uribe (latest modification by $Author: struberg $)
145  * @version $Rev: 1188235 $ $Date: 2011-10-24 17:09:33 +0000 (Mon, 24 Oct 2011) $
146  */
147 class _DeltaStateHelper <A extends AjaxBehavior> implements StateHelper
148 {
149 
150     /**
151      * We need to hold a component instance because:
152      * 
153      * - The component is the one who knows if we are on initial or delta mode
154      * - eval assume calls to component.ValueExpression
155      */
156     private A _target;
157 
158     /**
159      * This map holds the full current state
160      */
161     private Map<Serializable, Object> _fullState;
162 
163     /**
164      * This map only keep track of delta changes to be saved
165      */
166     private Map<Serializable, Object> _deltas;
167     
168     /**
169      * This map keep track of StateHolder keys, to be saved when
170      * saveState is called. 
171      */
172     //private Set<Serializable> _stateHolderKeys;  
173 
174     private boolean _transient = false;
175 
176     public _DeltaStateHelper(A target)
177     {
178         super();
179         this._target = target;
180         _fullState = new HashMap<Serializable, Object>();
181         _deltas = null;
182         //_stateHolderKeys = new HashSet<Serializable>();
183     }
184 
185     /**
186      * Used to create delta map on demand
187      * 
188      * @return
189      */
190     private boolean _createDeltas()
191     {
192         if (isInitialStateMarked())
193         {
194             if (_deltas == null)
195             {
196                 _deltas = new HashMap<Serializable, Object>(2);
197             }
198             return true;
199         }
200 
201         return false;
202     }
203     
204     protected boolean isInitialStateMarked()
205     {
206         return _target.initialStateMarked();
207     }
208 
209     public void add(Serializable key, Object value)
210     {
211         if (_createDeltas())
212         {
213             //Track delta case
214             Map<Object, Boolean> deltaListMapValues = (Map<Object, Boolean>) _deltas
215                     .get(key);
216             if (deltaListMapValues == null)
217             {
218                 deltaListMapValues = new InternalDeltaListMap<Object, Boolean>(
219                         3);
220                 _deltas.put(key, deltaListMapValues);
221             }
222             deltaListMapValues.put(value, Boolean.TRUE);
223         }
224 
225         //Handle change on full map
226         List<Object> fullListValues = (List<Object>) _fullState.get(key);
227         if (fullListValues == null)
228         {
229             fullListValues = new InternalList<Object>(3);
230             _fullState.put(key, fullListValues);
231         }
232         fullListValues.add(value);
233     }
234 
235     public Object eval(Serializable key)
236     {
237         Object returnValue = _fullState.get(key);
238         if (returnValue != null)
239         {
240             return returnValue;
241         }
242         ValueExpression expression = _target.getValueExpression(key
243                 .toString());
244         if (expression != null)
245         {
246             return expression.getValue(FacesContext.getCurrentInstance()
247                     .getELContext());
248         }
249         return null;
250     }
251 
252     public Object eval(Serializable key, Object defaultValue)
253     {
254         Object returnValue = _fullState.get(key);
255         if (returnValue != null)
256         {
257             return returnValue;
258         }
259         ValueExpression expression = _target.getValueExpression(key
260                 .toString());
261         if (expression != null)
262         {
263             return expression.getValue(FacesContext.getCurrentInstance()
264                     .getELContext());
265         }
266         return defaultValue;
267     }
268 
269     public Object get(Serializable key)
270     {
271         return _fullState.get(key);
272     }
273 
274     public Object put(Serializable key, Object value)
275     {
276         Object returnValue = null;
277         if (_createDeltas())
278         {
279             if (_deltas.containsKey(key))
280             {
281                 returnValue = _deltas.put(key, value);
282                 _fullState.put(key, value);
283             }
284             else if (value == null && !_fullState.containsKey(key))
285             {
286                 returnValue = null;
287             }
288             else
289             {
290                 _deltas.put(key, value);
291                 returnValue = _fullState.put(key, value);
292             }
293         }
294         else
295         {
296             /*
297             if (value instanceof StateHolder)
298             {
299                 _stateHolderKeys.add(key);
300             }
301             */
302             returnValue = _fullState.put(key, value);
303         }
304         return returnValue;
305     }
306 
307     public Object put(Serializable key, String mapKey, Object value)
308     {
309         boolean returnSet = false;
310         Object returnValue = null;
311         if (_createDeltas())
312         {
313             //Track delta case
314             Map<String, Object> mapValues = (Map<String, Object>) _deltas
315                     .get(key);
316             if (mapValues == null)
317             {
318                 mapValues = new InternalMap<String, Object>();
319                 _deltas.put(key, mapValues);
320             }
321             if (mapValues.containsKey(mapKey))
322             {
323                 returnValue = mapValues.put(mapKey, value);
324                 returnSet = true;
325             }
326             else
327             {
328                 mapValues.put(mapKey, value);
329             }
330         }
331 
332         //Handle change on full map
333         Map<String, Object> mapValues = (Map<String, Object>) _fullState
334                 .get(key);
335         if (mapValues == null)
336         {
337             mapValues = new InternalMap<String, Object>();
338             _fullState.put(key, mapValues);
339         }
340         if (returnSet)
341         {
342             mapValues.put(mapKey, value);
343         }
344         else
345         {
346             returnValue = mapValues.put(mapKey, value);
347         }
348         return returnValue;
349     }
350 
351     public Object remove(Serializable key)
352     {
353         Object returnValue = null;
354         if (_createDeltas())
355         {
356             if (_deltas.containsKey(key))
357             {
358                 // Keep track of the removed values using key/null pair on the delta map
359                 returnValue = _deltas.put(key, null);
360                 _fullState.remove(key);
361             }
362             else
363             {
364                 // Keep track of the removed values using key/null pair on the delta map
365                 _deltas.put(key, null);
366                 returnValue = _fullState.remove(key);
367             }
368         }
369         else
370         {
371             returnValue = _fullState.remove(key);
372         }
373         return returnValue;
374     }
375 
376     public Object remove(Serializable key, Object valueOrKey)
377     {
378         // Comment by lu4242 : The spec javadoc says if it is a Collection 
379         // or Map deal with it. But the intention of this method is work 
380         // with add(?,?) and put(?,?,?), this ones return instances of 
381         // InternalMap and InternalList to prevent mixing, so to be 
382         // consistent we'll cast to those classes here.
383         
384         Object collectionOrMap = _fullState.get(key);
385         Object returnValue = null;
386         if (collectionOrMap instanceof InternalMap)
387         {
388             if (_createDeltas())
389             {
390                 returnValue = _removeValueOrKeyFromMap(_deltas, key,
391                         valueOrKey, true);
392                 _removeValueOrKeyFromMap(_fullState, key, valueOrKey, false);
393             }
394             else
395             {
396                 returnValue = _removeValueOrKeyFromMap(_fullState, key,
397                         valueOrKey, false);
398             }
399         }
400         else if (collectionOrMap instanceof InternalList)
401         {
402             if (_createDeltas())
403             {
404                 returnValue = _removeValueOrKeyFromCollectionDelta(_deltas,
405                         key, valueOrKey);
406                 _removeValueOrKeyFromCollection(_fullState, key, valueOrKey);
407             }
408             else
409             {
410                 returnValue = _removeValueOrKeyFromCollection(_fullState, key,
411                         valueOrKey);
412             }
413         }
414         return returnValue;
415     }
416 
417     private static Object _removeValueOrKeyFromCollectionDelta(
418             Map<Serializable, Object> stateMap, Serializable key,
419             Object valueOrKey)
420     {
421         Object returnValue = null;
422         Map<Object, Boolean> c = (Map<Object, Boolean>) stateMap.get(key);
423         if (c != null)
424         {
425             if (c.containsKey(valueOrKey))
426             {
427                 returnValue = valueOrKey;
428             }
429             c.put(valueOrKey, Boolean.FALSE);
430         }
431         return returnValue;
432     }
433 
434     private static Object _removeValueOrKeyFromCollection(
435             Map<Serializable, Object> stateMap, Serializable key,
436             Object valueOrKey)
437     {
438         Object returnValue = null;
439         Collection c = (Collection) stateMap.get(key);
440         if (c != null)
441         {
442             if (c.remove(valueOrKey))
443             {
444                 returnValue = valueOrKey;
445             }
446             if (c.isEmpty())
447             {
448                 stateMap.remove(key);
449             }
450         }
451         return returnValue;
452     }
453 
454     private static Object _removeValueOrKeyFromMap(
455             Map<Serializable, Object> stateMap, Serializable key,
456             Object valueOrKey, boolean delta)
457     {
458         if (valueOrKey == null)
459         {
460             return null;
461         }
462 
463         Object returnValue = null;
464         Map<String, Object> map = (Map<String, Object>) stateMap.get(key);
465         if (map != null)
466         {
467             if (delta)
468             {
469                 // Keep track of the removed values using key/null pair on the delta map
470                 returnValue = map.put((String) valueOrKey, null);
471             }
472             else
473             {
474                 returnValue = map.remove(valueOrKey);
475             }
476 
477             if (map.isEmpty())
478             {
479                 //stateMap.remove(key);
480                 stateMap.put(key, null);
481             }
482         }
483         return returnValue;
484     }
485 
486     public boolean isTransient()
487     {
488         return _transient;
489     }
490 
491     /**
492      * Serializing cod
493      * the serialized data structure consists of key value pairs unless the value itself is an internal array
494      * or a map in case of an internal array or map the value itself is another array with its initial value
495      * myfaces.InternalArray, myfaces.internalMap
496      *
497      * the internal Array is then mapped to another array
498      *
499      * the internal Map again is then mapped to a map with key value pairs
500      *
501      *
502      */
503     public Object saveState(FacesContext context)
504     {
505         Map serializableMap = (isInitialStateMarked()) ? _deltas : _fullState;
506 
507         if (serializableMap == null || serializableMap.size() == 0)
508         {
509             return null;
510         }
511         
512         /*
513         int stateHolderKeyCount = 0;
514         if (isInitalStateMarked())
515         {
516             for (Iterator<Serializable> it = _stateHolderKeys.iterator(); it.hasNext();)
517             {
518                 Serializable key = it.next();
519                 if (!_deltas.containsKey(key))
520                 {
521                     stateHolderKeyCount++;
522                 }
523             }
524         }*/
525         
526         Map.Entry<Serializable, Object> entry;
527         //entry == key, value, key, value
528         Object[] retArr = new Object[serializableMap.entrySet().size() * 2];
529         //Object[] retArr = new Object[serializableMap.entrySet().size() * 2 + stateHolderKeyCount]; 
530 
531         Iterator<Map.Entry<Serializable, Object>> it = serializableMap
532                 .entrySet().iterator();
533         int cnt = 0;
534         while (it.hasNext())
535         {
536             entry = it.next();
537             retArr[cnt] = entry.getKey();
538 
539             Object value = entry.getValue();
540             
541             // The condition in which the call to saveAttachedState
542             // is to handle List, StateHolder or non Serializable instances.
543             // we check it here, to prevent unnecessary calls.
544             if (value instanceof StateHolder ||
545                 value instanceof List ||
546                 !(value instanceof Serializable))
547             {
548                 Object savedValue = saveAttachedState(context,
549                     value);
550                 retArr[cnt + 1] = savedValue;
551             }
552             else
553             {
554                 retArr[cnt + 1] = value;
555             }
556             cnt += 2;
557         }
558         
559         /*
560         if (isInitalStateMarked())
561         {
562             for (Iterator<Serializable> it2 = _stateHolderKeys.iterator(); it.hasNext();)
563             {
564                 Serializable key = it2.next();
565                 if (!_deltas.containsKey(key))
566                 {
567                     retArr[cnt] = key;
568                     Object value = _fullState.get(key);
569                     if (value instanceof PartialStateHolder)
570                     {
571                         //Could contain delta, save it as _AttachedDeltaState
572                         PartialStateHolder holder = (PartialStateHolder) value;
573                         if (holder.isTransient())
574                         {
575                             retArr[cnt + 1] = null;
576                         }
577                         else
578                         {
579                             retArr[cnt + 1] = new _AttachedDeltaWrapper(value.getClass(), holder.saveState(context));
580                         }
581                     }
582                     else
583                     {
584                         //Save everything
585                         retArr[cnt + 1] = saveAttachedState(context, _fullState.get(key));
586                     }
587                     cnt += 2;
588                 }
589             }
590         }
591         */       
592         return retArr;
593     }
594 
595     public void restoreState(FacesContext context, Object state)
596     {
597         if (state == null)
598         {
599             return;
600         }
601 
602         Object[] serializedState = (Object[]) state;
603         
604         if (!isInitialStateMarked() && !_fullState.isEmpty())
605         {
606             _fullState.clear();
607             if(_deltas != null)
608             {
609                 _deltas.clear();
610             }
611         }
612 
613         for (int cnt = 0; cnt < serializedState.length; cnt += 2)
614         {
615             Serializable key = (Serializable) serializedState[cnt];
616             Object savedValue = restoreAttachedState(context,
617                     serializedState[cnt + 1]);
618 
619             if (isInitialStateMarked())
620             {
621                 if (savedValue instanceof InternalDeltaListMap)
622                 {
623                     for (Map.Entry<Object, Boolean> mapEntry : ((Map<Object, Boolean>) savedValue)
624                             .entrySet())
625                     {
626                         boolean addOrRemove = mapEntry.getValue();
627                         if (addOrRemove)
628                         {
629                             //add
630                             this.add(key, mapEntry.getKey());
631                         }
632                         else
633                         {
634                             //remove
635                             this.remove(key, mapEntry.getKey());
636                         }
637                     }
638                 }
639                 else if (savedValue instanceof InternalMap)
640                 {
641                     for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) savedValue)
642                             .entrySet())
643                     {
644                         this.put(key, mapEntry.getKey(), mapEntry.getValue());
645                     }
646                 }
647                 /*
648                 else if (savedValue instanceof _AttachedDeltaWrapper)
649                 {
650                     _AttachedStateWrapper wrapper = (_AttachedStateWrapper) savedValue;
651                     //Restore delta state
652                     ((PartialStateHolder)_fullState.get(key)).restoreState(context, wrapper.getWrappedStateObject());
653                     //Add this key as StateHolder key 
654                     _stateHolderKeys.add(key);
655                 }
656                 */
657                 else
658                 {
659                     put(key, savedValue);
660                 }
661             }
662             else
663             {
664                 put(key, savedValue);
665             }
666         }
667     }
668 
669     public void setTransient(boolean transientValue)
670     {
671         _transient = transientValue;
672     }
673 
674     //We use our own data structures just to make sure
675     //nothing gets mixed up internally
676     static class InternalMap<K, V> extends HashMap<K, V> implements StateHolder
677     {
678         public InternalMap()
679         {
680             super();
681         }
682 
683         public InternalMap(int initialCapacity, float loadFactor)
684         {
685             super(initialCapacity, loadFactor);
686         }
687 
688         public InternalMap(Map<? extends K, ? extends V> m)
689         {
690             super(m);
691         }
692 
693         public InternalMap(int initialSize)
694         {
695             super(initialSize);
696         }
697 
698         public boolean isTransient()
699         {
700             return false;
701         }
702 
703         public void setTransient(boolean newTransientValue)
704         {
705             // No op
706         }
707 
708         public void restoreState(FacesContext context, Object state)
709         {
710             Object[] listAsMap = (Object[]) state;
711             for (int cnt = 0; cnt < listAsMap.length; cnt += 2)
712             {
713                 this.put((K) listAsMap[cnt], (V) UIComponentBase
714                         .restoreAttachedState(context, listAsMap[cnt + 1]));
715             }
716         }
717 
718         public Object saveState(FacesContext context)
719         {
720             int cnt = 0;
721             Object[] mapArr = new Object[this.size() * 2];
722             for (Map.Entry<K, V> entry : this.entrySet())
723             {
724                 mapArr[cnt] = entry.getKey();
725                 Object value = entry.getValue();
726                 
727                 if (value instanceof StateHolder ||
728                     value instanceof List ||
729                     !(value instanceof Serializable))
730                 {
731                     mapArr[cnt + 1] = saveAttachedState(context, value);
732                 }
733                 else
734                 {
735                     mapArr[cnt + 1] = value;
736                 }
737                 cnt += 2;
738             }
739             return mapArr;
740         }
741     }
742 
743     /**
744      * Map used to keep track of list changes 
745      */
746     static class InternalDeltaListMap<K, V> extends InternalMap<K, V>
747     {
748 
749         public InternalDeltaListMap()
750         {
751             super();
752         }
753 
754         public InternalDeltaListMap(int initialCapacity, float loadFactor)
755         {
756             super(initialCapacity, loadFactor);
757         }
758 
759         public InternalDeltaListMap(int initialSize)
760         {
761             super(initialSize);
762         }
763 
764         public InternalDeltaListMap(Map<? extends K, ? extends V> m)
765         {
766             super(m);
767         }
768     }
769 
770     static class InternalList<T> extends ArrayList<T> implements StateHolder
771     {
772         public InternalList()
773         {
774             super();
775         }
776 
777         public InternalList(Collection<? extends T> c)
778         {
779             super(c);
780         }
781 
782         public InternalList(int initialSize)
783         {
784             super(initialSize);
785         }
786 
787         public boolean isTransient()
788         {
789             return false;
790         }
791 
792         public void setTransient(boolean newTransientValue)
793         {
794         }
795 
796         public void restoreState(FacesContext context, Object state)
797         {
798             Object[] listAsArr = (Object[]) state;
799             //since all other options would mean dual iteration 
800             //we have to do it the hard way
801             for (Object elem : listAsArr)
802             {
803                 add((T) restoreAttachedState(context, elem));
804             }
805         }
806 
807         public Object saveState(FacesContext context)
808         {
809             Object[] values = new Object[size()];
810             for (int i = 0; i < size(); i++)
811             {
812                 Object value = get(i);
813                 
814                 if (value instanceof StateHolder ||
815                     value instanceof List ||
816                     !(value instanceof Serializable))
817                 {
818                     values[i] = saveAttachedState(context, value);
819                 }
820                 else
821                 {
822                     values[i] = value;
823                 }                
824             }
825             return values;
826         }
827     }
828     
829     private static Object saveAttachedState(FacesContext context, Object attachedObject)
830     {
831         if (context == null)
832         {
833             throw new NullPointerException ("context");
834         }
835         
836         if (attachedObject == null)
837         {
838             return null;
839         }
840         // StateHolder interface should take precedence over
841         // List children
842         if (attachedObject instanceof StateHolder)
843         {
844             StateHolder holder = (StateHolder) attachedObject;
845             if (holder.isTransient())
846             {
847                 return null;
848             }
849 
850             return new _AttachedStateWrapper(attachedObject.getClass(), holder.saveState(context));
851         }        
852         else if (attachedObject instanceof List)
853         {
854             List<Object> lst = new ArrayList<Object>(((List<?>) attachedObject).size());
855             for (Object item : (List<?>) attachedObject)
856             {
857                 if (item != null)
858                 {
859                     lst.add(saveAttachedState(context, item));
860                 }
861             }
862 
863             return new _AttachedListStateWrapper(lst);
864         }
865         else if (attachedObject instanceof Serializable)
866         {
867             return attachedObject;
868         }
869         else
870         {
871             return new _AttachedStateWrapper(attachedObject.getClass(), null);
872         }
873     }
874 
875     private static Object restoreAttachedState(FacesContext context, Object stateObj) throws IllegalStateException
876     {
877         if (context == null)
878         {
879             throw new NullPointerException("context");
880         }
881         if (stateObj == null)
882         {
883             return null;
884         }
885         if (stateObj instanceof _AttachedListStateWrapper)
886         {
887             List<Object> lst = ((_AttachedListStateWrapper) stateObj).getWrappedStateList();
888             List<Object> restoredList = new ArrayList<Object>(lst.size());
889             for (Object item : lst)
890             {
891                 restoredList.add(restoreAttachedState(context, item));
892             }
893             return restoredList;
894         }
895         else if (stateObj instanceof _AttachedStateWrapper)
896         {
897             Class<?> clazz = ((_AttachedStateWrapper) stateObj).getClazz();
898             Object restoredObject;
899             try
900             {
901                 restoredObject = clazz.newInstance();
902             }
903             catch (InstantiationException e)
904             {
905                 throw new RuntimeException("Could not restore StateHolder of type " + clazz.getName()
906                         + " (missing no-args constructor?)", e);
907             }
908             catch (IllegalAccessException e)
909             {
910                 throw new RuntimeException(e);
911             }
912             if (restoredObject instanceof StateHolder)
913             {
914                 _AttachedStateWrapper wrapper = (_AttachedStateWrapper) stateObj;
915                 Object wrappedState = wrapper.getWrappedStateObject();
916 
917                 StateHolder holder = (StateHolder) restoredObject;
918                 holder.restoreState(context, wrappedState);
919             }
920             return restoredObject;
921         }
922         else
923         {
924             return stateObj;
925         }
926     }
927 }