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.Iterator;
024    import java.util.Map;
025    
026    import org.apache.commons.collections.Transformer;
027    
028    /**
029     * Decorates another <code>Map</code> to transform objects that are added.
030     * <p>
031     * The Map put methods and Map.Entry setValue method are affected by this class.
032     * Thus objects must be removed or searched for using their transformed form.
033     * For example, if the transformation converts Strings to Integers, you must
034     * use the Integer form to remove objects.
035     * <p>
036     * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
037     * If you wish to use this map from multiple threads concurrently, you must use
038     * appropriate synchronization. The simplest approach is to wrap this map
039     * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 
040     * exceptions when accessed by concurrent threads without synchronization.
041     * <p>
042     * This class is Serializable from Commons Collections 3.1.
043     *
044     * @since Commons Collections 3.0
045     * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
046     * 
047     * @author Stephen Colebourne
048     */
049    public class TransformedMap
050            extends AbstractInputCheckedMapDecorator
051            implements Serializable {
052    
053        /** Serialization version */
054        private static final long serialVersionUID = 7023152376788900464L;
055    
056        /** The transformer to use for the key */
057        protected final Transformer keyTransformer;
058        /** The transformer to use for the value */
059        protected final Transformer valueTransformer;
060    
061        /**
062         * Factory method to create a transforming map.
063         * <p>
064         * If there are any elements already in the map being decorated, they
065         * are NOT transformed.
066         * Constrast this with {@link #decorateTransform}.
067         * 
068         * @param map  the map to decorate, must not be null
069         * @param keyTransformer  the transformer to use for key conversion, null means no transformation
070         * @param valueTransformer  the transformer to use for value conversion, null means no transformation
071         * @throws IllegalArgumentException if map is null
072         */
073        public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
074            return new TransformedMap(map, keyTransformer, valueTransformer);
075        }
076    
077        /**
078         * Factory method to create a transforming map that will transform
079         * existing contents of the specified map.
080         * <p>
081         * If there are any elements already in the map being decorated, they
082         * will be transformed by this method.
083         * Constrast this with {@link #decorate}.
084         * 
085         * @param map  the map to decorate, must not be null
086         * @param keyTransformer  the transformer to use for key conversion, null means no transformation
087         * @param valueTransformer  the transformer to use for value conversion, null means no transformation
088         * @throws IllegalArgumentException if map is null
089         * @since Commons Collections 3.2
090         */
091        public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
092            TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
093            if (map.size() > 0) {
094                Map transformed = decorated.transformMap(map);
095                decorated.clear();
096                decorated.getMap().putAll(transformed);  // avoids double transformation
097            }
098            return decorated;
099        }
100    
101        //-----------------------------------------------------------------------
102        /**
103         * Constructor that wraps (not copies).
104         * <p>
105         * If there are any elements already in the collection being decorated, they
106         * are NOT transformed.
107         * 
108         * @param map  the map to decorate, must not be null
109         * @param keyTransformer  the transformer to use for key conversion, null means no conversion
110         * @param valueTransformer  the transformer to use for value conversion, null means no conversion
111         * @throws IllegalArgumentException if map is null
112         */
113        protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
114            super(map);
115            this.keyTransformer = keyTransformer;
116            this.valueTransformer = valueTransformer;
117        }
118    
119        //-----------------------------------------------------------------------
120        /**
121         * Write the map out using a custom routine.
122         * 
123         * @param out  the output stream
124         * @throws IOException
125         * @since Commons Collections 3.1
126         */
127        private void writeObject(ObjectOutputStream out) throws IOException {
128            out.defaultWriteObject();
129            out.writeObject(map);
130        }
131    
132        /**
133         * Read the map in using a custom routine.
134         * 
135         * @param in  the input stream
136         * @throws IOException
137         * @throws ClassNotFoundException
138         * @since Commons Collections 3.1
139         */
140        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
141            in.defaultReadObject();
142            map = (Map) in.readObject();
143        }
144    
145        //-----------------------------------------------------------------------
146        /**
147         * Transforms a key.
148         * <p>
149         * The transformer itself may throw an exception if necessary.
150         * 
151         * @param object  the object to transform
152         * @throws the transformed object
153         */
154        protected Object transformKey(Object object) {
155            if (keyTransformer == null) {
156                return object;
157            }
158            return keyTransformer.transform(object);
159        }
160    
161        /**
162         * Transforms a value.
163         * <p>
164         * The transformer itself may throw an exception if necessary.
165         * 
166         * @param object  the object to transform
167         * @throws the transformed object
168         */
169        protected Object transformValue(Object object) {
170            if (valueTransformer == null) {
171                return object;
172            }
173            return valueTransformer.transform(object);
174        }
175    
176        /**
177         * Transforms a map.
178         * <p>
179         * The transformer itself may throw an exception if necessary.
180         * 
181         * @param map  the map to transform
182         * @throws the transformed object
183         */
184        protected Map transformMap(Map map) {
185            if (map.isEmpty()) {
186                return map;
187            }
188            Map result = new LinkedMap(map.size());
189            for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
190                Map.Entry entry = (Map.Entry) it.next();
191                result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
192            }
193            return result;
194        }
195    
196        /**
197         * Override to transform the value when using <code>setValue</code>.
198         * 
199         * @param value  the value to transform
200         * @return the transformed value
201         * @since Commons Collections 3.1
202         */
203        protected Object checkSetValue(Object value) {
204            return valueTransformer.transform(value);
205        }
206    
207        /**
208         * Override to only return true when there is a value transformer.
209         * 
210         * @return true if a value transformer is in use
211         * @since Commons Collections 3.1
212         */
213        protected boolean isSetValueChecking() {
214            return (valueTransformer != null);
215        }
216    
217        //-----------------------------------------------------------------------
218        public Object put(Object key, Object value) {
219            key = transformKey(key);
220            value = transformValue(value);
221            return getMap().put(key, value);
222        }
223    
224        public void putAll(Map mapToCopy) {
225            mapToCopy = transformMap(mapToCopy);
226            getMap().putAll(mapToCopy);
227        }
228    
229    }