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 class _DeltaStateHelper <A extends AjaxBehavior> implements StateHelper
142 {
143 
144     /**
145      * We need to hold a component instance because:
146      * 
147      * - The component is the one who knows if we are on initial or delta mode
148      * - eval assume calls to component.ValueExpression
149      */
150     private A _target;
151 
152     /**
153      * This map holds the full current state
154      */
155     private Map<Serializable, Object> _fullState;
156 
157     /**
158      * This map only keep track of delta changes to be saved
159      */
160     private Map<Serializable, Object> _deltas;
161     
162     /**
163      * This map keep track of StateHolder keys, to be saved when
164      * saveState is called. 
165      */
166     //private Set<Serializable> _stateHolderKeys;  
167 
168     private boolean _transient = false;
169 
170     public _DeltaStateHelper(A target)
171     {
172         super();
173         this._target = target;
174         _fullState = new HashMap<Serializable, Object>();
175         _deltas = null;
176         //_stateHolderKeys = new HashSet<Serializable>();
177     }
178 
179     /**
180      * Used to create delta map on demand
181      * 
182      * @return
183      */
184     private boolean _createDeltas()
185     {
186         if (isInitialStateMarked())
187         {
188             if (_deltas == null)
189             {
190                 _deltas = new HashMap<Serializable, Object>(2);
191             }
192             return true;
193         }
194 
195         return false;
196     }
197     
198     protected boolean isInitialStateMarked()
199     {
200         return _target.initialStateMarked();
201     }
202 
203     public void add(Serializable key, Object value)
204     {
205         if (_createDeltas())
206         {
207             //Track delta case
208             Map<Object, Boolean> deltaListMapValues = (Map<Object, Boolean>) _deltas
209                     .get(key);
210             if (deltaListMapValues == null)
211             {
212                 deltaListMapValues = new InternalDeltaListMap<Object, Boolean>(
213                         3);
214                 _deltas.put(key, deltaListMapValues);
215             }
216             deltaListMapValues.put(value, Boolean.TRUE);
217         }
218 
219         //Handle change on full map
220         List<Object> fullListValues = (List<Object>) _fullState.get(key);
221         if (fullListValues == null)
222         {
223             fullListValues = new InternalList<Object>(3);
224             _fullState.put(key, fullListValues);
225         }
226         fullListValues.add(value);
227     }
228 
229     public Object eval(Serializable key)
230     {
231         Object returnValue = _fullState.get(key);
232         if (returnValue != null)
233         {
234             return returnValue;
235         }
236         ValueExpression expression = _target.getValueExpression(key
237                 .toString());
238         if (expression != null)
239         {
240             return expression.getValue(_target.getFacesContext()
241                     .getELContext());
242         }
243         return null;
244     }
245 
246     public Object eval(Serializable key, Object defaultValue)
247     {
248         Object returnValue = _fullState.get(key);
249         if (returnValue != null)
250         {
251             return returnValue;
252         }
253         ValueExpression expression = _target.getValueExpression(key
254                 .toString());
255         if (expression != null)
256         {
257             return expression.getValue(_target.getFacesContext()
258                     .getELContext());
259         }
260         return defaultValue;
261     }
262 
263     public Object get(Serializable key)
264     {
265         return _fullState.get(key);
266     }
267 
268     public Object put(Serializable key, Object value)
269     {
270         Object returnValue = null;
271         if (_createDeltas())
272         {
273             if (_deltas.containsKey(key))
274             {
275                 returnValue = _deltas.put(key, value);
276                 _fullState.put(key, value);
277             }
278             else if (value == null && !_fullState.containsKey(key))
279             {
280                 returnValue = null;
281             }
282             else
283             {
284                 _deltas.put(key, value);
285                 returnValue = _fullState.put(key, value);
286             }
287         }
288         else
289         {
290             /*
291             if (value instanceof StateHolder)
292             {
293                 _stateHolderKeys.add(key);
294             }
295             */
296             returnValue = _fullState.put(key, value);
297         }
298         return returnValue;
299     }
300 
301     public Object put(Serializable key, String mapKey, Object value)
302     {
303         boolean returnSet = false;
304         Object returnValue = null;
305         if (_createDeltas())
306         {
307             //Track delta case
308             Map<String, Object> mapValues = (Map<String, Object>) _deltas
309                     .get(key);
310             if (mapValues == null)
311             {
312                 mapValues = new InternalMap<String, Object>();
313                 _deltas.put(key, mapValues);
314             }
315             if (mapValues.containsKey(mapKey))
316             {
317                 returnValue = mapValues.put(mapKey, value);
318                 returnSet = true;
319             }
320             else
321             {
322                 mapValues.put(mapKey, value);
323             }
324         }
325 
326         //Handle change on full map
327         Map<String, Object> mapValues = (Map<String, Object>) _fullState
328                 .get(key);
329         if (mapValues == null)
330         {
331             mapValues = new InternalMap<String, Object>();
332             _fullState.put(key, mapValues);
333         }
334         if (returnSet)
335         {
336             mapValues.put(mapKey, value);
337         }
338         else
339         {
340             returnValue = mapValues.put(mapKey, value);
341         }
342         return returnValue;
343     }
344 
345     public Object remove(Serializable key)
346     {
347         Object returnValue = null;
348         if (_createDeltas())
349         {
350             if (_deltas.containsKey(key))
351             {
352                 // Keep track of the removed values using key/null pair on the delta map
353                 returnValue = _deltas.put(key, null);
354                 _fullState.remove(key);
355             }
356             else
357             {
358                 // Keep track of the removed values using key/null pair on the delta map
359                 _deltas.put(key, null);
360                 returnValue = _fullState.remove(key);
361             }
362         }
363         else
364         {
365             returnValue = _fullState.remove(key);
366         }
367         return returnValue;
368     }
369 
370     public Object remove(Serializable key, Object valueOrKey)
371     {
372         // Comment by lu4242 : The spec javadoc says if it is a Collection 
373         // or Map deal with it. But the intention of this method is work 
374         // with add(?,?) and put(?,?,?), this ones return instances of 
375         // InternalMap and InternalList to prevent mixing, so to be 
376         // consistent we'll cast to those classes here.
377         
378         Object collectionOrMap = _fullState.get(key);
379         Object returnValue = null;
380         if (collectionOrMap instanceof InternalMap)
381         {
382             if (_createDeltas())
383             {
384                 returnValue = _removeValueOrKeyFromMap(_deltas, key,
385                         valueOrKey, true);
386                 _removeValueOrKeyFromMap(_fullState, key, valueOrKey, false);
387             }
388             else
389             {
390                 returnValue = _removeValueOrKeyFromMap(_fullState, key,
391                         valueOrKey, false);
392             }
393         }
394         else if (collectionOrMap instanceof InternalList)
395         {
396             if (_createDeltas())
397             {
398                 returnValue = _removeValueOrKeyFromCollectionDelta(_deltas,
399                         key, valueOrKey);
400                 _removeValueOrKeyFromCollection(_fullState, key, valueOrKey);
401             }
402             else
403             {
404                 returnValue = _removeValueOrKeyFromCollection(_fullState, key,
405                         valueOrKey);
406             }
407         }
408         return returnValue;
409     }
410 
411     private static Object _removeValueOrKeyFromCollectionDelta(
412             Map<Serializable, Object> stateMap, Serializable key,
413             Object valueOrKey)
414     {
415         Object returnValue = null;
416         Map<Object, Boolean> c = (Map<Object, Boolean>) stateMap.get(key);
417         if (c != null)
418         {
419             if (c.containsKey(valueOrKey))
420             {
421                 returnValue = valueOrKey;
422             }
423             c.put(valueOrKey, Boolean.FALSE);
424         }
425         return returnValue;
426     }
427 
428     private static Object _removeValueOrKeyFromCollection(
429             Map<Serializable, Object> stateMap, Serializable key,
430             Object valueOrKey)
431     {
432         Object returnValue = null;
433         Collection c = (Collection) stateMap.get(key);
434         if (c != null)
435         {
436             if (c.remove(valueOrKey))
437             {
438                 returnValue = valueOrKey;
439             }
440             if (c.isEmpty())
441             {
442                 stateMap.remove(key);
443             }
444         }
445         return returnValue;
446     }
447 
448     private static Object _removeValueOrKeyFromMap(
449             Map<Serializable, Object> stateMap, Serializable key,
450             Object valueOrKey, boolean delta)
451     {
452         if (valueOrKey == null)
453         {
454             return null;
455         }
456 
457         Object returnValue = null;
458         Map<String, Object> map = (Map<String, Object>) stateMap.get(key);
459         if (map != null)
460         {
461             if (delta)
462             {
463                 // Keep track of the removed values using key/null pair on the delta map
464                 returnValue = map.put((String) valueOrKey, null);
465             }
466             else
467             {
468                 returnValue = map.remove(valueOrKey);
469             }
470 
471             if (map.isEmpty())
472             {
473                 //stateMap.remove(key);
474                 stateMap.put(key, null);
475             }
476         }
477         return returnValue;
478     }
479 
480     public boolean isTransient()
481     {
482         return _transient;
483     }
484 
485     /**
486      * Serializing cod
487      * the serialized data structure consists of key value pairs unless the value itself is an internal array
488      * or a map in case of an internal array or map the value itself is another array with its initial value
489      * myfaces.InternalArray, myfaces.internalMap
490      *
491      * the internal Array is then mapped to another array
492      *
493      * the internal Map again is then mapped to a map with key value pairs
494      *
495      *
496      */
497     public Object saveState(FacesContext context)
498     {
499         Map serializableMap = (isInitialStateMarked()) ? _deltas : _fullState;
500 
501         if (serializableMap == null || serializableMap.isEmpty())
502         {
503             return null;
504         }
505         
506         /*
507         int stateHolderKeyCount = 0;
508         if (isInitalStateMarked())
509         {
510             for (Iterator<Serializable> it = _stateHolderKeys.iterator(); it.hasNext();)
511             {
512                 Serializable key = it.next();
513                 if (!_deltas.containsKey(key))
514                 {
515                     stateHolderKeyCount++;
516                 }
517             }
518         }*/
519         
520         Map.Entry<Serializable, Object> entry;
521         //entry == key, value, key, value
522         Object[] retArr = new Object[serializableMap.entrySet().size() * 2];
523         //Object[] retArr = new Object[serializableMap.entrySet().size() * 2 + stateHolderKeyCount]; 
524 
525         Iterator<Map.Entry<Serializable, Object>> it = serializableMap
526                 .entrySet().iterator();
527         int cnt = 0;
528         while (it.hasNext())
529         {
530             entry = it.next();
531             retArr[cnt] = entry.getKey();
532 
533             Object value = entry.getValue();
534             
535             // The condition in which the call to saveAttachedState
536             // is to handle List, StateHolder or non Serializable instances.
537             // we check it here, to prevent unnecessary calls.
538             if (value instanceof StateHolder ||
539                 value instanceof List ||
540                 !(value instanceof Serializable))
541             {
542                 Object savedValue = saveAttachedState(context,
543                     value);
544                 retArr[cnt + 1] = savedValue;
545             }
546             else
547             {
548                 retArr[cnt + 1] = value;
549             }
550             cnt += 2;
551         }
552         
553         /*
554         if (isInitalStateMarked())
555         {
556             for (Iterator<Serializable> it2 = _stateHolderKeys.iterator(); it.hasNext();)
557             {
558                 Serializable key = it2.next();
559                 if (!_deltas.containsKey(key))
560                 {
561                     retArr[cnt] = key;
562                     Object value = _fullState.get(key);
563                     if (value instanceof PartialStateHolder)
564                     {
565                         //Could contain delta, save it as _AttachedDeltaState
566                         PartialStateHolder holder = (PartialStateHolder) value;
567                         if (holder.isTransient())
568                         {
569                             retArr[cnt + 1] = null;
570                         }
571                         else
572                         {
573                             retArr[cnt + 1] = new _AttachedDeltaWrapper(value.getClass(), holder.saveState(context));
574                         }
575                     }
576                     else
577                     {
578                         //Save everything
579                         retArr[cnt + 1] = saveAttachedState(context, _fullState.get(key));
580                     }
581                     cnt += 2;
582                 }
583             }
584         }
585         */       
586         return retArr;
587     }
588 
589     public void restoreState(FacesContext context, Object state)
590     {
591         if (state == null)
592         {
593             return;
594         }
595 
596         Object[] serializedState = (Object[]) state;
597         
598         if (!isInitialStateMarked() && !_fullState.isEmpty())
599         {
600             _fullState.clear();
601             if(_deltas != null)
602             {
603                 _deltas.clear();
604             }
605         }
606 
607         for (int cnt = 0; cnt < serializedState.length; cnt += 2)
608         {
609             Serializable key = (Serializable) serializedState[cnt];
610             Object savedValue = restoreAttachedState(context,
611                     serializedState[cnt + 1]);
612 
613             if (isInitialStateMarked())
614             {
615                 if (savedValue instanceof InternalDeltaListMap)
616                 {
617                     for (Map.Entry<Object, Boolean> mapEntry : ((Map<Object, Boolean>) savedValue)
618                             .entrySet())
619                     {
620                         boolean addOrRemove = mapEntry.getValue();
621                         if (addOrRemove)
622                         {
623                             //add
624                             this.add(key, mapEntry.getKey());
625                         }
626                         else
627                         {
628                             //remove
629                             this.remove(key, mapEntry.getKey());
630                         }
631                     }
632                 }
633                 else if (savedValue instanceof InternalMap)
634                 {
635                     for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) savedValue)
636                             .entrySet())
637                     {
638                         this.put(key, mapEntry.getKey(), mapEntry.getValue());
639                     }
640                 }
641                 /*
642                 else if (savedValue instanceof _AttachedDeltaWrapper)
643                 {
644                     _AttachedStateWrapper wrapper = (_AttachedStateWrapper) savedValue;
645                     //Restore delta state
646                     ((PartialStateHolder)_fullState.get(key)).restoreState(context, wrapper.getWrappedStateObject());
647                     //Add this key as StateHolder key 
648                     _stateHolderKeys.add(key);
649                 }
650                 */
651                 else
652                 {
653                     put(key, savedValue);
654                 }
655             }
656             else
657             {
658                 put(key, savedValue);
659             }
660         }
661     }
662 
663     public void setTransient(boolean transientValue)
664     {
665         _transient = transientValue;
666     }
667 
668     //We use our own data structures just to make sure
669     //nothing gets mixed up internally
670     static class InternalMap<K, V> extends HashMap<K, V> implements StateHolder
671     {
672         public InternalMap()
673         {
674             super();
675         }
676 
677         public InternalMap(int initialCapacity, float loadFactor)
678         {
679             super(initialCapacity, loadFactor);
680         }
681 
682         public InternalMap(Map<? extends K, ? extends V> m)
683         {
684             super(m);
685         }
686 
687         public InternalMap(int initialSize)
688         {
689             super(initialSize);
690         }
691 
692         public boolean isTransient()
693         {
694             return false;
695         }
696 
697         public void setTransient(boolean newTransientValue)
698         {
699             // No op
700         }
701 
702         public void restoreState(FacesContext context, Object state)
703         {
704             Object[] listAsMap = (Object[]) state;
705             for (int cnt = 0; cnt < listAsMap.length; cnt += 2)
706             {
707                 this.put((K) listAsMap[cnt], (V) UIComponentBase
708                         .restoreAttachedState(context, listAsMap[cnt + 1]));
709             }
710         }
711 
712         public Object saveState(FacesContext context)
713         {
714             int cnt = 0;
715             Object[] mapArr = new Object[this.size() * 2];
716             for (Map.Entry<K, V> entry : this.entrySet())
717             {
718                 mapArr[cnt] = entry.getKey();
719                 Object value = entry.getValue();
720                 
721                 if (value instanceof StateHolder ||
722                     value instanceof List ||
723                     !(value instanceof Serializable))
724                 {
725                     mapArr[cnt + 1] = saveAttachedState(context, value);
726                 }
727                 else
728                 {
729                     mapArr[cnt + 1] = value;
730                 }
731                 cnt += 2;
732             }
733             return mapArr;
734         }
735     }
736 
737     /**
738      * Map used to keep track of list changes 
739      */
740     static class InternalDeltaListMap<K, V> extends InternalMap<K, V>
741     {
742 
743         public InternalDeltaListMap()
744         {
745             super();
746         }
747 
748         public InternalDeltaListMap(int initialCapacity, float loadFactor)
749         {
750             super(initialCapacity, loadFactor);
751         }
752 
753         public InternalDeltaListMap(int initialSize)
754         {
755             super(initialSize);
756         }
757 
758         public InternalDeltaListMap(Map<? extends K, ? extends V> m)
759         {
760             super(m);
761         }
762     }
763 
764     static class InternalList<T> extends ArrayList<T> implements StateHolder
765     {
766         public InternalList()
767         {
768             super();
769         }
770 
771         public InternalList(Collection<? extends T> c)
772         {
773             super(c);
774         }
775 
776         public InternalList(int initialSize)
777         {
778             super(initialSize);
779         }
780 
781         public boolean isTransient()
782         {
783             return false;
784         }
785 
786         public void setTransient(boolean newTransientValue)
787         {
788         }
789 
790         public void restoreState(FacesContext context, Object state)
791         {
792             Object[] listAsArr = (Object[]) state;
793             //since all other options would mean dual iteration 
794             //we have to do it the hard way
795             for (Object elem : listAsArr)
796             {
797                 add((T) restoreAttachedState(context, elem));
798             }
799         }
800 
801         public Object saveState(FacesContext context)
802         {
803             Object[] values = new Object[size()];
804             for (int i = 0; i < size(); i++)
805             {
806                 Object value = get(i);
807                 
808                 if (value instanceof StateHolder ||
809                     value instanceof List ||
810                     !(value instanceof Serializable))
811                 {
812                     values[i] = saveAttachedState(context, value);
813                 }
814                 else
815                 {
816                     values[i] = value;
817                 }                
818             }
819             return values;
820         }
821     }
822     
823     private static Object saveAttachedState(FacesContext context, Object attachedObject)
824     {
825         if (context == null)
826         {
827             throw new NullPointerException ("context");
828         }
829         
830         if (attachedObject == null)
831         {
832             return null;
833         }
834         // StateHolder interface should take precedence over
835         // List children
836         if (attachedObject instanceof StateHolder)
837         {
838             StateHolder holder = (StateHolder) attachedObject;
839             if (holder.isTransient())
840             {
841                 return null;
842             }
843 
844             return new _AttachedStateWrapper(attachedObject.getClass(), holder.saveState(context));
845         }        
846         else if (attachedObject instanceof List)
847         {
848             List<Object> lst = new ArrayList<Object>(((List<?>) attachedObject).size());
849             for (Object item : (List<?>) attachedObject)
850             {
851                 if (item != null)
852                 {
853                     lst.add(saveAttachedState(context, item));
854                 }
855             }
856 
857             return new _AttachedListStateWrapper(lst);
858         }
859         else if (attachedObject instanceof Serializable)
860         {
861             return attachedObject;
862         }
863         else
864         {
865             return new _AttachedStateWrapper(attachedObject.getClass(), null);
866         }
867     }
868 
869     private static Object restoreAttachedState(FacesContext context, Object stateObj) throws IllegalStateException
870     {
871         if (context == null)
872         {
873             throw new NullPointerException("context");
874         }
875         if (stateObj == null)
876         {
877             return null;
878         }
879         if (stateObj instanceof _AttachedListStateWrapper)
880         {
881             List<Object> lst = ((_AttachedListStateWrapper) stateObj).getWrappedStateList();
882             List<Object> restoredList = new ArrayList<Object>(lst.size());
883             for (Object item : lst)
884             {
885                 restoredList.add(restoreAttachedState(context, item));
886             }
887             return restoredList;
888         }
889         else if (stateObj instanceof _AttachedStateWrapper)
890         {
891             Class<?> clazz = ((_AttachedStateWrapper) stateObj).getClazz();
892             Object restoredObject;
893             try
894             {
895                 restoredObject = clazz.newInstance();
896             }
897             catch (InstantiationException e)
898             {
899                 throw new RuntimeException("Could not restore StateHolder of type " + clazz.getName()
900                         + " (missing no-args constructor?)", e);
901             }
902             catch (IllegalAccessException e)
903             {
904                 throw new RuntimeException(e);
905             }
906             if (restoredObject instanceof StateHolder)
907             {
908                 _AttachedStateWrapper wrapper = (_AttachedStateWrapper) stateObj;
909                 Object wrappedState = wrapper.getWrappedStateObject();
910 
911                 StateHolder holder = (StateHolder) restoredObject;
912                 holder.restoreState(context, wrappedState);
913             }
914             return restoredObject;
915         }
916         else
917         {
918             return stateObj;
919         }
920     }
921 }