blob: 5298b5968c371987139d035df0ac0239a4bbd165 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.test.mock;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* NOTE: Class copied from trinidad to be used on MockFlash.
* <p/>
* Map that wraps another to provide an isolated namespace using
* a prefix. This is especially handy for storing properties on
* the session in a structured manner without putting them into
* a true "Map" - because storing in a Map breaks session failover.
* (Session failover won't trigger on mutations of contained objects.)
* <p/>
* Note that there is a potential design flaw; if you create a SubKeyMap
* for "mypackage.foo" and for "mypackage.foo.bar", all the keys in the
* latter will actually show up in the former (prefixed by ".bar"). This
* "flaw" is actually relied on by PageFlowScopeMap (since it provides
* a handy way to clear out all descendents), so don't "fix" it!
*
* @since 1.0.0
*/
final class MockSubKeyMap<V> extends AbstractMap<String, V>
{
public MockSubKeyMap(Map<String, Object> base, String prefix)
{
if (base == null)
{
throw new NullPointerException();
}
if (prefix == null)
{
throw new NullPointerException();
}
// Optimize the scenario where we're wrapping another SubKeyMap
if (base instanceof MockSubKeyMap)
{
_base = ((MockSubKeyMap) base)._base;
_prefix = ((MockSubKeyMap) base)._prefix + prefix;
}
else
{
_base = base;
_prefix = prefix;
}
}
@Override
public boolean isEmpty()
{
return entrySet().isEmpty();
}
@Override
public V get(Object key)
{
key = _getBaseKey(key);
return (V) _base.get(key);
}
@Override
public V put(String key, V value)
{
key = _getBaseKey(key);
return (V) _base.put(key, value);
}
@Override
public V remove(Object key)
{
key = _getBaseKey(key);
return (V) _base.remove(key);
}
@Override
public boolean containsKey(Object key)
{
if (!(key instanceof String))
{
return false;
}
return _base.containsKey(_getBaseKey(key));
}
@Override
public Set<Map.Entry<String, V>> entrySet()
{
if (_entrySet == null)
{
_entrySet = new Entries<V>();
}
return _entrySet;
}
private String _getBaseKey(Object key)
{
if (key == null)
{
throw new NullPointerException();
}
// Yes, I want a ClassCastException if it's not a String
return _prefix + ((String) key);
}
private List<String> _gatherKeys()
{
List<String> list = new ArrayList<String>();
for (String key : _base.keySet())
{
if (key != null && key.startsWith(_prefix))
{
list.add(key);
}
}
return list;
}
//
// Set implementation for SubkeyMap.entrySet()
//
private class Entries<V> extends AbstractSet<Map.Entry<String, V>>
{
public Entries()
{
}
@Override
public Iterator<Map.Entry<String, V>> iterator()
{
// Sadly, if you just try to use a filtering approach
// on the iterator, you'll get concurrent modification
// exceptions. Consequently, gather the keys in a list
// and iterator over that.
List<String> keyList = _gatherKeys();
return new EntryIterator<V>(keyList.iterator());
}
@Override
public int size()
{
int size = 0;
for (String key : _base.keySet())
{
if (key != null && key.startsWith(_prefix))
{
size++;
}
}
return size;
}
@Override
public boolean isEmpty()
{
Iterator<String> keys = _base.keySet().iterator();
while (keys.hasNext())
{
String key = keys.next();
// Short-circuit: the default implementation would always
// need to iterate to find the total size.
if (key != null && key.startsWith(_prefix))
{
return false;
}
}
return true;
}
@Override
public void clear()
{
Iterator<String> keys = _base.keySet().iterator();
while (keys.hasNext())
{
String key = keys.next();
if (key != null && key.startsWith(_prefix))
{
keys.remove();
}
}
}
}
private class EntryIterator<V> implements Iterator<Map.Entry<String, V>>
{
public EntryIterator(Iterator<String> iterator)
{
_iterator = iterator;
}
public boolean hasNext()
{
return _iterator.hasNext();
}
public Map.Entry<String, V> next()
{
String baseKey = _iterator.next();
_currentKey = baseKey;
return new Entry<V>(baseKey);
}
public void remove()
{
if (_currentKey == null)
{
throw new IllegalStateException();
}
_base.remove(_currentKey);
_currentKey = null;
}
private Iterator<String> _iterator;
private String _currentKey;
}
private class Entry<V> implements Map.Entry<String, V>
{
public Entry(String baseKey)
{
_baseKey = baseKey;
}
public String getKey()
{
if (_key == null)
{
_key = _baseKey.substring(_prefix.length());
}
return _key;
}
public V getValue()
{
return (V) _base.get(_baseKey);
}
public V setValue(V value)
{
return (V) _base.put(_baseKey, value);
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o)
{
if (!(o instanceof Map.Entry))
{
return false;
}
Map.Entry<String, V> e = (Map.Entry<String, V>) o;
return _equals(getKey(), e.getKey())
&& _equals(getValue(), e.getValue());
}
@Override
public int hashCode()
{
Object key = getKey();
Object value = getValue();
return ((key == null) ? 0 : key.hashCode())
^ ((value == null) ? 0 : value.hashCode());
}
private String _baseKey;
private String _key;
}
static private boolean _equals(Object a, Object b)
{
if (a == null)
{
return b == null;
}
return a.equals(b);
}
private final Map<String, Object> _base;
private final String _prefix;
private Set<Map.Entry<String, V>> _entrySet;
}