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, the state will be for the component
107    * itself, not including it's child/facet state.
108    */
109   public static Object saveStampState(FacesContext context, UIComponent stamp)
110   {
111     RowState state = _createState(stamp);
112     return state;
113   }
114 
115   /**
116    * Restore the per-row state of a given stamp.
117    */
118   public static void restoreStampState(FacesContext context, UIComponent stamp,
119                                        Object stampState)
120   {
121     if (stampState != null)
122     {
123       RowState state = (RowState) stampState;
124       state.restoreRowState(stamp);
125     }
126   }
127 
128   /**
129    * save the stamp state of just the children of the given component
130    * in the given table.
131    */
132   @SuppressWarnings("unchecked")
133   public static Object saveChildStampState(
134     FacesContext context,
135     UIComponent   stamp,
136     UIXCollection table)
137   {
138     int childCount = stamp.getChildCount();
139     // If we have any children, iterate through the map,
140     // saving state
141     if (childCount == 0)
142       return null;
143 
144     Map<String, Object> childStateMap = null;
145     List<UIComponent> children = stamp.getChildren();
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 map.
152       // So: allocate the map if we encounter our first
153       // non-null childState
154 
155       if (childState == null)
156         continue;
157 
158       if (childStateMap == null)
159       {
160         childStateMap = new HashMap<String, Object>(childCount);
161       }
162 
163       // Store a value into the array
164       childStateMap.put(child.getId(), childState);
165     }
166 
167     return childStateMap;
168   }
169 
170   /**
171    * Restore the stamp state of just the children of the given component
172    * in the given table.
173    */
174   @SuppressWarnings("unchecked")
175   public static void restoreChildStampState(
176     FacesContext context,
177     UIComponent stamp,
178     UIXCollection table,
179     Object stampState)
180   {
181     if (stampState == null || !(stampState instanceof Map))
182       return;
183 
184     List<UIComponent> kids = stamp.getChildren();
185     Map<String, Object> state = (Map<String, Object>)stampState;
186 
187     for (UIComponent kid : kids)
188     {
189       Object childState = state.get(kid.getId());
190       if (childState == null)
191         continue;
192 
193       table.restoreStampState(context, kid, childState);
194     }
195   }
196 
197   /**
198    * @todo can do better...
199    */
200   public void writeExternal(ObjectOutput out) throws IOException
201   {
202     out.writeInt(_rows.size());
203 
204     if (_rows.isEmpty())
205       return;
206 
207     HashMap<DualKey, Object> map = new HashMap<DualKey, Object>(_rows.size());
208     map.putAll(_rows);
209 
210     if (_LOG.isFinest())
211     {
212       for(Map.Entry<DualKey, Object> entry : map.entrySet())
213       {
214         _LOG.finest("Saving " + entry.getKey() + ", " + entry.getValue());
215       }
216     }
217 
218     out.writeObject(map);
219   }
220 
221   @SuppressWarnings("unchecked")
222   public void readExternal(ObjectInput in)
223     throws IOException, ClassNotFoundException
224   {
225     int size = in.readInt();
226 
227     if (size > 0)
228     _rows = (Map<DualKey, Object>) in.readObject();
229 
230     if (_LOG.isFinest())
231     {
232       for(Map.Entry<DualKey, Object> entry : _rows.entrySet())
233       {
234         _LOG.finest("Restoring " + entry.getKey() + ", " + entry.getValue());
235       }
236     }
237   }
238 
239   private static RowState _createState(UIComponent child)
240   {
241     RowState state;
242     if (child instanceof EditableValueHolder)
243     {
244       state = new EVHState();
245       state.saveRowState(child);
246     }
247     else if (child instanceof UIXCollection)
248     {
249       state = new TableState();
250       state.saveRowState(child);
251     }
252     else if (child instanceof UIXShowDetail)
253     {
254       state = SDState.getState((UIXShowDetail) child);
255     }
256     else
257     {
258       state = null;
259     }
260 
261     return state;
262   }
263 
264   private static boolean _eq(Object k1, Object k2)
265   {
266     if (k1 == null)
267       return k2 == null;
268     return k1.equals(k2);
269   }
270 
271   // State for a single row
272   static private abstract class RowState implements Serializable
273   {
274 
275     public RowState()
276     {
277     }
278 
279     abstract public void saveRowState(UIComponent child);
280 
281     abstract public void restoreRowState(UIComponent child);
282 
283     abstract public boolean isNull();
284     
285     private static final long serialVersionUID = 1L;
286   }
287 
288   static private final class SDState extends RowState
289   {
290     /**
291      * Return cached, shared instances of SDState.
292      */
293     static public RowState getState(UIXShowDetail child)
294     {
295       FacesBean bean = child.getFacesBean();
296       Boolean disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
297       if (disclosed == null)
298         return _NULL;
299       else if (disclosed)
300         return _TRUE;
301       else
302         return _FALSE;
303     }
304 
305     public SDState()
306     {
307     }
308 
309     private SDState(Boolean disclosed)
310     {
311       _disclosed = disclosed;
312     }
313 
314     @Override
315     public void saveRowState(UIComponent child)
316     {
317       FacesBean bean = ((UIXShowDetail)child).getFacesBean();
318       _disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
319     }
320 
321     @Override
322     public void restoreRowState(UIComponent child)
323     {
324       FacesBean bean = ((UIXShowDetail)child).getFacesBean();
325       bean.setProperty(UIXShowDetail.DISCLOSED_KEY, _disclosed);
326     }
327 
328     @Override
329     public boolean isNull()
330     {
331       return _disclosed == null;
332     }
333 
334     @Override
335     public String toString()
336     {
337       return "SDState[disclosed=" + _disclosed + "]";
338     }
339 
340     // Reusable instances of SDState. TODO: use readResolve/writeReplace
341     // so that we only send across and restore instances of these
342     static private final SDState _TRUE = new SDState(true);
343     static private final SDState _FALSE = new SDState(false);
344     static private final SDState _NULL = new SDState(null);
345 
346     /**
347      *
348      */
349     private static final long serialVersionUID = -8605916495935317932L;
350 
351     private Boolean _disclosed;
352   }
353 
354   static private final class TableState extends RowState
355   {
356     public TableState()
357     {
358     }
359 
360     @Override
361     public void saveRowState(UIComponent child)
362     {
363       _state = ((UIXCollection) child).__getMyStampState();
364     }
365 
366     @Override
367     public void restoreRowState(UIComponent child)
368     {
369       //There is a bug in the RI where, on a PPR request, an UIPanel will sometimes be
370       //added to a facet that contains only one child in facelets.  This, of course,
371       //changes the structure of the saved data.  If we get a UIPanel here, then this
372       //but is the cause.  It means we have a Collection which contains a switcher
373       //which in turn contains another Collection, and UIPanel was returned instead of
374       //the origional collection.  Therefore, this UIPanel should ALWAYS only have one
375       //Item.  If the facet contained more then one item, we would ALWAYS have a UIPanel
376       //and therefore we would be using a different stamp state.
377       UIXCollection myChild = (child instanceof UIPanel)?(UIXCollection)child.getChildren().get(0):(UIXCollection)child;
378       
379       myChild.__setMyStampState(_state);
380     }
381 
382     @Override
383     public boolean isNull()
384     {
385       return _state == null;
386     }
387 
388     private Object _state = null;
389 
390     private static final long serialVersionUID = 1L;
391   }
392 
393   static private class EVHState extends RowState
394   {
395     public EVHState()
396     {
397       _valid = true;
398     }
399 
400     @Override
401     public void saveRowState(UIComponent child)
402     {
403       assert _assertIsStampCorrect(child);
404 
405       EditableValueHolder evh = (EditableValueHolder) child;
406       _submitted = evh.getSubmittedValue();
407       _localSet = evh.isLocalValueSet();
408       _local = evh.getLocalValue();
409       _valid = evh.isValid();
410     }
411 
412     @Override
413     public void restoreRowState(UIComponent child)
414     {
415       assert _assertIsStampCorrect(child);
416 
417       EditableValueHolder evh = (EditableValueHolder) child;
418       evh.setSubmittedValue(_submitted);
419       evh.setValue(_local);
420       evh.setLocalValueSet(_localSet);
421       evh.setValid(_valid);
422 
423       assert _assertStampHonoursState(evh);
424     }
425 
426     @Override
427     public boolean isNull()
428     {
429       return (_valid && (!_localSet) && (_submitted == null));
430     }
431 
432 
433     @Override
434     public String toString()
435     {
436       return "EVHState[value=" + _local + ",submitted=" + _submitted + "]";
437     }
438 
439     private boolean _assertStampHonoursState(EditableValueHolder evh)
440     {
441       return (evh.getSubmittedValue() == _submitted) &&
442         (evh.getLocalValue() == _local) &&
443         (evh.isLocalValueSet() == _localSet) &&
444         (evh.isValid() == _valid);
445     }
446 
447     /**
448      * Make sure that this stampState is used to save/restore state from the
449      * same stamp each time.
450      */
451     private boolean _assertIsStampCorrect(UIComponent stamp)
452     {
453       if (_assertStamp != null)
454       {
455         String stampId = stamp.getId();
456         String assertStampId = _assertStamp.getId();
457         assert (((assertStampId == null) && (stampId == null)) ||
458                 ((assertStampId != null) && assertStampId.equals(stampId))) :
459           "Current stamp:"+stamp+
460           " with id:"+stamp.getId()+
461           ". Previously stamp was:"+_assertStamp+
462           " with id:"+_assertStamp.getId();
463       }
464       else
465       {
466         _assertStamp = stamp;
467       }
468       return true;
469     }
470 
471     private Object _submitted;
472     private Object _local;
473     private boolean _localSet;
474     private boolean _valid;
475     private transient UIComponent _assertStamp = null;
476 
477     private static final long serialVersionUID = 1L;
478   }
479 
480   private static final class DualKey implements Serializable
481   {
482     public DualKey(Object key1, Object key2)
483     {
484       _key1 = key1;
485       _key2 = key2;
486 
487       _hash = _hash(key1) + _hash(key2);
488     }
489 
490     @Override
491     public boolean equals(Object other)
492     {
493       if (other == this)
494         return true;
495       if (other instanceof DualKey)
496       {
497         DualKey otherKey = (DualKey) other;
498         if (hashCode() != otherKey.hashCode())
499           return false;
500 
501         return _eq(_key1, otherKey._key1) && _eq(_key2, otherKey._key2);
502       }
503       return false;
504     }
505 
506     @Override
507     public int hashCode()
508     {
509       return _hash;
510     }
511 
512     @Override
513     public String toString()
514     {
515       return "<"+_key1+","+_key2+">";
516     }
517 
518     private static int _hash(Object k)
519     {
520       return (k==null) ? 0 : k.hashCode();
521     }
522 
523     private final Object _key1, _key2;
524     private final int _hash;
525 
526     private static final long serialVersionUID = 1L;
527   }
528 
529   private static final TrinidadLogger _LOG =
530      TrinidadLogger.createTrinidadLogger(StampState.class);
531 
532   private Map<DualKey, Object> _rows;
533   private static final Object[] _EMPTY_ARRAY = new Object[0];
534   private static final long serialVersionUID = 1L;
535 }