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><f:loadBundle/><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 }