org.onebusaway.collections.FactoryMap Maven / Gradle / Ivy
/**
* Copyright (C) 2011 Brian Ferris
*
* Licensed 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.onebusaway.collections;
import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
/**
* A extension of {@link HashMap} that will automatically create a {@link Map}
* key-value entry if a call to {@link #get(Object)} is made where the key is
* not already present in the map.
*
* Default map entries can be created by passing in an instance of
* {@link IValueFactory} as an object factory (see
* {@link #FactoryMap(IValueFactory)}).
*
* Maps can also be created by passing a plain-old Java object. The class of the
* Java object will be used to create new value instances on demand, as long the
* class has a no-arg constructor (see {@link #FactoryMap(Object)}).
*
* @author bdferris
*/
public class FactoryMap extends HashMap {
private static final long serialVersionUID = 1L;
private IValueFactory _valueFactory;
/**
* Object factory interface for creating a new value for a specified key for
* use in {@link FactoryMap}
*
* @author bdferris
*/
public interface IValueFactory {
public VF create(KF key);
}
/**
* A convenience method for creating an instance of {@link FactoryMap} that
* wraps an existing {@link Map} and has a specific default value. The default
* value's class will be used to create new value instances as long as it has
* a no-arg constructor.
*
* @param map an existing map to wrap
* @param defaultValue see {@link #FactoryMap(Object)} for discussion
* @return a {@link Map} with factory-map behavior
*/
public static Map create(Map map, V defaultValue) {
return new MapImpl(map, new ClassInstanceFactory(
defaultValue.getClass()));
}
/**
* A convenience method for creating an instance of {@link FactoryMap} that
* wraps an existing {@link Map} and has a specific default value factory.
*
* @param map an existing map to wrap
* @param factory see {@link #FactoryMap(IValueFactory)} for discussion
* @return a {@link Map} with factory-map behavior
*/
public static Map create(Map map,
IValueFactory factory) {
return new MapImpl(map, factory);
}
/**
* A convenience method for creating an instance of {@link FactoryMap} that
* wraps an existing {@link SortedMap} and has a specific default value. The
* default value's class will be used to create new value instances as long as
* it has a no-arg constructor.
*
* @param map an existing sorted map to wrap
* @param defaultValue see {@link #FactoryMap(Object)} for discussion
* @return a {@link SortedMap} with factory-map behavior
*/
public static SortedMap createSorted(SortedMap map,
V defaultValue) {
return new SortedMapImpl(map, new ClassInstanceFactory(
defaultValue.getClass()));
}
/**
* A convenience method for creating an instance of {@link FactoryMap} that
* wraps an existing {@link SortedMap} and has a specific default value
* factory.
*
* @param map an existing sorted map to wrap
* @param factory see {@link #FactoryMap(IValueFactory)} for discussion
* @return a {@link SortedMap} with factory-map behavior
*/
public static SortedMap createSorted(SortedMap map,
IValueFactory factory) {
return new SortedMapImpl(map, factory);
}
/**
* A factory map constructor that accepts a default value instance. The
* {@link Class} of the default value instance will be used to create new
* default value instances as needed assuming the class has no-arg
* constructor. New values will be created when calls are made to
* {@link #get(Object)} and the specified key is not already present in the
* map. Why do we accept an object instance instead of a class instance? It
* makes it easier to handle cases where V is itself a parameterized type.
*
* @param factoryInstance the {@link Class} of the instance will be used to
* create new values as needed
*/
public FactoryMap(V factoryInstance) {
this(new ClassInstanceFactory(factoryInstance.getClass()));
}
/**
* A factory map constructor that accepts a {@link IValueFactory} default
* value factory. The value factory will be called when calls are made to
* {@link #get(Object)} and the specified key is not already present in the
* map.
*
* @param valueFactory the default value factory
*/
public FactoryMap(IValueFactory valueFactory) {
_valueFactory = valueFactory;
}
/**
* Returns the value to which the specified key is mapped, or a default value
* instance if the specified key is not present in the map. Subsequent clals
* to {@link #get(Object)} with the same key will return the same value
* instance.
*
* @see Map#get(Object)
* @see #put(Object, Object)
*/
@SuppressWarnings("unchecked")
@Override
public V get(Object key) {
if (!containsKey(key))
put((K) key, createValue((K) key));
return super.get(key);
}
private V createValue(K key) {
return _valueFactory.create(key);
}
private static class ClassInstanceFactory implements
IValueFactory, Serializable {
private static final long serialVersionUID = 1L;
private Class extends V> _valueClass;
@SuppressWarnings({"rawtypes", "unchecked"})
public ClassInstanceFactory(Class valueClass) {
_valueClass = valueClass;
}
public V create(K key) {
try {
return _valueClass.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
private static class MapImpl implements Map, Serializable {
private static final long serialVersionUID = 1L;
private Map _source;
private IValueFactory _valueFactory;
public MapImpl(Map source, IValueFactory valueFactory) {
_source = source;
_valueFactory = valueFactory;
}
public void clear() {
_source.clear();
}
public boolean containsKey(Object key) {
return _source.containsKey(key);
}
public boolean containsValue(Object value) {
return _source.containsValue(value);
}
public Set> entrySet() {
return _source.entrySet();
}
@SuppressWarnings("unchecked")
public V get(Object key) {
if (!containsKey(key))
_source.put((K) key, createValue((K) key));
return _source.get(key);
}
public boolean isEmpty() {
return _source.isEmpty();
}
public Set keySet() {
return _source.keySet();
}
public V put(K key, V value) {
return _source.put(key, value);
}
public void putAll(Map extends K, ? extends V> t) {
_source.putAll(t);
}
public V remove(Object key) {
return _source.remove(key);
}
public int size() {
return _source.size();
}
public Collection values() {
return _source.values();
}
private V createValue(K key) {
return _valueFactory.create(key);
}
}
private static class SortedMapImpl extends MapImpl implements
SortedMap {
private static final long serialVersionUID = 1L;
private SortedMap _source;
public SortedMapImpl(SortedMap source,
IValueFactory valueFactory) {
super(source, valueFactory);
_source = source;
}
public Comparator super K> comparator() {
return _source.comparator();
}
public K firstKey() {
return _source.firstKey();
}
public SortedMap headMap(K toKey) {
return _source.headMap(toKey);
}
public K lastKey() {
return _source.lastKey();
}
public SortedMap subMap(K fromKey, K toKey) {
return _source.subMap(fromKey, toKey);
}
public SortedMap tailMap(K fromKey) {
return _source.tailMap(fromKey);
}
}
}