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.io.Serializable;
22  
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.faces.context.FacesContext;
29  
30  import org.apache.myfaces.trinidad.bean.util.StateUtils;
31  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
32  
33  /**
34   * Key for an entry in a FacesBean.
35   */
36  public class PropertyKey
37  {
38    /**
39     * Capability indicating this property does not support bindings.
40     */
41    static public final int CAP_NOT_BOUND = 1;
42  
43    /**
44     * Capability indicating this property is transient.
45     */
46    static public final int CAP_TRANSIENT = 2;
47  
48    /**
49     * Capability indicating this property describes a list.  List
50     * PropertyKeys will automatically be marked as not supporting
51     * bindings.
52     */
53    static public final int CAP_LIST = 4;
54  
55    /**
56     * Capability indicating this property can use the StateHolder API.
57     */
58    static public final int CAP_STATE_HOLDER = 8;
59  
60    /**
61     * Create a named PropertyKey, not attached to any type.
62     */
63    static public PropertyKey createPropertyKey(String name)
64    {
65      return new PropertyKey(name);
66    }
67    
68    //
69    // Constructors, all package-private.  Only the constructor
70    // that takes a simple String could be exposed at all safely;
71    // note that state saving cannot possibly re-create an
72    // anonymous PropertyKey with anything other than default metadata,
73    // so it is only safe to create such a PropertyKey as part
74    // of a FacesBean.Type.
75    //
76    PropertyKey(
77      String name)
78    {
79      this(name, _TYPE_DEFAULT);
80    }
81    
82    PropertyKey(
83      String   name,
84      Class<?> type)
85    {
86      this(name, type, null);
87    }
88  
89    PropertyKey(
90      String   name,
91      Class<?> type,
92      Object   defaultValue)
93    {
94      this(name, type, defaultValue, _CAPS_DEFAULT, -1);
95    }
96  
97    // Needs to be protected for UINodePropertyKey implementation
98    protected PropertyKey(
99      String   name,
100     Class<?> type,
101     Object   defaultValue,
102     int      capabilities,
103     int      index)
104   {
105     if (name == null)
106       throw new NullPointerException();
107 
108     if (type == null)
109       throw new NullPointerException();
110 
111     if (defaultValue != null)
112     {
113       // Ensure that default value is legal for this property type.
114       Class<?> boxedType = _getBoxedType(type);
115       if (!boxedType.isAssignableFrom(defaultValue.getClass()))
116       {
117         throw new IllegalStateException(_LOG.getMessage(
118           "DEFAULT_VALUE_IS_NOT_ASSIGNED_TO_TYPE", new Object[]{defaultValue, type}));
119       }
120     }
121     else
122     {
123       // Default the default value according to Java Language Specification
124       defaultValue = _getJavaDefault(type);
125 
126       // simplify equality testing in .equals()
127       if (defaultValue == null)
128         defaultValue = _OBJECT_NULL;
129     }
130         
131     if ((capabilities & ~_CAPS_ALL) != 0)
132       throw new IllegalStateException(_LOG.getMessage(
133         "CAPABILITY_MASK_NOT_UNDERSTOOD", (capabilities & ~_CAPS_ALL)));
134 
135     // Lists cannot be bound
136     boolean hasListCapability = (capabilities & CAP_LIST) != 0;
137                                                                
138     if (hasListCapability)
139       capabilities = capabilities | CAP_NOT_BOUND;
140 
141     _name = name;
142     _type = type;
143     _default = defaultValue;
144     _capabilities = capabilities;
145     _index = index;
146     
147     // save using StatUtils.saveList if the value is of type list
148     _serializeAsList = hasListCapability || LIST_CLASS.isAssignableFrom(_type);
149     _hashCode = _name.hashCode();
150   }
151 
152   /**
153    * Returns the type of this property.
154    */
155   public Class<?> getType()
156   {
157     return _type;
158   }
159 
160   /**
161    * Returns the default value of this property.
162    */
163   public Object getDefault()
164   {
165     return (_default != _OBJECT_NULL) ? _default : null;
166   }
167 
168   /**
169    * Returns the owning type for this property key.
170    */
171   public FacesBean.Type getOwner()
172   {
173     return _owner;
174   }
175 
176   /**
177    * Returns true if the property supports being bound.
178    */
179   public boolean getSupportsBinding()
180   {
181     return (_capabilities & CAP_NOT_BOUND) == 0;
182   }
183 
184   /**
185    * Returns true if the property is transient.
186    */
187   public boolean isTransient()
188   {
189     return (_capabilities & CAP_TRANSIENT) != 0;
190   }
191 
192   /**
193    * Returns true if the property is used to store a list.
194    */
195   public boolean isList()
196   {
197     return (_capabilities & CAP_LIST) != 0;
198   }
199 
200   /**
201    * Returns the name of this property.
202    */
203   public String getName()
204   {
205     return _name;
206   }
207 
208   /**
209    * Returns the index of this property.
210    */
211   public int getIndex()
212   {
213     return _index;
214   }
215 
216 
217   public Object saveValue(
218     FacesContext context,
219     Object value)
220   {
221     if ((_capabilities & CAP_STATE_HOLDER) != 0)
222       return StateUtils.saveStateHolder(context, value);
223     
224     // only serialize as list if this really is a list.  This is necessary because value
225     // could be a ValueExpression
226     if (this._serializeAsList && (value instanceof List))
227       return StateUtils.saveList(context, value);
228     
229     // warn if we are state saving non-serializable values, as this will cause client
230     // state saving and fail-over to fail.  See JIRA 1236
231     if ((value != null) && !(value instanceof Serializable) && _LOG.isWarning())
232       _LOG.warning(_LOG.getMessage("UNSERIALIZABLE_PROPERTY_VALUE_NO_CONTAINER",
233                                    new Object[]{value, this}));
234     
235     return value;
236   }
237 
238   public Object restoreValue(
239     FacesContext context,
240     Object savedValue)
241   {
242     if ((_capabilities & CAP_STATE_HOLDER) != 0)
243       return StateUtils.restoreStateHolder(context, savedValue);
244 
245     if (this._serializeAsList && (savedValue instanceof Object[]))
246       return StateUtils.restoreList(context, savedValue);
247 
248     return savedValue;
249   }
250 
251   
252   @Override
253   public boolean equals(Object o)
254   {
255     if (o == this)
256       return true;
257 
258     if (!(o instanceof PropertyKey))
259       return false;
260 
261     PropertyKey that = (PropertyKey)o;
262 
263     // If we're not both in the same Type, we're not equals.
264     if (_owner != that._owner)
265       return false;
266 
267     // If the index is -1, then it might be an anonymous
268     // type, in which case we have to compare names
269     int index = this._index;
270     if (index == -1)
271     {
272       return ((that._index == -1) && 
273               (that._name.equals(_name)) &&
274               (that._type.equals(_type)) &&
275               (that._default.equals(_default)));
276     }
277 
278     // But otherwise, since the types are the same, then the
279     // same index would imply the same instance - which would
280     // have been caught up above.  So, again, this ain't the same equal
281     return false;
282   }
283 
284   @Override
285   public int hashCode()
286   {
287     return _hashCode;
288   }
289 
290   @Override
291   public String toString()
292   {
293     String className = getClass().getName();
294     int lastPeriod = className.lastIndexOf('.');
295     if (lastPeriod >= 0)
296       className = className.substring(lastPeriod + 1);
297 
298     if (_index >= 0)
299       return className + "[" + _name + "," + _index + "]";
300     return className + "[" + _name + "]";
301   }
302 
303   void __setOwner(FacesBean.Type owner)
304   {
305     _owner = owner;
306   }
307 
308   static private Object _getJavaDefault(
309     Class<?> type)
310   {
311     return _PRIMITIVE_DEFAULTS.get(type);
312   }
313   
314   static private Class<?> _getBoxedType(
315     Class<?> type)
316   {
317     Class<?> boxedType = _BOXED_PRIMITIVES.get(type);
318     return (boxedType != null ? boxedType : type);
319   }
320   
321   static private Map<Class<?>, Object> _createPrimitiveDefaults()
322   {
323     Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
324     map.put(Boolean.TYPE, Boolean.FALSE);
325     map.put(Byte.TYPE, Byte.valueOf((byte)0));
326     map.put(Character.TYPE, Character.valueOf('\0'));
327     map.put(Double.TYPE, Double.valueOf(0.0));
328     map.put(Float.TYPE, Float.valueOf(0.0f));
329     map.put(Integer.TYPE, Integer.valueOf(0));
330     map.put(Long.TYPE, Long.valueOf(0L));
331     map.put(Short.TYPE, Short.valueOf((short)0));
332 
333     return Collections.unmodifiableMap(map);
334   }
335 
336   static private Map<Class<?>, Class<?>> _createBoxedPrimitives()
337   {
338     Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
339     map.put(Boolean.TYPE, Boolean.class);
340     map.put(Byte.TYPE, Byte.class);
341     map.put(Character.TYPE, Character.class);
342     map.put(Double.TYPE, Double.class);
343     map.put(Float.TYPE, Float.class);
344     map.put(Integer.TYPE, Integer.class);
345     map.put(Long.TYPE, Long.class);
346     map.put(Short.TYPE, Short.class);
347 
348     return Collections.unmodifiableMap(map);
349   }
350 
351   static private final Map<Class<?>, Object>   _PRIMITIVE_DEFAULTS = _createPrimitiveDefaults();
352   static private final Map<Class<?>, Class<?>> _BOXED_PRIMITIVES   = _createBoxedPrimitives();
353 
354   private final int     _hashCode;
355   private final String   _name;
356   private final int      _index;
357   private final int      _capabilities;
358   private final Class<?> _type;
359   private final Object   _default;
360   // true if we should use StateUtils.saveList() to save the state
361   private final boolean  _serializeAsList;
362   private       FacesBean.Type _owner;
363 
364   private static final Class<List> LIST_CLASS = List.class;
365   
366   static private final int _CAPS_DEFAULT = 
367     0;
368   
369   static private final int _CAPS_ALL = 
370     CAP_NOT_BOUND |
371     CAP_TRANSIENT |
372     CAP_LIST |
373     CAP_STATE_HOLDER;
374 
375   static private final Class<Object> _TYPE_DEFAULT = Object.class;
376 
377   static private final Object _OBJECT_NULL = new Object();
378   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
379     PropertyKey.class);
380 }
381 
382 
383 
384