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.shared.util;
20  
21  import java.util.AbstractMap;
22  import java.util.AbstractSet;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  /**
30   * NOTE: Class copied from trinidad to be used on FlashImpl.
31   * 
32   * Map that wraps another to provide an isolated namespace using
33   * a prefix.  This is especially handy for storing properties on
34   * the session in a structured manner without putting them into
35   * a true "Map" - because storing in a Map breaks session failover.
36   * (Session failover won't trigger on mutations of contained objects.)
37   * <p>
38   * Note that there is a potential design flaw;  if you create a SubKeyMap
39   * for "mypackage.foo" and for "mypackage.foo.bar", all the keys in the
40   * latter will actually show up in the former (prefixed by ".bar").  This
41   * "flaw" is actually relied on by PageFlowScopeMap (since it provides
42   * a handy way to clear out all descendents), so don't "fix" it!
43   */
44  public final class SubKeyMap<V> extends AbstractMap<String, V>
45  {
46      public SubKeyMap(Map<String, Object> base, String prefix)
47      {
48          if (base == null)
49          {
50              throw new NullPointerException();
51          }
52          if (prefix == null)
53          {
54              throw new NullPointerException();
55          }
56  
57          // Optimize the scenario where we're wrapping another SubKeyMap
58          if (base instanceof SubKeyMap)
59          {
60              _base = ((SubKeyMap) base)._base;
61              _prefix = ((SubKeyMap) base)._prefix + prefix;
62          }
63          else
64          {
65              _base = base;
66              _prefix = prefix;
67          }
68          _keyBuffer = new StringBuilder(32);
69      }
70  
71      @Override
72      public boolean isEmpty()
73      {
74          return entrySet().isEmpty();
75      }
76  
77      @Override
78      public V get(Object key)
79      {
80          key = _getBaseKey(key);
81          return (V) _base.get(key);
82      }
83  
84      @Override
85      public V put(String key, V value)
86      {
87          key = _getBaseKey(key);
88          return (V) _base.put(key, value);
89      }
90  
91      @Override
92      public V remove(Object key)
93      {
94          key = _getBaseKey(key);
95          return (V) _base.remove(key);
96      }
97  
98      @Override
99      public boolean containsKey(Object key)
100     {
101         if (!(key instanceof String))
102         {
103             return false;
104         }
105 
106         return _base.containsKey(_getBaseKey(key));
107     }
108 
109     @Override
110     public Set<Map.Entry<String, V>> entrySet()
111     {
112         if (_entrySet == null)
113         {
114             _entrySet = new Entries<V>();
115         }
116         return _entrySet;
117     }
118 
119     private String _getBaseKey(Object key)
120     {
121         if (key == null)
122         {
123             throw new NullPointerException();
124         }
125         // Yes, I want a ClassCastException if it's not a String
126         //return _prefix + ((String) key);
127         _keyBuffer.setLength(0);
128         _keyBuffer.append(_prefix);
129         _keyBuffer.append((String) key);
130         return _keyBuffer.toString();
131     }
132 
133     private List<String> _gatherKeys()
134     {
135         List<String> list = new ArrayList<String>();
136         for (String key : _base.keySet())
137         {
138             if (key != null && key.startsWith(_prefix))
139             {
140                 list.add(key);
141             }
142         }
143 
144         return list;
145     }
146 
147     //
148     // Set implementation for SubkeyMap.entrySet()
149     //
150     private class Entries<V> extends AbstractSet<Map.Entry<String, V>>
151     {
152         public Entries()
153         {
154         }
155 
156         @Override
157         public Iterator<Map.Entry<String, V>> iterator()
158         {
159             // Sadly, if you just try to use a filtering approach
160             // on the iterator, you'll get concurrent modification
161             // exceptions.  Consequently, gather the keys in a list
162             // and iterator over that.
163             List<String> keyList = _gatherKeys();
164             return new EntryIterator<V>(keyList.iterator());
165         }
166 
167         @Override
168         public int size()
169         {
170             int size = 0;
171             for (String key : _base.keySet())
172             {
173                 if (key != null && key.startsWith(_prefix))
174                 {
175                     size++;
176                 }
177             }
178 
179             return size;
180         }
181 
182         @Override
183         public boolean isEmpty()
184         {
185             Iterator<String> keys = _base.keySet().iterator();
186             while (keys.hasNext())
187             {
188                 String key = keys.next();
189                 // Short-circuit:  the default implementation would always
190                 // need to iterate to find the total size.
191                 if (key != null && key.startsWith(_prefix))
192                 {
193                     return false;
194                 }
195             }
196 
197             return true;
198         }
199 
200         @Override
201         public void clear()
202         {
203             Iterator<String> keys = _base.keySet().iterator();
204             while (keys.hasNext())
205             {
206                 String key = keys.next();
207                 if (key != null && key.startsWith(_prefix))
208                 {
209                     keys.remove();
210                 }
211             }
212         }
213     }
214 
215     private class EntryIterator<V> implements Iterator<Map.Entry<String, V>>
216     {
217         public EntryIterator(Iterator<String> iterator)
218         {
219             _iterator = iterator;
220         }
221 
222         public boolean hasNext()
223         {
224             return _iterator.hasNext();
225         }
226 
227         public Map.Entry<String, V> next()
228         {
229             String baseKey = _iterator.next();
230             _currentKey = baseKey;
231             return new Entry<V>(baseKey);
232         }
233 
234         public void remove()
235         {
236             if (_currentKey == null)
237             {
238                 throw new IllegalStateException();
239             }
240 
241             _base.remove(_currentKey);
242 
243             _currentKey = null;
244         }
245 
246         private Iterator<String> _iterator;
247         private String _currentKey;
248     }
249 
250     private class Entry<V> implements Map.Entry<String, V>
251     {
252         public Entry(String baseKey)
253         {
254             _baseKey = baseKey;
255         }
256 
257         public String getKey()
258         {
259             if (_key == null)
260             {
261                 _key = _baseKey.substring(_prefix.length());
262             }
263             return _key;
264         }
265 
266         public V getValue()
267         {
268             return (V) _base.get(_baseKey);
269         }
270 
271         public V setValue(V value)
272         {
273             return (V) _base.put(_baseKey, value);
274         }
275 
276         @SuppressWarnings("unchecked")
277         @Override
278         public boolean equals(Object o)
279         {
280             if (!(o instanceof Map.Entry))
281             {
282                 return false;
283             }
284             Map.Entry<String, V> e = (Map.Entry<String, V>) o;
285             return _equals(getKey(), e.getKey())
286                     && _equals(getValue(), e.getValue());
287         }
288 
289         @Override
290         public int hashCode()
291         {
292             Object key = getKey();
293             Object value = getValue();
294             return ((key == null) ? 0 : key.hashCode())
295                     ^ ((value == null) ? 0 : value.hashCode());
296         }
297 
298         private String _baseKey;
299         private String _key;
300     }
301 
302     static private boolean _equals(Object a, Object b)
303     {
304         if (a == null)
305         {
306             return b == null;
307         }
308         return a.equals(b);
309     }
310 
311     private final Map<String, Object> _base;
312     private final String _prefix;
313     private Set<Map.Entry<String, V>> _entrySet;
314     private StringBuilder _keyBuffer;
315 
316 }