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  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import javax.faces.component.EditableValueHolder;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  
35  import org.apache.myfaces.trinidad.bean.FacesBean;
36  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
37  
38  /**
39   * This class saves the state of stamp components.
40   */
41  final class StampState implements Externalizable
42  {
43    public StampState()
44    {
45      _rows = Collections.emptyMap();
46    }
47  
48    /**
49     * Clears all state except for the state associated with the
50     * give currencyObj
51     * @param skipCurrencyObj
52     */
53    public void clear(Object skipCurrencyObj)
54    {
55      if (!_rows.isEmpty())
56      {
57        Iterator<DualKey> iter = _rows.keySet().iterator();
58        while(iter.hasNext())
59        {
60          DualKey dk = iter.next();
61          if (_eq(dk._key1, skipCurrencyObj))
62            continue;
63          iter.remove();
64        }
65      }
66    }
67  
68    public void put(Object currencyObj, String key, Object value)
69    {
70      Map<DualKey, Object> comparant = Collections.emptyMap();
71      if (_rows == comparant)
72      {
73        if (value == null)
74          return;
75  
76        // =-=AEW Better default sizes
77        _rows = new HashMap<DualKey, Object>(109);
78      }
79  
80      DualKey dk = new DualKey(currencyObj, key);
81      // Make sure that if we're applying a null value, that we 
82      // don't hold on to the key and retain the entry - just nuke
83      // the entry
84      if (value == null)
85        _rows.remove(dk);
86      else
87        _rows.put(dk, value);
88    }
89  
90    public int size()
91    {
92      return _rows.size();
93    }
94  
95    public Object get(Object currencyObj, String key)
96    {
97      DualKey dk = new DualKey(currencyObj, key);
98      return _rows.get(dk);
99    }
100 
101   /**
102    * Save the per-row state of a given stamp.
103    */
104   public static Object saveStampState(FacesContext context, UIComponent stamp)
105   {
106     RowState state = _createState(stamp);
107     return state;
108   }
109 
110   /**
111    * Restore the per-row state of a given stamp.
112    */
113   public static void restoreStampState(FacesContext context, UIComponent stamp,
114                                        Object stampState)
115   {
116     if (stampState != null)
117     {
118       String stampId = stamp.getId();
119       // Force the ID to be reset to reset the client identifier (needed
120       // for UIComponentBase implementation which caches clientId too
121       // aggressively)
122       stamp.setId(stampId);
123 
124       RowState state = (RowState) stampState;
125       state.restoreRowState(stamp);
126     }
127   }
128 
129   /**
130    * save the stamp state of just the children of the given component
131    * in the given table.
132    */
133   @SuppressWarnings("unchecked")
134   public static Object saveChildStampState(
135     FacesContext context,
136     UIComponent   stamp,
137     UIXCollection table)
138   {
139     int childCount = stamp.getChildCount();
140     // If we have any children, iterate through the array,
141     // saving state
142     if (childCount == 0)
143       return null;
144 
145     Object[] childStateArray = null;
146     List<UIComponent> children = stamp.getChildren();
147     boolean childStateIsEmpty = true;
148     for(int i=0; i < childCount; i++)
149     {
150       UIComponent child = children.get(i);
151       Object childState = table.saveStampState(context, child);
152 
153       // Until we have one non-null entry, don't allocate the array.
154       // Unlike facets, we *do* care about stashing Transient.TRUE,
155       // because we have to keep track of them relative to any
156       // later components, BUT if it's all null and transient, we 
157       // can discard the array.  This does mean that putting 
158       // transient components into a stamp is a bit inefficient
159       
160       // So: allocate the array if we encounter our first
161       // non-null childState (even if it's transient)
162       if (childStateArray == null)
163       {
164         if (childState == null)
165           continue;
166         
167         childStateArray = new Object[childCount];
168       }
169       
170       // But remember the moment we've encountered a non-null
171       // *and* non-transient component, because that means we'll
172       // really need to keep this array
173       if ((childState != UIXCollection.Transient.TRUE) && (childState != null))
174         childStateIsEmpty = false;
175       
176       // Store a value into the array
177       assert(childStateArray != null);
178       childStateArray[i] = childState;
179     }
180 
181     // Even if we bothered to allocate an array, if all we 
182     // had were transient + null, don't bother with the array at all
183     if (childStateIsEmpty)
184       return null;
185 
186     return childStateArray;
187   }
188 
189   /**
190    * Restore the stamp state of just the children of the given component
191    * in the given table.
192    */
193   @SuppressWarnings("unchecked")
194   public static void restoreChildStampState(
195     FacesContext context,
196     UIComponent stamp,
197     UIXCollection table,
198     Object stampState)
199   {
200     if (stampState == null)
201       return;
202 
203     List<UIComponent> kids = stamp.getChildren();
204     Object[] state = (Object[]) stampState;
205 
206     int childIndex = 0;
207     for(int i=0; i<state.length; i++)
208     {
209       Object childState = state[i];
210       // Skip over any saved state that corresponds to transient
211       // components
212       if (childState != UIXCollection.Transient.TRUE)
213       {
214         while (childIndex < kids.size())
215         {
216           UIComponent kid = kids.get(childIndex);
217           childIndex++;
218           // Skip over any transient components before restoring state
219           if (!kid.isTransient())
220           {
221             table.restoreStampState(context, kid, childState);
222             break;
223           }
224         }
225       }
226       // The component may or may not still be there;  if it
227       // is, then we'd better skip over it
228       else
229       {
230         if (childIndex < kids.size())
231         {
232           UIComponent child = kids.get(childIndex);
233           // If the child isn't transient, then it must be
234           // something that we want to look at on the next
235           // iteration.
236           if (child.isTransient())
237             childIndex++;
238         }
239       }
240     }
241   }
242 
243   /**
244    * @todo can do better...
245    */
246   public void writeExternal(ObjectOutput out) throws IOException
247   {
248     out.writeInt(_rows.size());
249 
250     if (_rows.isEmpty())
251       return;
252 
253     HashMap<DualKey, Object> map = new HashMap<DualKey, Object>(_rows.size());
254     map.putAll(_rows);
255 
256     if (_LOG.isFinest())
257     {
258       for(Map.Entry<DualKey, Object> entry : map.entrySet())
259       {
260         _LOG.finest("Saving " + entry.getKey() + ", " + entry.getValue());
261       }
262     }
263 
264     out.writeObject(map);
265   }
266 
267   @SuppressWarnings("unchecked")
268   public void readExternal(ObjectInput in)
269     throws IOException, ClassNotFoundException
270   {
271     int size = in.readInt();
272 
273     if (size > 0)
274     _rows = (Map<DualKey, Object>) in.readObject();
275 
276     if (_LOG.isFinest())
277     {
278       for(Map.Entry<DualKey, Object> entry : _rows.entrySet())
279       {
280         _LOG.finest("Restoring " + entry.getKey() + ", " + entry.getValue());
281       }
282     }
283   }
284 
285   private static RowState _createState(UIComponent child)
286   {
287     RowState state;
288     if (child instanceof EditableValueHolder)
289     {
290       state = new EVHState();
291       state.saveRowState(child);
292     }
293     else if (child instanceof UIXCollection)
294     {
295       state = new TableState();
296       state.saveRowState(child);
297     }
298     else if (child instanceof UIXShowDetail)
299     {
300       state = SDState.getState((UIXShowDetail) child);
301     }
302     else
303     {
304       state = null;
305     }
306 
307     return state;
308   }
309 
310   private static boolean _eq(Object k1, Object k2)
311   {
312     if (k1 == null)
313       return k2 == null;
314     return k1.equals(k2);
315   }
316 
317   // State for a single row
318   static private abstract class RowState implements Serializable
319   {
320 
321     public RowState()
322     {
323     }
324 
325     abstract public void saveRowState(UIComponent child);
326 
327     abstract public void restoreRowState(UIComponent child);
328 
329     abstract public boolean isNull();
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       ((UIXCollection) child).__setMyStampState(_state);
414     }
415 
416     @Override
417     public boolean isNull()
418     {
419       return _state == null;
420     }
421 
422     private Object _state = null;
423 
424     private static final long serialVersionUID = 1L;
425   }
426 
427   static private class EVHState extends RowState
428   {
429     public EVHState()
430     {
431       _valid = true;
432     }
433 
434     @Override
435     public void saveRowState(UIComponent child)
436     {
437       assert _assertIsStampCorrect(child);
438 
439       EditableValueHolder evh = (EditableValueHolder) child;
440       _submitted = evh.getSubmittedValue();
441       _localSet = evh.isLocalValueSet();
442       _local = evh.getLocalValue();
443       _valid = evh.isValid();
444     }
445 
446     @Override
447     public void restoreRowState(UIComponent child)
448     {
449       assert _assertIsStampCorrect(child);
450 
451       EditableValueHolder evh = (EditableValueHolder) child;
452       evh.setSubmittedValue(_submitted);
453       evh.setValue(_local);
454       evh.setLocalValueSet(_localSet);
455       evh.setValid(_valid);
456 
457       assert _assertStampHonoursState(evh);
458     }
459 
460     @Override
461     public boolean isNull()
462     {
463       return (_valid && (!_localSet) && (_submitted == null));
464     }
465 
466 
467     @Override
468     public String toString()
469     {
470       return "EVHState[value=" + _local + ",submitted=" + _submitted + "]";
471     }
472 
473     private boolean _assertStampHonoursState(EditableValueHolder evh)
474     {
475       return (evh.getSubmittedValue() == _submitted) &&
476         (evh.getLocalValue() == _local) &&
477         (evh.isLocalValueSet() == _localSet) &&
478         (evh.isValid() == _valid);
479     }
480 
481     /**
482      * Make sure that this stampState is used to save/restore state from the
483      * same stamp each time.
484      */
485     private boolean _assertIsStampCorrect(UIComponent stamp)
486     {
487       if (_assertStamp != null)
488       {
489         String stampId = stamp.getId();
490         String assertStampId = _assertStamp.getId();
491         assert (((assertStampId == null) && (stampId == null)) ||
492                 ((assertStampId != null) && assertStampId.equals(stampId))) :
493           "Current stamp:"+stamp+
494           " with id:"+stamp.getId()+
495           ". Previously stamp was:"+_assertStamp+
496           " with id:"+_assertStamp.getId();
497       }
498       else
499       {
500         _assertStamp = stamp;
501       }
502       return true;
503     }
504 
505     private Object _submitted;
506     private Object _local;
507     private boolean _localSet;
508     private boolean _valid;
509     private transient UIComponent _assertStamp = null;
510 
511     private static final long serialVersionUID = 1L;
512   }
513 
514   private static final class DualKey implements Serializable
515   {
516     public DualKey(Object key1, Object key2)
517     {
518       _key1 = key1;
519       _key2 = key2;
520 
521       _hash = _hash(key1) + _hash(key2);
522     }
523 
524     @Override
525     public boolean equals(Object other)
526     {
527       if (other == this)
528         return true;
529       if (other instanceof DualKey)
530       {
531         DualKey otherKey = (DualKey) other;
532         if (hashCode() != otherKey.hashCode())
533           return false;
534 
535         return _eq(_key1, otherKey._key1) && _eq(_key2, otherKey._key2);
536       }
537       return false;
538     }
539 
540     @Override
541     public int hashCode()
542     {
543       return _hash;
544     }
545 
546     @Override
547     public String toString()
548     {
549       return "<"+_key1+","+_key2+">";
550     }
551 
552     private static int _hash(Object k)
553     {
554       return (k==null) ? 0 : k.hashCode();
555     }
556 
557     private final Object _key1, _key2;
558     private final int _hash;
559 
560     private static final long serialVersionUID = 1L;
561   }
562 
563   private static final TrinidadLogger _LOG =
564      TrinidadLogger.createTrinidadLogger(StampState.class);
565 
566   private Map<DualKey, Object> _rows;
567   private static final Object[] _EMPTY_ARRAY = new Object[0];
568   private static final long serialVersionUID = 1L;
569 }