Coverage Report - javax.faces.component._ComponentAttributesMap
 
Classes in this File Line Coverage Branch Coverage Complexity
_ComponentAttributesMap
0%
0/94
0%
0/36
0
 
 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 javax.faces.component;
 20  
 
 21  
 import javax.el.ValueExpression;
 22  
 import javax.faces.FacesException;
 23  
 import javax.faces.context.FacesContext;
 24  
 import java.beans.BeanInfo;
 25  
 import java.beans.IntrospectionException;
 26  
 import java.beans.Introspector;
 27  
 import java.beans.PropertyDescriptor;
 28  
 import java.io.Serializable;
 29  
 import java.lang.reflect.Method;
 30  
 import java.util.*;
 31  
 
 32  
 /**
 33  
  * A custom implementation of the Map interface, where get and put calls
 34  
  * try to access getter/setter methods of an associated UIComponent before
 35  
  * falling back to accessing a real Map object.
 36  
  * <p/>
 37  
  * Some of the behaviours of this class don't really comply with the
 38  
  * definitions of the Map class; for example the key parameter to all
 39  
  * methods is required to be of type String only, and after clear(),
 40  
  * calls to get can return non-null values. However the JSF spec
 41  
  * requires that this class behave in the way implemented below. See
 42  
  * UIComponent.getAttributes for more details.
 43  
  * <p/>
 44  
  * The term "property" is used here to refer to real javabean properties
 45  
  * on the underlying UIComponent, while "attribute" refers to an entry
 46  
  * in the associated Map.
 47  
  *
 48  
  * @author Manfred Geiler (latest modification by $Author: lu4242 $)
 49  
  * @version $Revision: 949071 $ $Date: 2010-05-27 21:22:17 -0500 (Thu, 27 May 2010) $
 50  
  */
 51  
 class _ComponentAttributesMap
 52  
         implements Map, Serializable
 53  
 {
 54  
     private static final long serialVersionUID = -9106832179394257866L;
 55  
 
 56  0
     private static final Object[] EMPTY_ARGS = new Object[0];
 57  
 
 58  
     // The component that is read/written via this map.
 59  
     private UIComponent _component;
 60  
 
 61  
     // We delegate instead of derive from HashMap, so that we can later
 62  
     // optimize Serialization
 63  0
     private Map<Object, Object> _attributes = null;
 64  
 
 65  
     // A cached hashmap of propertyName => PropertyDescriptor object for all
 66  
     // the javabean properties of the associated component. This is built by
 67  
     // introspection on the associated UIComponent. Don't serialize this as
 68  
     // it can always be recreated when needed.
 69  0
     private transient Map<String, PropertyDescriptor> _propertyDescriptorMap = null;
 70  
 
 71  
     // Cache for component property descriptors
 72  0
     private static Map<Class, Map<String, PropertyDescriptor>> _propertyDescriptorCache = new WeakHashMap<Class, Map<String, PropertyDescriptor>>();
 73  
 
 74  
     /**
 75  
      * Create a map backed by the specified component.
 76  
      * <p/>
 77  
      * This method is expected to be called when a component is first created.
 78  
      */
 79  
     _ComponentAttributesMap(UIComponent component)
 80  0
     {
 81  0
         _component = component;
 82  0
         _attributes = new HashMap<Object, Object>();
 83  0
     }
 84  
 
 85  
     /**
 86  
      * Create a map backed by the specified component. Attributes already
 87  
      * associated with the component are provided in the specified Map
 88  
      * class. A reference to the provided map is kept; this object's contents
 89  
      * are updated during put calls on this instance.
 90  
      * <p/>
 91  
      * This method is expected to be called during the "restore view" phase.
 92  
      */
 93  
     _ComponentAttributesMap(UIComponent component, Map<Object, Object> attributes)
 94  0
     {
 95  0
         _component = component;
 96  0
         _attributes = new HashMap(attributes);
 97  0
     }
 98  
 
 99  
     /**
 100  
      * Return the number of <i>attributes</i> in this map. Properties of the
 101  
      * underlying UIComponent are not counted.
 102  
      * <p/>
 103  
      * Note that because the get method can read properties of the
 104  
      * UIComponent and evaluate value-bindings, it is possible to have
 105  
      * size return zero while calls to the get method return non-null
 106  
      * values.
 107  
      */
 108  
     public int size()
 109  
     {
 110  0
         return _attributes.size();
 111  
     }
 112  
 
 113  
     /**
 114  
      * Clear all the <i>attributes</i> in this map. Properties of the
 115  
      * underlying UIComponent are not modified.
 116  
      * <p/>
 117  
      * Note that because the get method can read properties of the
 118  
      * UIComponent and evaluate value-bindings, it is possible to have
 119  
      * calls to the get method return non-null values immediately after
 120  
      * a call to clear.
 121  
      */
 122  
     public void clear()
 123  
     {
 124  0
         _attributes.clear();
 125  0
     }
 126  
 
 127  
     /**
 128  
      * Return true if there are no <i>attributes</i> in this map. Properties
 129  
      * of the underlying UIComponent are not counted.
 130  
      * <p/>
 131  
      * Note that because the get method can read properties of the
 132  
      * UIComponent and evaluate value-bindings, it is possible to have
 133  
      * isEmpty return true, while calls to the get method return non-null
 134  
      * values.
 135  
      */
 136  
     public boolean isEmpty()
 137  
     {
 138  0
         return _attributes.isEmpty();
 139  
     }
 140  
 
 141  
     /**
 142  
      * Return true if there is an <i>attribute</i> with the specified name,
 143  
      * but false if there is a javabean <i>property</i> of that name on the
 144  
      * associated UIComponent.
 145  
      * <p/>
 146  
      * Note that it should be impossible for the attributes map to contain
 147  
      * an entry with the same name as a javabean property on the associated
 148  
      * UIComponent.
 149  
      *
 150  
      * @param key <i>must</i> be a String. Anything else will cause a
 151  
      *            ClassCastException to be thrown.
 152  
      */
 153  
     public boolean containsKey(Object key)
 154  
     {
 155  0
         checkKey(key);
 156  
 
 157  0
         return getPropertyDescriptor((String) key) == null ? _attributes.containsKey(key) : false;
 158  
     }
 159  
 
 160  
     /**
 161  
      * Returns true if there is an <i>attribute</i> with the specified
 162  
      * value. Properties of the underlying UIComponent aren't examined,
 163  
      * nor value-bindings.
 164  
      *
 165  
      * @param value null is allowed
 166  
      */
 167  
     public boolean containsValue(Object value)
 168  
     {
 169  0
         return _attributes.containsValue(value);
 170  
     }
 171  
 
 172  
     /**
 173  
      * Return a collection of the values of all <i>attributes</i>. Property
 174  
      * values are not included, nor value-bindings.
 175  
      */
 176  
     public Collection<Object> values()
 177  
     {
 178  0
         return _attributes.values();
 179  
     }
 180  
 
 181  
     /**
 182  
      * Call put(key, value) for each entry in the provided map.
 183  
      */
 184  
     public void putAll(Map t)
 185  
     {
 186  0
         for (Iterator it = t.entrySet().iterator(); it.hasNext();)
 187  
         {
 188  0
             Map.Entry entry = (Entry) it.next();
 189  0
             put(entry.getKey(), entry.getValue());
 190  0
         }
 191  0
     }
 192  
 
 193  
     /**
 194  
      * Return a set of all <i>attributes</i>. Properties of the underlying
 195  
      * UIComponent are not included, nor value-bindings.
 196  
      */
 197  
     public Set entrySet()
 198  
     {
 199  0
         return _attributes.entrySet();
 200  
     }
 201  
 
 202  
     /**
 203  
      * Return a set of the keys for all <i>attributes</i>. Properties of the
 204  
      * underlying UIComponent are not included, nor value-bindings.
 205  
      */
 206  
     public Set<Object> keySet()
 207  
     {
 208  0
         return _attributes.keySet();
 209  
     }
 210  
 
 211  
     /**
 212  
      * In order: get the value of a <i>property</i> of the underlying
 213  
      * UIComponent, read an <i>attribute</i> from this map, or evaluate
 214  
      * the component's value-binding of the specified name.
 215  
      *
 216  
      * @param key must be a String. Any other type will cause ClassCastException.
 217  
      */
 218  
     public Object get(Object key)
 219  
     {
 220  0
         checkKey(key);
 221  
 
 222  
         // is there a javabean property to read?
 223  0
         PropertyDescriptor propertyDescriptor
 224  
                 = getPropertyDescriptor((String) key);
 225  0
         if (propertyDescriptor != null)
 226  
         {
 227  0
             return getComponentProperty(propertyDescriptor);
 228  
         }
 229  
 
 230  
         // is there a literal value to read?
 231  0
         Object mapValue = _attributes.get(key);
 232  0
         if (mapValue != null)
 233  
         {
 234  0
             return mapValue;
 235  
         }
 236  
 
 237  
         // is there a value-binding to read?
 238  0
         ValueExpression ve = _component.getValueExpression((String) key);
 239  0
         if (ve != null)
 240  
         {
 241  0
             return ve.getValue(_component.getFacesContext().getELContext());
 242  
         }
 243  
 
 244  
         // no value found
 245  0
         return null;
 246  
     }
 247  
 
 248  
     /**
 249  
      * Remove the attribute with the specified name. An attempt to
 250  
      * remove an entry whose name is that of a <i>property</i> on
 251  
      * the underlying UIComponent will cause an IllegalArgumentException.
 252  
      * Value-bindings for the underlying component are ignored.
 253  
      *
 254  
      * @param key must be a String. Any other type will cause ClassCastException.
 255  
      */
 256  
     public Object remove(Object key)
 257  
     {
 258  0
         checkKey(key);
 259  0
         PropertyDescriptor propertyDescriptor = getPropertyDescriptor((String) key);
 260  0
         if (propertyDescriptor != null)
 261  
         {
 262  0
             throw new IllegalArgumentException("Cannot remove component property attribute");
 263  
         }
 264  0
         return _attributes.remove(key);
 265  
     }
 266  
 
 267  
     /**
 268  
      * Store the provided value as a <i>property</i> on the underlying
 269  
      * UIComponent, or as an <i>attribute</i> in a Map if no such property
 270  
      * exists. Value-bindings associated with the component are ignored; to
 271  
      * write to a value-binding, the value-binding must be explicitly
 272  
      * retrieved from the component and evaluated.
 273  
      * <p/>
 274  
      * Note that this method is different from the get method, which
 275  
      * does read from a value-binding if one exists. When a value-binding
 276  
      * exists for a non-property, putting a value here essentially "masks"
 277  
      * the value-binding until that attribute is removed.
 278  
      * <p/>
 279  
      * The put method is expected to return the previous value of the
 280  
      * property/attribute (if any). Because UIComponent property getter
 281  
      * methods typically try to evaluate any value-binding expression of
 282  
      * the same name this can cause an EL expression to be evaluated,
 283  
      * thus invoking a getter method on the user's model. This is fine
 284  
      * when the returned value will be used; Unfortunately this is quite
 285  
      * pointless when initialising a freshly created component with whatever
 286  
      * attributes were specified in the view definition (eg JSP tag
 287  
      * attributes). Because the UIComponent.getAttributes method
 288  
      * only returns a Map class and this class must be package-private,
 289  
      * there is no way of exposing a "putNoReturn" type method.
 290  
      *
 291  
      * @param key   String, null is not allowed
 292  
      * @param value null is allowed
 293  
      */
 294  
     public Object put(Object key, Object value)
 295  
     {
 296  0
         checkKey(key);
 297  
 
 298  0
         PropertyDescriptor propertyDescriptor = getPropertyDescriptor((String) key);
 299  0
         if (propertyDescriptor == null)
 300  
         {
 301  0
             if (value == null)
 302  
             {
 303  0
                 throw new NullPointerException("value is null for a not available property: " + key);
 304  
             }
 305  
         }
 306  
         else
 307  
         {
 308  0
             if (propertyDescriptor.getReadMethod() != null)
 309  
             {
 310  0
                 Object oldValue = getComponentProperty(propertyDescriptor);
 311  0
                 setComponentProperty(propertyDescriptor, value);
 312  0
                 return oldValue;
 313  
             }
 314  0
             setComponentProperty(propertyDescriptor, value);
 315  0
             return null;
 316  
         }
 317  0
         return _attributes.put(key, value);
 318  
     }
 319  
 
 320  
     /**
 321  
      * Retrieve info about getter/setter methods for the javabean property
 322  
      * of the specified name on the underlying UIComponent object.
 323  
      * <p/>
 324  
      * This method optimises access to javabean properties of the underlying
 325  
      * UIComponent by maintaining a cache of ProperyDescriptor objects for
 326  
      * that class.
 327  
      * <p/>
 328  
      * TODO: Consider making the cache shared between component instances;
 329  
      * currently 100 UIInputText components means performing introspection
 330  
      * on the UIInputText component 100 times.
 331  
      */
 332  
     private PropertyDescriptor getPropertyDescriptor(String key)
 333  
     {
 334  0
         if (_propertyDescriptorMap == null)
 335  
         {
 336  
             // Try to get descriptor map from cache
 337  0
             _propertyDescriptorMap = _propertyDescriptorCache.get(_component.getClass());
 338  
             // Cache miss: create descriptor map and put it in cache
 339  0
             if (_propertyDescriptorMap == null)
 340  
             {
 341  
                 // Create descriptor map...
 342  
                 BeanInfo beanInfo;
 343  
                 try
 344  
                 {
 345  0
                     beanInfo = Introspector.getBeanInfo(_component.getClass());
 346  
                 }
 347  0
                 catch (IntrospectionException e)
 348  
                 {
 349  0
                     throw new FacesException(e);
 350  0
                 }
 351  0
                 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
 352  0
                 _propertyDescriptorMap = new HashMap<String, PropertyDescriptor>();
 353  0
                 for (int i = 0; i < propertyDescriptors.length; i++)
 354  
                 {
 355  0
                     PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
 356  0
                     if (propertyDescriptor.getReadMethod() != null)
 357  
                     {
 358  0
                         _propertyDescriptorMap.put(propertyDescriptor.getName(),
 359  
                                 propertyDescriptor);
 360  
                     }
 361  
                 }
 362  
                 // ... and put it in cache
 363  0
                 _propertyDescriptorCache.put(_component.getClass(), _propertyDescriptorMap);
 364  
             }
 365  
         }
 366  0
         return _propertyDescriptorMap.get(key);
 367  
     }
 368  
 
 369  
 
 370  
     /**
 371  
      * Execute the getter method of the specified property on the underlying
 372  
      * component.
 373  
      *
 374  
      * @param propertyDescriptor specifies which property to read.
 375  
      * @return the value returned by the getter method.
 376  
      * @throws IllegalArgumentException if the property is not readable.
 377  
      * @throws FacesException           if any other problem occurs while invoking
 378  
      *                                  the getter method.
 379  
      */
 380  
     private Object getComponentProperty(PropertyDescriptor propertyDescriptor)
 381  
     {
 382  0
         Method readMethod = propertyDescriptor.getReadMethod();
 383  0
         if (readMethod == null)
 384  
         {
 385  0
             throw new IllegalArgumentException("Component property " + propertyDescriptor.getName() + " is not readable");
 386  
         }
 387  
         try
 388  
         {
 389  0
             return readMethod.invoke(_component, EMPTY_ARGS);
 390  
         }
 391  0
         catch (Exception e)
 392  
         {
 393  0
             FacesContext facesContext = _component.getFacesContext();
 394  0
             throw new FacesException("Could not get property " + propertyDescriptor.getName() + " of component " + _component.getClientId(facesContext), e);
 395  
         }
 396  
     }
 397  
 
 398  
     /**
 399  
      * Execute the setter method of the specified property on the underlying
 400  
      * component.
 401  
      *
 402  
      * @param propertyDescriptor specifies which property to write.
 403  
      * @throws IllegalArgumentException if the property is not writable.
 404  
      * @throws FacesException           if any other problem occurs while invoking
 405  
      *                                  the getter method.
 406  
      */
 407  
     private void setComponentProperty(PropertyDescriptor propertyDescriptor, Object value)
 408  
     {
 409  0
         Method writeMethod = propertyDescriptor.getWriteMethod();
 410  0
         if (writeMethod == null)
 411  
         {
 412  0
             throw new IllegalArgumentException("Component property " + propertyDescriptor.getName() + " is not writable");
 413  
         }
 414  
         try
 415  
         {
 416  0
             writeMethod.invoke(_component, new Object[]{value});
 417  
         }
 418  0
         catch (Exception e)
 419  
         {
 420  0
             FacesContext facesContext = _component.getFacesContext();
 421  0
             throw new FacesException("Could not set property " + propertyDescriptor.getName() +
 422  
                     " of component " + _component.getClientId(facesContext) + " to value : " + value + " with type : " +
 423  
                     (value == null ? "null" : value.getClass().getName()), e);
 424  0
         }
 425  0
     }
 426  
 
 427  
     private void checkKey(Object key)
 428  
     {
 429  0
         if (key == null)
 430  
         {
 431  0
             throw new NullPointerException("key");
 432  
         }
 433  0
         if (!(key instanceof String))
 434  
         {
 435  0
             throw new ClassCastException("key is not a String");
 436  
         }
 437  0
     }
 438  
 
 439  
     /**
 440  
      * Return the map containing the attributes.
 441  
      * <p/>
 442  
      * This method is package-scope so that the UIComponentBase class can access it
 443  
      * directly when serializing the component.
 444  
      */
 445  
     Map<Object, Object> getUnderlyingMap()
 446  
     {
 447  0
         return _attributes;
 448  
     }
 449  
 
 450  
     /**
 451  
      * TODO: Document why this method is necessary, and why it doesn't try to
 452  
      * compare the _component field.
 453  
      */
 454  
     public boolean equals(Object obj)
 455  
     {
 456  0
         return _attributes.equals(obj);
 457  
     }
 458  
 
 459  
     public int hashCode()
 460  
     {
 461  0
         return _attributes.hashCode();
 462  
     }
 463  
 }