/** * Copyright 2012 by dueni.ch * * 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 ch.dueni.util.collections; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; /** * <code>CreateOnWriteMap</code> is intended to be used for most likely empty maps within objects * that are intended to keep in session and therefore may occupy memory for long time.  * <code>CreateOnWriteMap</code> in best usage case will use 0 bytes of memory and still provides a * fully operable Map implementation using a call-back method to create the real map before first * {@link Map#put(Object, Object)} operation is executed. * <p> * Memory analysis for different types of maps show: * </p> * * <pre> * Nr Test case retained shallow * == ================================================== ========= ======= * 1 HashMap (default size) 120 40 * 2 HashMap (size 0) 56 40 * 3 LinkedHashMap (empty) 160 48 * 4 CreatOnWriteMap (empty, assigned to variable) 32 16 * 5 CreatOnWriteMap (return new from getList() method) 0 0 * </pre> * * <h5>Example code for above test nr 4</h5> * * <pre> * public class MyOwner { * private Map<String, String> map; * * public MyOwner() { * map = new CreateOnWriteMap<String, String>() { * @Override * public Map<String, String> newMap() { * map = new HashMap<String, String>(2); // size will grow to 2 on first put anyway * return map; * } * }; * } * * public Map<String, String> getMap() { * return map; * } * } * </pre> * * <h5>Example code for above test nr 5</h5> * * <pre> * public class MyOwner { * private Map<String, String> map; * * public MyOwner() { * } * * public Map<String, String> getMap() { * if (map == null) { * return new CreateOnWriteMap<String, String>() { * @Override * public Map<String, String> newMap() { * map = new HashMap<String, String>(2); // size will grow to 2 on first put anyway * return map; * } * }; * } * return map; * } * } * </pre> * * <h5>Which variant to use?</h5> * <p> * It is recommended to return new CreateOnWriteMap within the get-method as shown in * "example code for test nr 5" unless you have very frequent access to empty maps without putting * entries. * </p> * * @author Hanspeter Dünnenberger */ public abstract class CreateOnWriteMap<K, V> implements Map<K, V> { /** * To keep the just created map as delegate in case this CreateOnWriteMap is kept on local * variable. */ private Map<K, V> wrapped; /** * Return the just created real Map after assigning it to the owning object's member variable. * * Example use: * * <pre> * public class OwningType { * private Map<String, String> map; * * public Map<String, String> getMap() { * if (map == null) { * return new CreateOnWriteMap<String, String>() { * * @Override * public Map<String, String> newMap() { * map = new HashMap<String>(2); // init size 2, would grow to 2 on first put anyway * return map; * } * }; * } * return map; * } * } * </pre> * * @return the just created real Map after assigning it to the owning object's member variable. */ public abstract Map<K, V> newMap(); /** * Make sure wrapped is assigned from {@link #newMap()} return the wrapped Map. * * @return the real Map as returned from the call-back. */ private Map<K, V> getRealMap() { if (wrapped == null) { wrapped = newMap(); } return wrapped; } @Override public void clear() { if (wrapped != null) { wrapped.clear(); } } @Override public boolean containsKey(Object key) { if (wrapped != null) { return wrapped.containsKey(key); } return false; } @Override public boolean containsValue(Object value) { if (wrapped != null) { return wrapped.containsValue(value); } return false; } @Override @SuppressWarnings("unchecked") public Set<java.util.Map.Entry<K, V>> entrySet() { if (wrapped != null) { return wrapped.entrySet(); } return Collections.EMPTY_MAP.entrySet(); } @Override public V get(Object key) { if (wrapped != null) { return wrapped.get(key); } return null; } @Override public boolean isEmpty() { if (wrapped != null) { return wrapped.isEmpty(); } return true; } @Override @SuppressWarnings("unchecked") public Set<K> keySet() { if (wrapped != null) { return wrapped.keySet(); } return Collections.EMPTY_MAP.keySet(); } /** * Make sure wrapped is assigned from {@link #newMap()} and delegate the passed argument to the * wrapped Map. * * @see Map#put(Object, Object) */ @Override public V put(K key, V value) { return getRealMap().put(key, value); } /** * Make sure wrapped is assigned from {@link #newMap()} and delegate the passed argument to the * wrapped Map. * * @see Map#putAll(Map) */ @Override public void putAll(Map<? extends K, ? extends V> m) { getRealMap().putAll(m); } @Override public V remove(Object key) { if (wrapped != null) { return wrapped.remove(key); } return null; } @Override public int size() { if (wrapped != null) { return wrapped.size(); } return 0; } @Override @SuppressWarnings("unchecked") public Collection<V> values() { if (wrapped != null) { return wrapped.values(); } return Collections.EMPTY_MAP.values(); } @Override public boolean equals(Object obj) { if (wrapped != null) { return wrapped.equals(obj); } return super.equals(obj); } }