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