001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.collections.map; 018 019 import java.io.IOException; 020 import java.io.ObjectInputStream; 021 import java.io.ObjectOutputStream; 022 import java.io.Serializable; 023 import java.util.HashMap; 024 import java.util.Map; 025 026 import org.apache.commons.collections.Factory; 027 import org.apache.commons.collections.Transformer; 028 import org.apache.commons.collections.functors.ConstantTransformer; 029 import org.apache.commons.collections.functors.FactoryTransformer; 030 031 /** 032 * Decorates another <code>Map</code> returning a default value if the map 033 * does not contain the requested key. 034 * <p> 035 * When the {@link #get(Object)} method is called with a key that does not 036 * exist in the map, this map will return the default value specified in 037 * the constructor/factory. Only the get method is altered, so the 038 * {@link Map#containsKey(Object)} can be used to determine if a key really 039 * is in the map or not. 040 * <p> 041 * The defaulted value is not added to the map. 042 * Compare this behaviour with {@link LazyMap}, which does add the value 043 * to the map (via a Transformer). 044 * <p> 045 * For instance: 046 * <pre> 047 * Map map = new DefaultedMap("NULL"); 048 * Object obj = map.get("Surname"); 049 * // obj == "NULL" 050 * </pre> 051 * After the above code is executed the map is still empty. 052 * <p> 053 * <strong>Note that DefaultedMap is not synchronized and is not thread-safe.</strong> 054 * If you wish to use this map from multiple threads concurrently, you must use 055 * appropriate synchronization. The simplest approach is to wrap this map 056 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 057 * exceptions when accessed by concurrent threads without synchronization. 058 * 059 * @since Commons Collections 3.2 060 * @version $Revision: 1.7 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ 061 * 062 * @author Stephen Colebourne 063 * @author Rafael U.C. Afonso 064 * @see LazyMap 065 */ 066 public class DefaultedMap 067 extends AbstractMapDecorator 068 implements Map, Serializable { 069 070 /** Serialization version */ 071 private static final long serialVersionUID = 19698628745827L; 072 073 /** The transformer to use if the map does not contain a key */ 074 protected final Object value; 075 076 //----------------------------------------------------------------------- 077 /** 078 * Factory method to create a defaulting map. 079 * <p> 080 * The value specified is returned when a missing key is found. 081 * 082 * @param map the map to decorate, must not be null 083 * @param defaultValue the default value to return when the key is not found 084 * @throws IllegalArgumentException if map is null 085 */ 086 public static Map decorate(Map map, Object defaultValue) { 087 if (defaultValue instanceof Transformer) { 088 defaultValue = ConstantTransformer.getInstance(defaultValue); 089 } 090 return new DefaultedMap(map, defaultValue); 091 } 092 093 /** 094 * Factory method to create a defaulting map. 095 * <p> 096 * The factory specified is called when a missing key is found. 097 * The result will be returned as the result of the map get(key) method. 098 * 099 * @param map the map to decorate, must not be null 100 * @param factory the factory to use, must not be null 101 * @throws IllegalArgumentException if map or factory is null 102 */ 103 public static Map decorate(Map map, Factory factory) { 104 if (factory == null) { 105 throw new IllegalArgumentException("Factory must not be null"); 106 } 107 return new DefaultedMap(map, FactoryTransformer.getInstance(factory)); 108 } 109 110 /** 111 * Factory method to create a defaulting map. 112 * <p> 113 * The transformer specified is called when a missing key is found. 114 * The key is passed to the transformer as the input, and the result 115 * will be returned as the result of the map get(key) method. 116 * 117 * @param map the map to decorate, must not be null 118 * @param factory the factory to use, must not be null 119 * @throws IllegalArgumentException if map or factory is null 120 */ 121 public static Map decorate(Map map, Transformer factory) { 122 if (factory == null) { 123 throw new IllegalArgumentException("Transformer must not be null"); 124 } 125 return new DefaultedMap(map, factory); 126 } 127 128 //----------------------------------------------------------------------- 129 /** 130 * Constructs a new empty <code>DefaultedMap</code> that decorates 131 * a <code>HashMap</code>. 132 * <p> 133 * The object passed in will be returned by the map whenever an 134 * unknown key is requested. 135 * 136 * @param defaultValue the default value to return when the key is not found 137 */ 138 public DefaultedMap(Object defaultValue) { 139 super(new HashMap()); 140 if (defaultValue instanceof Transformer) { 141 defaultValue = ConstantTransformer.getInstance(defaultValue); 142 } 143 this.value = defaultValue; 144 } 145 146 /** 147 * Constructor that wraps (not copies). 148 * 149 * @param map the map to decorate, must not be null 150 * @param value the value to use 151 * @throws IllegalArgumentException if map or transformer is null 152 */ 153 protected DefaultedMap(Map map, Object value) { 154 super(map); 155 this.value = value; 156 } 157 158 //----------------------------------------------------------------------- 159 /** 160 * Write the map out using a custom routine. 161 * 162 * @param out the output stream 163 * @throws IOException 164 */ 165 private void writeObject(ObjectOutputStream out) throws IOException { 166 out.defaultWriteObject(); 167 out.writeObject(map); 168 } 169 170 /** 171 * Read the map in using a custom routine. 172 * 173 * @param in the input stream 174 * @throws IOException 175 * @throws ClassNotFoundException 176 */ 177 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 178 in.defaultReadObject(); 179 map = (Map) in.readObject(); 180 } 181 182 //----------------------------------------------------------------------- 183 public Object get(Object key) { 184 // create value for key if key is not currently in the map 185 if (map.containsKey(key) == false) { 186 if (value instanceof Transformer) { 187 return ((Transformer) value).transform(key); 188 } 189 return value; 190 } 191 return map.get(key); 192 } 193 194 // no need to wrap keySet, entrySet or values as they are views of 195 // existing map entries - you can't do a map-style get on them. 196 }