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