View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.component;
20  
21  import java.io.Externalizable;
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  import java.io.Serializable;
26  
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.faces.component.EditableValueHolder;
34  import javax.faces.component.UIComponent;
35  import javax.faces.component.UIPanel;
36  import javax.faces.context.FacesContext;
37  
38  import org.apache.myfaces.trinidad.bean.FacesBean;
39  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
40  
41  
42  /**
43   * This class saves the state of stamp components.
44   */
45  final class StampState implements Externalizable
46  {
47    public StampState()
48    {
49      _rows = Collections.emptyMap();
50    }
51  
52    /**
53     * Clears all state except for the state associated with the
54     * give currencyObj
55     * @param skipCurrencyObj
56     */
57    public void clear(Object skipCurrencyObj)
58    {
59      if (!_rows.isEmpty())
60      {
61        Iterator<DualKey> iter = _rows.keySet().iterator();
62        while(iter.hasNext())
63        {
64          DualKey dk = iter.next();
65          if (_eq(dk._key1, skipCurrencyObj))
66            continue;
67          iter.remove();
68        }
69      }
70    }
71  
72    public void put(Object currencyObj, String key, Object value)
73    {
74      Map<DualKey, Object> comparant = Collections.emptyMap();
75      if (_rows == comparant)
76      {
77        if (value == null)
78          return;
79  
80        // =-=AEW Better default sizes
81        _rows = new HashMap<DualKey, Object>(109);
82      }
83  
84      DualKey dk = new DualKey(currencyObj, key);
85      // Make sure that if we're applying a null value, that we
86      // don't hold on to the key and retain the entry - just nuke
87      // the entry
88      if (value == null)
89        _rows.remove(dk);
90      else
91        _rows.put(dk, value);
92    }
93  
94    public int size()
95    {
96      return _rows.size();
97    }
98  
99    public Object get(Object currencyObj, String key)
100   {
101     DualKey dk = new DualKey(currencyObj, key);
102     return _rows.get(dk);
103   }
104 
105   /**
106    * Save the per-row state of a given stamp.
107    */
108   public static Object saveStampState(FacesContext context, UIComponent stamp)
109   {
110     RowState state = _createState(stamp);
111     return state;
112   }
113 
114   /**
115    * Restore the per-row state of a given stamp.
116    */
117   public static void restoreStampState(FacesContext context, UIComponent stamp,
118                                        Object stampState)
119   {
120     if (stampState != null)
121     {
122       RowState state = (RowState) stampState;
123       state.restoreRowState(stamp);
124     }
125   }
126 
127   /**
128    * save the stamp state of just the children of the given component
129    * in the given table.
130    */
131   @SuppressWarnings("unchecked")
132   public static Object saveChildStampState(
133     FacesContext context,
134     UIComponent   stamp,
135     UIXCollection table)
136   {
137     int childCount = stamp.getChildCount();
138     // If we have any children, iterate through the array,
139     // saving state
140     if (childCount == 0)
141       return null;
142 
143     Object[] childStateArray = null;
144     List<UIComponent> children = stamp.getChildren();
145     boolean childStateIsEmpty = true;
146     for(int i=0; i < childCount; i++)
147     {
148       UIComponent child = children.get(i);
149       Object childState = table.saveStampState(context, child);
150 
151       // Until we have one non-null entry, don't allocate the array.
152       // Unlike facets, we *do* care about stashing Transient.TRUE,
153       // because we have to keep track of them relative to any
154       // later components, BUT if it's all null and transient, we
155       // can discard the array.  This does mean that putting
156       // transient components into a stamp is a bit inefficient
157 
158       // So: allocate the array if we encounter our first
159       // non-null childState (even if it's transient)
160       if (childStateArray == null)
161       {
162         if (childState == null)
163           continue;
164 
165         childStateArray = new Object[childCount];
166       }
167 
168       // But remember the moment we've encountered a non-null
169       // *and* non-transient component, because that means we'll
170       // really need to keep this array
171       if ((childState != UIXCollection.Transient.TRUE) && (childState != null))
172         childStateIsEmpty = false;
173 
174       // Store a value into the array
175       assert(childStateArray != null);
176       childStateArray[i] = childState;
177     }
178 
179     // Even if we bothered to allocate an array, if all we
180     // had were transient + null, don't bother with the array at all
181     if (childStateIsEmpty)
182       return null;
183 
184     return childStateArray;
185   }
186 
187   /**
188    * Restore the stamp state of just the children of the given component
189    * in the given table.
190    */
191   @SuppressWarnings("unchecked")
192   public static void restoreChildStampState(
193     FacesContext context,
194     UIComponent stamp,
195     UIXCollection table,
196     Object stampState)
197   {
198     if (stampState == null)
199       return;
200 
201     List<UIComponent> kids = stamp.getChildren();
202     Object[] state = (Object[]) stampState;
203 
204     int childIndex = 0;
205     for(int i=0; i<state.length; i++)
206     {
207       Object childState = state[i];
208       // Skip over any saved state that corresponds to transient
209       // components
210       if (childState != UIXCollection.Transient.TRUE)
211       {
212         while (childIndex < kids.size())
213         {
214           UIComponent kid = kids.get(childIndex);
215           childIndex++;
216           // Skip over any transient components before restoring state
217           if (!kid.isTransient())
218           {
219             table.restoreStampState(context, kid, childState);
220             break;
221           }
222         }
223       }
224       // The component may or may not still be there;  if it
225       // is, then we'd better skip over it
226       else
227       {
228         if (childIndex < kids.size())
229         {
230           UIComponent child = kids.get(childIndex);
231           // If the child isn't transient, then it must be
232           // something that we want to look at on the next
233           // iteration.
234           if (child.isTransient())
235             childIndex++;
236         }
237       }
238     }
239   }
240 
241   /**
242    * @todo can do better...
243    */
244   public void writeExternal(ObjectOutput out) throws IOException
245   {
246     out.writeInt(_rows.size());
247 
248     if (_rows.isEmpty())
249       return;
250 
251     HashMap<DualKey, Object> map = new HashMap<DualKey, Object>(_rows.size());
252     map.putAll(_rows);
253 
254     if (_LOG.isFinest())
255     {
256       for(Map.Entry<DualKey, Object> entry : map.entrySet())
257       {
258         _LOG.finest("Saving " + entry.getKey() + ", " + entry.getValue());
259       }
260     }
261 
262     out.writeObject(map);
263   }
264 
265   @SuppressWarnings("unchecked")
266   public void readExternal(ObjectInput in)
267     throws IOException, ClassNotFoundException
268   {
269     int size = in.readInt();
270 
271     if (size > 0)
272     _rows = (Map<DualKey, Object>) in.readObject();
273 
274     if (_LOG.isFinest())
275     {
276       for(Map.Entry<DualKey, Object> entry : _rows.entrySet())
277       {
278         _LOG.finest("Restoring " + entry.getKey() + ", " + entry.getValue());
279       }
280     }
281   }
282 
283   private static RowState _createState(UIComponent child)
284   {
285     RowState state;
286     if (child instanceof EditableValueHolder)
287     {
288       state = new EVHState();
289       state.saveRowState(child);
290     }
291     else if (child instanceof UIXCollection)
292     {
293       state = new TableState();
294       state.saveRowState(child);
295     }
296     else if (child instanceof UIXShowDetail)
297     {
298       state = SDState.getState((UIXShowDetail) child);
299     }
300     else
301     {
302       state = null;
303     }
304 
305     return state;
306   }
307 
308   private static boolean _eq(Object k1, Object k2)
309   {
310     if (k1 == null)
311       return k2 == null;
312     return k1.equals(k2);
313   }
314 
315   // State for a single row
316   static private abstract class RowState implements Serializable
317   {
318 
319     public RowState()
320     {
321     }
322 
323     abstract public void saveRowState(UIComponent child);
324 
325     abstract public void restoreRowState(UIComponent child);
326 
327     abstract public boolean isNull();
328     
329     private static final long serialVersionUID = 1L;
330   }
331 
332   static private final class SDState extends RowState
333   {
334     /**
335      * Return cached, shared instances of SDState.
336      */
337     static public RowState getState(UIXShowDetail child)
338     {
339       FacesBean bean = child.getFacesBean();
340       Boolean disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
341       if (disclosed == null)
342         return _NULL;
343       else if (disclosed)
344         return _TRUE;
345       else
346         return _FALSE;
347     }
348 
349     public SDState()
350     {
351     }
352 
353     private SDState(Boolean disclosed)
354     {
355       _disclosed = disclosed;
356     }
357 
358     @Override
359     public void saveRowState(UIComponent child)
360     {
361       FacesBean bean = ((UIXShowDetail)child).getFacesBean();
362       _disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
363     }
364 
365     @Override
366     public void restoreRowState(UIComponent child)
367     {
368       FacesBean bean = ((UIXShowDetail)child).getFacesBean();
369       bean.setProperty(UIXShowDetail.DISCLOSED_KEY, _disclosed);
370     }
371 
372     @Override
373     public boolean isNull()
374     {
375       return _disclosed == null;
376     }
377 
378     @Override
379     public String toString()
380     {
381       return "SDState[disclosed=" + _disclosed + "]";
382     }
383 
384     // Reusable instances of SDState. TODO: use readResolve/writeReplace
385     // so that we only send across and restore instances of these
386     static private final SDState _TRUE = new SDState(true);
387     static private final SDState _FALSE = new SDState(false);
388     static private final SDState _NULL = new SDState(null);
389 
390     /**
391      *
392      */
393     private static final long serialVersionUID = -8605916495935317932L;
394 
395     private Boolean _disclosed;
396   }
397 
398   static private final class TableState extends RowState
399   {
400     public TableState()
401     {
402     }
403 
404     @Override
405     public void saveRowState(UIComponent child)
406     {
407       _state = ((UIXCollection) child).__getMyStampState();
408     }
409 
410     @Override
411     public void restoreRowState(UIComponent child)
412     {
413       //There is a bug in the RI where, on a PPR request, an UIPanel will sometimes be
414       //added to a facet that contains only one child in facelets.  This, of course,
415       //changes the structure of the saved data.  If we get a UIPanel here, then this
416       //but is the cause.  It means we have a Collection which contains a switcher
417       //which in turn contains another Collection, and UIPanel was returned instead of
418       //the origional collection.  Therefore, this UIPanel should ALWAYS only have one
419       //Item.  If the facet contained more then one item, we would ALWAYS have a UIPanel
420       //and therefore we would be using a different stamp state.
421       UIXCollection myChild = (child instanceof UIPanel)?(UIXCollection)child.getChildren().get(0):(UIXCollection)child;
422       
423       myChild.__setMyStampState(_state);
424     }
425 
426     @Override
427     public boolean isNull()
428     {
429       return _state == null;
430     }
431 
432     private Object _state = null;
433 
434     private static final long serialVersionUID = 1L;
435   }
436 
437   static private class EVHState extends RowState
438   {
439     public EVHState()
440     {
441       _valid = true;
442     }
443 
444     @Override
445     public void saveRowState(UIComponent child)
446     {
447       assert _assertIsStampCorrect(child);
448 
449       EditableValueHolder evh = (EditableValueHolder) child;
450       _submitted = evh.getSubmittedValue();
451       _localSet = evh.isLocalValueSet();
452       _local = evh.getLocalValue();
453       _valid = evh.isValid();
454     }
455 
456     @Override
457     public void restoreRowState(UIComponent child)
458     {
459       assert _assertIsStampCorrect(child);
460 
461       EditableValueHolder evh = (EditableValueHolder) child;
462       evh.setSubmittedValue(_submitted);
463       evh.setValue(_local);
464       evh.setLocalValueSet(_localSet);
465       evh.setValid(_valid);
466 
467       assert _assertStampHonoursState(evh);
468     }
469 
470     @Override
471     public boolean isNull()
472     {
473       return (_valid && (!_localSet) && (_submitted == null));
474     }
475 
476 
477     @Override
478     public String toString()
479     {
480       return "EVHState[value=" + _local + ",submitted=" + _submitted + "]";
481     }
482 
483     private boolean _assertStampHonoursState(EditableValueHolder evh)
484     {
485       return (evh.getSubmittedValue() == _submitted) &&
486         (evh.getLocalValue() == _local) &&
487         (evh.isLocalValueSet() == _localSet) &&
488         (evh.isValid() == _valid);
489     }
490 
491     /**
492      * Make sure that this stampState is used to save/restore state from the
493      * same stamp each time.
494      */
495     private boolean _assertIsStampCorrect(UIComponent stamp)
496     {
497       if (_assertStamp != null)
498       {
499         String stampId = stamp.getId();
500         String assertStampId = _assertStamp.getId();
501         assert (((assertStampId == null) && (stampId == null)) ||
502                 ((assertStampId != null) && assertStampId.equals(stampId))) :
503           "Current stamp:"+stamp+
504           " with id:"+stamp.getId()+
505           ". Previously stamp was:"+_assertStamp+
506           " with id:"+_assertStamp.getId();
507       }
508       else
509       {
510         _assertStamp = stamp;
511       }
512       return true;
513     }
514 
515     private Object _submitted;
516     private Object _local;
517     private boolean _localSet;
518     private boolean _valid;
519     private transient UIComponent _assertStamp = null;
520 
521     private static final long serialVersionUID = 1L;
522   }
523 
524   private static final class DualKey implements Serializable
525   {
526     public DualKey(Object key1, Object key2)
527     {
528       _key1 = key1;
529       _key2 = key2;
530 
531       _hash = _hash(key1) + _hash(key2);
532     }
533 
534     @Override
535     public boolean equals(Object other)
536     {
537       if (other == this)
538         return true;
539       if (other instanceof DualKey)
540       {
541         DualKey otherKey = (DualKey) other;
542         if (hashCode() != otherKey.hashCode())
543           return false;
544 
545         return _eq(_key1, otherKey._key1) && _eq(_key2, otherKey._key2);
546       }
547       return false;
548     }
549 
550     @Override
551     public int hashCode()
552     {
553       return _hash;
554     }
555 
556     @Override
557     public String toString()
558     {
559       return "<"+_key1+","+_key2+">";
560     }
561 
562     private static int _hash(Object k)
563     {
564       return (k==null) ? 0 : k.hashCode();
565     }
566 
567     private final Object _key1, _key2;
568     private final int _hash;
569 
570     private static final long serialVersionUID = 1L;
571   }
572 
573   private static final TrinidadLogger _LOG =
574      TrinidadLogger.createTrinidadLogger(StampState.class);
575 
576   private Map<DualKey, Object> _rows;
577   private static final Object[] _EMPTY_ARRAY = new Object[0];
578   private static final long serialVersionUID = 1L;
579 }