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