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.util;
20  
21  import java.util.AbstractMap;
22  import java.util.AbstractSet;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.myfaces.trinidad.bean.FacesBean;
28  import org.apache.myfaces.trinidad.bean.PropertyKey;
29  import org.apache.myfaces.trinidad.util.CollectionUtils;
30  
31  /**
32   * Map implementation that exposes the properties of a FacesBean
33   * as a Map.  This Map supports Iterator.remove(), treats
34   * putting a null value as identical to removing the value,
35   * but does not support null keys.  The keys may be either
36   * Strings or {@link PropertyKey}s, but all Map.Entry objects
37   * will return String keys.
38   *
39   */
40  public class ValueMap extends AbstractMap<String, Object>
41  {
42    public ValueMap(FacesBean bean)
43    {
44      _bean = bean;
45    }
46  
47    @Override
48    public Object get(Object key)
49    {
50      if (key == null)
51        throw new NullPointerException();
52  
53      PropertyKey propertyKey = _getPropertyKey(key);
54      // Support attribute transparency for list-based
55      // properties
56      if (propertyKey.isList())
57      {
58        Class<?> type = propertyKey.getType();
59        if (type.isArray())
60          type = type.getComponentType();
61  
62        return _bean.getEntries(propertyKey, type);
63      }
64      else
65      {
66        Object val = _bean.getProperty(propertyKey);
67        return (val != null) ? val : propertyKey.getDefault();
68      }
69    }
70  
71    @Override
72    public Object put(String key, Object value)
73    {
74      if (key == null)
75        throw new NullPointerException();
76  
77      return _putInternal(_getPropertyKey(key), value);
78    }
79    
80    // TODO Should remove just remove values, or also remove bindings?
81    @Override
82    public Object remove(Object key)
83    {
84      PropertyKey propertyKey = _getPropertyKey(key);
85      Object oldValue = _bean.getProperty(propertyKey);
86      _bean.setProperty(propertyKey, null);
87  
88      return oldValue;
89    }
90  
91    /**
92     * Override for better performance
93     * @param key
94     * @return
95     */
96    @Override
97    public boolean containsKey(Object key)
98    {
99      if (key == null)
100       throw new NullPointerException();
101 
102     PropertyKey propertyKey = _getPropertyKey(key);
103 
104     // Some notes:
105     // 
106     // 1.  We could optimize this further by exposing containsLocalKey()
107     //     and containsValueExpression() methods on FacesBean.  This would
108     //     allow FacesBean implementations to avoid retrieving the
109     //     actual property value.  (For example, FacesBean implememtations
110     //     that delegate to FlaggedPropertyMap would be able to short-circuit
111     //     after checking flags, rather than have to look up the value in
112     //     the underlying PropertyMap.)  However, since this requires an
113     //     API change to the FacesBean interface, we are skipping this for
114     //     now.  We should re-evaluate once we can rely on Java 8 (and take
115     //     advantage of defender methods).
116     // 
117     // 2.  ValueMap.containsKey() has always checked for the presence of either
118     //     a local property value or a value expression.  This differs from
119     //     the corresponding code in Mojarra and MyFaces.  Both Mojarra and
120     //     MyFaces implement UIComponent.getAttributes().containsKey() by
121     //     only checking for the presence of a local propery.  Thus, to be
122     //     consistent, we could call _bean.getLocalProperty() here.  However,
123     //     given that ValueMap has behaved this way forever, we may have
124     //     users that depend on the current behavior.  As such, leaving
125     //     this as is.
126     //
127     // 3.  The "!= null" comparison that we perform here relies on the assumption
128     //     that FacesBean does not allow null property values.  While this is not
129     //     obvious from the FacesBean documentation, this is true in practice.
130     return (_bean.getRawProperty(propertyKey) != null);    
131   }
132 
133   @Override
134   public Set<Map.Entry<String, Object>> entrySet()
135   {
136     return CollectionUtils.overlappingCompositeSet(
137              new MakeEntries(_bean.keySet()),
138              new MakeEntries(_bean.bindingKeySet()));
139   }
140   
141   private Object _putInternal(PropertyKey propertyKey, Object value)
142   {
143     assert propertyKey != null;
144 
145     Object oldValue = _bean.getProperty(propertyKey);
146     _bean.setProperty(propertyKey, value);
147 
148     return oldValue;
149   }
150 
151   private class MakeEntries extends AbstractSet<Map.Entry<String, Object>>
152   {
153     public MakeEntries(Set<PropertyKey> keys)
154     {
155       _keys = keys;
156     }
157 
158     @Override
159     public int size()
160     {
161       return _keys.size();
162     }
163 
164     @Override
165     public Iterator<Map.Entry<String, Object>> iterator()
166     {
167       final Iterator<PropertyKey> base = _keys.iterator();
168       return new Iterator<Map.Entry<String, Object>>()
169       {
170         public boolean hasNext()
171         {
172           return base.hasNext();
173         }
174 
175         public Map.Entry<String, Object> next()
176         {
177           _lastEntry = new EntryImpl(base.next());
178           return _lastEntry;
179         }
180 
181         public void remove()
182         {
183           if (_lastEntry == null)
184             throw new IllegalStateException();
185           base.remove();
186           _lastEntry = null;
187         }
188         
189         private EntryImpl _lastEntry;
190       };
191     }
192 
193     private final Set<PropertyKey> _keys;
194   }
195 
196   private class EntryImpl implements Entry<String, Object>
197   {
198     public EntryImpl(PropertyKey key)
199     {
200       assert key != null;
201       
202       _key = key;
203     }
204 
205     public String getKey()
206     {
207       return _key.getName();
208     }
209 
210     public Object getValue()
211     {
212       return get(_key);
213     }
214 
215     public Object setValue(Object value)
216     {
217       return _putInternal(_key, value);
218     }
219 
220     @SuppressWarnings("unchecked")
221     @Override
222     public boolean equals(Object o)
223     {
224       if (o == this)
225         return true;
226 
227       if (!(o instanceof Map.Entry))
228         return false;
229 
230       Map.Entry<Object, Object> e = (Map.Entry<Object, Object>)o;
231       Object k1 = getKey();
232       Object k2 = e.getKey();
233       if (k1 == k2 || (k1 != null && k1.equals(k2)))
234       {
235         Object v1 = getValue();
236         Object v2 = e.getValue();
237         if (v1 == v2 || (v1 != null && v1.equals(v2))) 
238           return true;
239       }
240 
241       return false;
242     }
243     
244     @Override
245     public int hashCode()
246     {
247       Object value = getValue();
248       return _key.hashCode() ^ (value==null ? 0 : value.hashCode());
249     }
250 
251     private final PropertyKey _key;
252   }
253 
254   private PropertyKey _getPropertyKey(Object key)
255   {
256     if (key instanceof String)
257     {
258       String keyString = (String) key;
259       PropertyKey propertyKey = _bean.getType().findKey(keyString);
260       if (propertyKey == null)
261         propertyKey = PropertyKey.getDefaultPropertyKey(keyString);
262       
263       return propertyKey;
264     }
265     
266     if (key instanceof PropertyKey)
267     {
268       return (PropertyKey) key;
269     }
270     
271     throw new ClassCastException();
272   }
273 
274   private FacesBean    _bean;
275 }