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