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    }