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