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