/** * 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; /** * CreateOnWriteMap 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.  * CreateOnWriteMap 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. *

* Memory analysis for different types of maps show: *

* *
 * 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
 * 
* *
Example code for above test nr 4
* *
 * 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;
 * 	}
 * }
 * 
* *
Example code for above test nr 5
* *
 * 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;
 * 	}
 * }
 * 
* *
Which variant to use?
*

* 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. *

* * @author Hanspeter Dünnenberger */ public abstract class CreateOnWriteMap implements Map { /** * To keep the just created map as delegate in case this CreateOnWriteMap is kept on local * variable. */ private Map wrapped; /** * Return the just created real Map after assigning it to the owning object's member variable. * * Example use: * *
	 * 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;
	 * 	}
	 * }
	 * 
* * @return the just created real Map after assigning it to the owning object's member variable. */ public abstract Map 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 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> 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 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 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 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); } }