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.bean;
20  
21  import java.lang.reflect.Array;
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.el.ValueExpression;
32  
33  import javax.faces.component.PartialStateHolder;
34  import javax.faces.component.StateHolder;
35  import javax.faces.component.behavior.ClientBehavior;
36  import javax.faces.context.FacesContext;
37  import javax.faces.el.ValueBinding;
38  
39  import org.apache.myfaces.trinidad.bean.util.FlaggedPropertyMap;
40  import org.apache.myfaces.trinidad.bean.util.StateUtils;
41  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
42  
43  
44  /**
45   * Base implementation of FacesBean.
46   *
47   */
48  abstract public class FacesBeanImpl implements FacesBean
49  {
50    public FacesBeanImpl()
51    {
52    }
53  
54    /**
55     * Get the type of a FacesBean
56     */
57    // TODO Use auto "TYPE" detection?
58    abstract public Type getType();
59  
60    final public Object getProperty(PropertyKey key)
61    {
62      Object o = getLocalProperty(key);
63      if (o != null)
64        return o;
65  
66      // Look for a binding if and only if the key supports bindings
67      if (key.getSupportsBinding())
68      {
69        ValueExpression expression = getValueExpression(key);
70        if (expression != null)
71        {
72          FacesContext context = FacesContext.getCurrentInstance();
73          return expression.getValue(context.getELContext());
74        }
75      }
76  
77      return null;
78    }
79  
80    /**
81     * {@inheritDoc}
82     */
83    final public Object getRawProperty(PropertyKey key)
84    {
85      Object local = getLocalProperty(key);
86      if (local != null)
87        return local;
88  
89      // Look for a binding if and only if the key supports bindings
90      return key.getSupportsBinding() ? getValueExpression(key) : null;
91    }
92  
93    // TODO Need *good* way of hooking property-sets;  it's
94    // currently not called from state restoring, so really, it shouldn't
95    // be used as a hook, but EditableValueBase currently
96    // requires hooking this method.
97    public void setProperty(PropertyKey key, Object value)
98    {
99      _checkNotListKey(key);
100     setPropertyImpl(key, value);
101   }
102 
103   final public Object getLocalProperty(PropertyKey key)
104   {
105     _checkNotListKey(key);
106 
107     return getLocalPropertyImpl(key);
108   }
109 
110   final public ValueExpression getValueExpression(PropertyKey key)
111   {
112     _checkNotListKey(key);
113 
114     PropertyMap map = _getExpressionsMap(false);
115     if (map == null)
116       return null;
117 
118     return (ValueExpression) map.get(key);
119   }
120 
121   final public void setValueExpression(PropertyKey key,
122                                        ValueExpression expression)
123   {
124     _checkNotListKey(key);
125 
126     if (!key.getSupportsBinding())
127     {
128       throw new IllegalArgumentException(_LOG.getMessage(
129         "CANNOT_FIND_PROPERTY", key.getName()));
130     }
131 
132     if (expression == null)
133     {
134       PropertyMap map = _getExpressionsMap(false);
135       if (map != null)
136         map.remove(key);
137     }
138     else
139     {
140       _getExpressionsMap(true).put(key, expression);
141     }
142 
143   }
144 
145   @SuppressWarnings("deprecation")
146   final public ValueBinding getValueBinding(PropertyKey key)
147   {
148     ValueExpression ve = getValueExpression(key);
149 
150     if (ve == null)
151     {
152       return null;
153     }
154     else
155     {
156       // wrap the ValueExpression if necessary
157       return ValueExpressionValueBinding.getValueBinding(ve);
158     }
159   }
160 
161   @SuppressWarnings("deprecation")
162   final public void setValueBinding(PropertyKey key, ValueBinding binding)
163   {
164     ValueExpression ve;
165 
166     if (binding == null)
167     {
168       ve = null;
169     }
170     else
171     {
172       ve = ValueBindingValueExpression.getValueExpression(binding);
173     }
174 
175     setValueExpression(key, ve);
176   }
177 
178   @SuppressWarnings("unchecked")
179   final public void addEntry(PropertyKey listKey, Object value)
180   {
181     _checkListKey(listKey);
182 
183     List<Object> l = (List<Object>) getLocalPropertyImpl(listKey);
184     if (l == null)
185     {
186       l = _createList();
187       setPropertyImpl(listKey, l);
188     }
189 
190     l.add(value);
191 
192     // If we've marked our initial state, forcibly update the property
193     // so we know to write out the change
194     if (_initialStateMarked)
195       setPropertyImpl(listKey, l);
196   }
197 
198   @SuppressWarnings("unchecked")
199   final public void removeEntry(PropertyKey listKey, Object value)
200   {
201     _checkListKey(listKey);
202 
203     List<Object> l = (List<Object>) getLocalPropertyImpl(listKey);
204     if (l != null)
205     {
206       l.remove(value);
207     }
208 
209     // If we've marked our initial state, forcibly update the property
210     // so we know to write out the change
211     if (_initialStateMarked && (l != null))
212       setPropertyImpl(listKey, l);
213   }
214 
215   @SuppressWarnings("unchecked")
216   final public Object[] getEntries(PropertyKey listKey, Class clazz)
217   {
218     _checkListKey(listKey);
219 
220     List<Object> l = (List<Object>) getLocalPropertyImpl(listKey);
221     if (l == null)
222       return (Object[]) Array.newInstance(clazz, 0);
223 
224     int size = l.size();
225     ArrayList<Object> tempList = new ArrayList<Object>(size);
226     for (int i = 0; i < size; i++)
227     {
228       Object o = l.get(i);
229       if (clazz.isInstance(o))
230         tempList.add(o);
231     }
232 
233     return tempList.toArray((Object[]) Array.newInstance(clazz,
234                                                          tempList.size()));
235   }
236 
237   @SuppressWarnings("unchecked")
238   final public boolean containsEntry(PropertyKey listKey, Class<?> clazz)
239   {
240     _checkListKey(listKey);
241 
242     List<Object> l = (List<Object>) getLocalPropertyImpl(listKey);
243     if (l == null)
244       return false;
245 
246     int size = l.size();
247     for (int i = 0; i < size; i++)
248     {
249       Object o = l.get(i);
250       if (clazz.isInstance(o))
251         return true;
252     }
253 
254     return false;
255   }
256 
257   @SuppressWarnings("unchecked")
258   final public Iterator<Object> entries(PropertyKey listKey)
259   {
260     _checkListKey(listKey);
261 
262     List<Object> l = (List<Object>) getLocalPropertyImpl(listKey);
263     if (l == null)
264       return Collections.emptyList().iterator();
265 
266     return l.iterator();
267   }
268 
269   // TODO provide more efficient implementation for copying
270   // from other FacesBeanImpl instances
271   public void addAll(FacesBean from)
272   {
273     if (from == this)
274       return;
275 
276     for(PropertyKey fromKey : from.keySet())
277     {
278       PropertyKey toKey = _convertKey(fromKey);
279       if ((toKey != null) &&
280           _isCompatible(fromKey, toKey))
281       {
282         if (!fromKey.isList())
283         {
284           setProperty(toKey, from.getLocalProperty(fromKey));
285         }
286         else
287         {
288           Iterator<? extends Object> entries = from.entries(fromKey);
289           while (entries.hasNext())
290             addEntry(toKey, entries.next());
291         }
292       }
293     }
294 
295     for(PropertyKey fromKey : from.bindingKeySet())
296     {
297       PropertyKey toKey = _convertKey(fromKey);
298       if (toKey.getSupportsBinding())
299       {
300         setValueExpression(toKey, from.getValueExpression(fromKey));
301       }
302     }
303   }
304 
305   public Set<PropertyKey> keySet()
306   {
307     if (_properties == null)
308       return Collections.emptySet();
309     else
310       return _properties.keySet();
311   }
312 
313   @SuppressWarnings("unchecked")
314   final public Set<PropertyKey> bindingKeySet()
315   {
316     if (_expressions == null)
317       return Collections.emptySet();
318 
319     return _expressions.keySet();
320   }
321 
322   public void markInitialState()
323   {
324     _initialStateMarked = true;
325 
326     if (_properties != null)
327       _properties.markInitialState();
328 
329     if (_expressions != null)
330       _expressions.markInitialState();
331   }
332 
333   public void clearInitialState()
334   {
335     _initialStateMarked = false;
336 
337     if (_properties != null)
338       _properties.clearInitialState();
339 
340     if (_expressions != null)
341       _expressions.clearInitialState();
342   }
343 
344   public boolean initialStateMarked()
345   {
346     return _initialStateMarked;
347   }
348 
349   public void restoreState(FacesContext context, Object state)
350   {
351     if (_LOG.isFiner())
352     {
353       _LOG.finer("Restoring state into " + this);
354     }
355 
356     if (state instanceof Object[])
357     {
358       Object[] asArray = (Object[]) state;
359       if (asArray.length == 2)
360       {
361         Object propertyState = asArray[0];
362         Object bindingsState = asArray[1];
363         _getPropertyMap().restoreState(context, getType(), propertyState);
364         _getExpressionsMap(true).restoreState(context, getType(), bindingsState);
365         return;
366       }
367       else if (asArray.length == 1)
368       {
369         Object propertyState = asArray[0];
370         _getPropertyMap().restoreState(context, getType(), propertyState);
371         return;
372       }
373     }
374 
375     _getPropertyMap().restoreState(context, getType(), state);
376   }
377 
378   public Object saveState(FacesContext context)
379   {
380     if (_LOG.isFiner())
381     {
382       _LOG.finer("Saving state of " + this);
383     }
384 
385     Object propertyState = (_properties == null)
386                             ? null
387                             : _properties.saveState(context);
388     Object bindingsState = (_expressions == null)
389                             ? null
390                             : _expressions.saveState(context);
391 
392     if (bindingsState != null)
393     {
394       return new Object[] { propertyState, bindingsState };
395     }
396 
397     if (propertyState == null)
398       return null;
399 
400     if (propertyState instanceof Object[])
401     {
402       Object[] asArray = (Object[]) propertyState;
403       if (asArray.length <= 2)
404         return new Object[]{propertyState};
405     }
406 
407     return propertyState;
408   }
409 
410   @Override
411   public String toString()
412   {
413     String className = getClass().getName();
414     int lastPeriod = className.lastIndexOf('.');
415     if (lastPeriod < 0)
416       return className;
417 
418     return className.substring(lastPeriod + 1);
419   }
420 
421   protected void setPropertyImpl(PropertyKey key, Object value)
422   {
423     if (value == null)
424       _getPropertyMap().remove(key);
425     else
426       _getPropertyMap().put(key, value);
427   }
428 
429   protected Object getLocalPropertyImpl(PropertyKey key)
430   {
431     return _getPropertyMap().get(key);
432   }
433 
434   protected PropertyMap createPropertyMap()
435   {
436     FlaggedPropertyMap map =  new FlaggedPropertyMap();
437     // We are providing Type information to the Map implementation
438     // to enable tracking certain properties in a bitmask.
439     // For now, the properties we are tracking will never be specified
440     // as expressions
441     map.setType(getType());
442     return map;
443   }
444 
445   protected PropertyMap createExpressionsMap()
446   {
447     return new FlaggedPropertyMap();
448   }
449 
450   // "listKey" is unused, but it seems plausible that
451   // if this ever gets converted to a protected hook that
452   // "listKey" may be useful in that context.
453   private List<Object> _createList(/*PropertyKey listKey*/)
454   {
455     return new ArrayList<Object>();
456   }
457 
458   /**
459    * Converts a key from one type to another.
460    */
461   private PropertyKey _convertKey(PropertyKey fromKey)
462   {
463     Type type = getType();
464     // If the "fromKey" has an index, then see if it's exactly
465     // the same key as one of ours.
466     PropertyKey toKey = type.findKey(fromKey.getIndex());
467     if (toKey == fromKey)
468       return toKey;
469 
470     // Otherwise, just look it up by name.
471     String name = fromKey.getName();
472     toKey = type.findKey(name);
473     if (toKey != null)
474       return toKey;
475 
476     // Finally, give up and create a transient key
477     return new PropertyKey(name);
478   }
479 
480   /**
481    * Returns true if two keys are of compatible types.
482    */
483   static private boolean _isCompatible(
484     PropertyKey fromKey, PropertyKey toKey)
485   {
486     return (fromKey.isList() == toKey.isList());
487   }
488 
489   private PropertyMap _getPropertyMap()
490   {
491     if (_properties == null)
492       _properties = createPropertyMap();
493 
494     return _properties;
495   }
496 
497  
498   private PropertyMap _getExpressionsMap(boolean createIfNew)
499   {
500     if (_expressions == null)
501     {
502       if (createIfNew)
503       {
504         _expressions = createExpressionsMap();
505       }
506     }
507 
508     return _expressions;
509   }
510 
511   static private void _checkListKey(PropertyKey listKey)
512     throws IllegalArgumentException
513   {
514     if (!listKey.isList())
515       throw new IllegalArgumentException(_LOG.getMessage(
516         ">KEY_CANNOT_BE_USED_FOR_LISTS", listKey));
517   }
518 
519   static private void _checkNotListKey(PropertyKey key)
520     throws IllegalArgumentException
521   {
522     if (key.isList())
523       throw new IllegalArgumentException(_LOG.getMessage(
524         "KEY_IS_LIST_KEY", key));
525   }
526 
527   private PropertyMap _properties;
528   private PropertyMap _expressions;
529  
530   private transient boolean                           _initialStateMarked;
531 
532   static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
533                                                FacesBeanImpl.class);
534 }