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.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import javax.el.ValueExpression;
31  
32  import javax.faces.context.FacesContext;
33  import javax.faces.el.ValueBinding;
34  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
35  
36  /**
37   * Base interface for FacesBean storage objects.
38   * 
39   */
40  public interface FacesBean
41  {
42    /**
43     * Returns the Type of this bean.
44     */
45    public Type getType();
46  
47    /**
48     * Returns a property.  If the property has not been explicitly
49     * set, and the key supports bindings, and a ValueBinding has
50     * been set for this key, that ValueBinding will be evaluated.
51     * 
52     * @param key the property key
53     * @exception IllegalArgumentException if key is a list key
54     */
55    // TODO Additional version that takes a FacesContext?
56    public Object getProperty(PropertyKey key);
57  
58    /**
59     *  Set a property.
60     * @exception IllegalArgumentException if key is a list key
61     */
62    public void setProperty(PropertyKey key, Object value);
63  
64    /**
65     * Return a property, ignoring any value bindings.
66     * 
67     * @exception IllegalArgumentException if key is a list key
68     */
69    public Object getLocalProperty(PropertyKey key);
70  
71    /**
72     * Return the value expression for a key.
73     * @exception IllegalArgumentException if the property does
74     *   not support value bindings.
75     */
76    public ValueExpression getValueExpression(PropertyKey key);
77  
78    /**
79     * Return the value binding for a key.
80     * @exception IllegalArgumentException if the property does
81     *   not support value bindings.
82     * @deprecated
83     */
84    public ValueBinding getValueBinding(PropertyKey key);
85  
86    /**
87     * Gets the current unevaluated value for the specified property key. 
88     * <p>The method will first look for a local value. If it exists, it will 
89     * be returned. If it does not and the bean supports value expressions, the 
90     * method will look for an expression with the specified key and return it 
91     * directly if it exists without evaluatig its value.</p>
92     * <p>This method is mainly used when:</p>
93     * <ul>
94     *   <li>The caller cannot ensure that FacesContext exists at the time 
95     *   of the call</li>
96     *   <li>The FacesContext does not yet contains the managed bean
97     *   referenced by the value binding</li>
98     *   <li>The managed bean referenced by the value binding is not yet 
99     *   in a coherent state to evaluate the expression</li>
100    * </ul>
101    * <p>The most common use case of this method is for message attributes 
102    * set on converters and validators using a value binding referencing 
103    * a managed bean created by <code>&lt;f:loadBundle/&gt;<code>. Since 
104    * loadBundle only creates its bean during the render response phase 
105    * while converter and validators take action during process validation 
106    * phase, the message property's value binding must be stored in a 
107    * special <code>FacesMessage</code> implementation that will evaluate 
108    * the binding only during render response.</p>
109    * 
110    * @param key the parameter key of the raw property value to get.
111    * 
112    * @return the local value of the specified key if it exists, a 
113    *         <code>ValueExpression</code> object if the specified key 
114    *         supports expressions and an expression was specified for that 
115    *         property, <code>null</code> otherwise.
116    * 
117    * @throws IllegalArgumentException if the specified key is a list key.
118    * 
119    * @see #getLocalProperty(PropertyKey)
120    * @see #getValueBinding(PropertyKey)
121    * @see #getValueExpression(PropertyKey)
122    */
123   public Object getRawProperty(PropertyKey key);
124 
125   /**
126    * Set the value expression for a key.
127    * @exception IllegalArgumentException if the property does
128    *   not support value expressions.
129    */
130   public void setValueExpression(PropertyKey key, ValueExpression expression);
131 
132   /**
133    * Set the value binding for a key.
134    * @exception IllegalArgumentException if the property does
135    *   not support value bindings.
136    * @deprecated
137    */
138   public void setValueBinding(PropertyKey key, ValueBinding binding);
139 
140   /**
141    * Add an entry to a list.  The same value may be added
142    * repeatedly;  null is also a legal value.  (Consumers of
143    * this API can apply more stringent rules to specific keys
144    * in cover functions.)
145    * @exception IllegalArgumentException if the key is not a list key.
146    */
147   public void addEntry(PropertyKey listKey, Object value);
148 
149   /**
150    * Remove an entry from a list.
151    * @exception IllegalArgumentException if the key is not a list key.
152    */
153   public void removeEntry(PropertyKey listKey, Object value);
154 
155   /**
156    * Return as an array all elements of this key that 
157    * are instances of the specified class.
158    * @return an array whose instance type is the class
159    * @exception IllegalArgumentException if the key is not a list key.
160    */
161   // TODO This can, of course, be implemented on top of entries();
162   // consider moving to a utility function;  however, it's
163   // universally needed by all consumers, so...
164   public Object[] getEntries(PropertyKey listKey, Class<?> clazz);
165 
166   /**
167    * Return true if at least one element of the list identified by
168    * this key is an instance of the specified class.
169    * @exception IllegalArgumentException if the key is not a list key.
170    */
171   public boolean containsEntry(PropertyKey listKey, Class<?> clazz);
172 
173   /**
174    * Returns an iterator over all entries at this key.
175    * @exception IllegalArgumentException if the key is not a list key.
176    */
177   // TODO is this iterator read-only or read-write?
178   public Iterator<? extends Object> entries(PropertyKey listKey);
179 
180   /**
181    * Copies all properties, bindings, and list entries from
182    * one bean to another.  If the beans are of different types,
183    * properties will be copied by name.  Incompatible properties will be
184    * ignored;  specifically, properties that are lists on only one
185    * of the beans or ValueBindings on the original bean that
186    * are not allowed on the target bean.
187    */
188   public void addAll(FacesBean from);
189 
190   /**
191    * Returns a Set of all PropertyKeys that have either lists
192    *  or values attached.
193    */
194   public Set<PropertyKey> keySet();
195 
196   /**
197    * Returns a Set of all PropertyKeys that have ValueBindings attached.
198    */
199   public Set<PropertyKey> bindingKeySet();
200 
201   public void markInitialState();
202 
203   /**
204    * Saves the state of a FacesBean.
205    */
206   public Object saveState(FacesContext context);
207 
208   /**
209    * Restores the state of a FacesBean.
210    */
211   public void restoreState(FacesContext context, Object state);
212 
213   /**
214    * Type of a FacesBean, encapsulating the set of registered
215    * PropertyKeys.
216    */
217   // TODO Extract as interface?
218   public static class Type
219   {
220     public Type()
221     {
222       this(null);
223     }
224 
225     public Type(Type superType)
226     {
227       _superType = superType;
228       // todo initial size of map, and type of map
229       // todo initial size of list, and type of list
230       // todo build combined data structure
231       _keyMap = new HashMap<String, PropertyKey>();
232       _keyList = new ArrayList<PropertyKey>();
233       _unmodifiableKeys = Collections.unmodifiableList(_keyList);
234 
235       if (_superType != null)
236       {
237         _keyMap.putAll(_superType._keyMap);
238         _keyList.addAll(_superType._keyList);
239         _index = _superType._index;
240         _superType.lock();
241       }
242 
243     }
244 
245     /**
246      * Find an existing key by name.
247      */
248     public PropertyKey findKey(String name)
249     {
250       return _keyMap.get(name);
251     }
252 
253     /**
254      * Find an existing key by index.
255      */
256     public PropertyKey findKey(int index)
257     {
258       if ((index < 0) || (index >= _keyList.size()))
259         return null;
260       
261       return _keyList.get(index);
262     }
263 
264     /**
265      * Register a new key.
266      * @exception IllegalStateException if the type is already locked,
267      *    or the key does not already exists.
268      */
269     public final PropertyKey registerKey(
270       String   name,
271       Class<?> type,
272       Object   defaultValue)
273     {
274       return registerKey(name, type, defaultValue, 0);
275     }
276 
277     /**
278      * Register a new key.
279      * @exception IllegalStateException if the type is already locked,
280      *    or the key does not already exists.
281      */
282     public final PropertyKey registerKey(
283       String   name,
284       Class<?> type)
285     {
286       return registerKey(name, type, null, 0);
287     }
288 
289     /**
290      * Register a new key.
291      * @exception IllegalStateException if the type is already locked,
292      *    or the key does not already exists.
293      */
294     public final PropertyKey registerKey(
295       String name)
296     {
297       return registerKey(name, Object.class, null, 0);
298     }
299 
300     /**
301      * Register a new key.
302      * @exception IllegalStateException if the type is already locked,
303      *    or the key does not already exists.
304      */
305     public final PropertyKey registerKey(
306       String name,
307       int    capabilities)
308     {
309       return registerKey(name, Object.class, null, capabilities);
310     }
311 
312     /**
313      * Register a new key.
314      * @exception IllegalStateException if the type is already locked,
315      *    or the key does not already exists.
316      */
317     public final PropertyKey registerKey(
318       String   name,
319       Class<?> type,
320       int      capabilities)
321     {
322       return registerKey(name, type, null, capabilities);
323     }
324 
325     /**
326      * Add an alias to an existing PropertyKey.
327      * @exception IllegalStateException if the type is already locked,
328      *    or a key already exists at the alias.
329      */
330     public PropertyKey registerAlias(PropertyKey key, String alias)
331     {
332       _checkLocked();
333       
334       if (findKey(alias) != null)
335         throw new IllegalStateException();
336 
337       _keyMap.put(alias, key);
338       return key;
339     }
340     
341 
342     /**
343      * Register a new key with a set of capabilities.
344      * @exception IllegalStateException if the type is already locked,
345      *    or the key already exists.
346      */
347     public PropertyKey registerKey(
348       String   name, 
349       Class<?> type,
350       Object   defaultValue,
351       int      capabilities)
352     {
353       _checkLocked();
354       _checkName(name);
355 
356       PropertyKey key = createPropertyKey(name,
357                                           type,
358                                           defaultValue,
359                                           capabilities,
360                                           getNextIndex());
361       addKey(key);
362       return key;
363     }
364     
365     
366     /**
367      * Locks the type object, preventing further changes.
368      */
369     public void lock()
370     {
371       _isLocked = true;
372     }
373 
374     /**
375      * Locks the type object, preventing further changes.
376      */
377     public void lockAndRegister(
378        /*String renderKitId,*/
379        String componentFamily,
380        String rendererType)
381     {
382       lock();
383       // =-=AEW We don't yet have the renderKitId available here yet
384       TypeRepository.registerType(/*renderKitId, */
385                                   componentFamily,
386                                   rendererType,
387                                   this);
388     }
389 
390     /**
391      * Returns the iterator of registered property keys, excluding aliases.
392      */
393     public Iterator<PropertyKey> keys()
394     {
395       return propertyKeys().iterator();
396     }
397     
398     /**
399      * Returns an unmodifiable <code>Collection</code> of registered property keys,
400      * excluding aliases.
401      * 
402      * @return unmodifiable <code>Collection</code> with registered 
403      */
404     public Collection<PropertyKey> propertyKeys()
405     {
406       return _unmodifiableKeys;
407     }
408 
409     protected PropertyKey createPropertyKey(
410       String   name,
411       Class<?> type,
412       Object   defaultValue,
413       int      capabilities,
414       int      index)
415     {
416       if (_superType != null)
417       {
418         return _superType.createPropertyKey(name, type, defaultValue,
419                                             capabilities, index);
420       }
421 
422       return new PropertyKey(name, type, defaultValue, capabilities, index);
423     }
424 
425     /**
426      * Return the next available index.
427      */
428     protected int getNextIndex()
429     {
430       int index = _index;
431       _index = index + 1;
432       return index;
433     }
434 
435 
436     /**
437      * Add a key to the type.
438      * @exception IllegalStateException if the type is already locked,
439      *    or a key with that name or index already exists.
440      */
441     protected void addKey(PropertyKey key)
442     {
443       _checkLocked();
444       
445       // Restore the old key
446       PropertyKey oldValue = _keyMap.put(key.getName(), key);
447       if (oldValue != null)
448       {
449         _keyMap.put(key.getName(), oldValue);
450         throw new IllegalStateException(_LOG.getMessage(
451           "NAME_ALREADY_REGISTERED", key.getName()));
452       }
453       
454       int index = key.getIndex();
455       if (index >= 0)
456       {
457         _expandListToIndex(_keyList, index);
458         oldValue = _keyList.set(index, key);
459         if (oldValue != null)
460           {
461             _keyList.set(index, oldValue);
462             throw new IllegalStateException(_LOG.getMessage(
463               "INDEX_ALREADY_REGISTERED", index));
464           }
465       }
466       
467       // Set the backpointer
468       key.__setOwner(this);
469     }
470      
471     
472     static private void _expandListToIndex(ArrayList<PropertyKey> list, int count)
473     {
474       list.ensureCapacity(count + 1);
475       int addCount = (count + 1) - list.size();
476       for (int i = 0; i < addCount; i++)
477         list.add(null);
478     }
479 
480 
481     private void _checkLocked()
482     {
483       if (_isLocked)
484         throw new IllegalStateException(_LOG.getMessage(
485           "TYPE_ALREADY_LOCKED"));
486     }
487 
488     private void _checkName(String name)
489     {
490       if (findKey(name) != null)
491       {
492         throw new IllegalStateException(_LOG.getMessage(
493           "NAME_ALREADY_REGISTERED", name));
494       }
495     }
496 
497     private final Map<String, PropertyKey> _keyMap;
498     private final List<PropertyKey> _unmodifiableKeys;
499     // keyList is used for optimized operations, like findKey(index)
500     private final ArrayList<PropertyKey>   _keyList;
501     private boolean   _isLocked;
502     private int       _index;
503     private final Type _superType;
504     static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(Type.class); 
505   }
506 }