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