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} 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 * <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 * <p>
043 * This class is Serializable from Commons Collections 3.1.
044 * </p>
045 *
046 * @param <K> the type of the keys in this map
047 * @param <V> the type of the values in this map
048 *
049 * @see org.apache.commons.collections4.splitmap.TransformedSplitMap
050 * @since 3.0
051 */
052public class TransformedMap<K, V>
053        extends AbstractInputCheckedMapDecorator<K, V>
054        implements Serializable {
055
056    /** Serialization version */
057    private static final long serialVersionUID = 7023152376788900464L;
058
059    /**
060     * Factory method to create a transforming map that will transform
061     * existing contents of the specified map.
062     * <p>
063     * If there are any elements already in the map being decorated, they
064     * will be transformed by this method.
065     * Contrast this with {@link #transformingMap(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 NullPointerException if map is null
074     * @since 4.0
075     */
076    public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map,
077            final Transformer<? super K, ? extends K> keyTransformer,
078            final Transformer<? super V, ? extends V> valueTransformer) {
079        final TransformedMap<K, V> decorated = new TransformedMap<>(map, keyTransformer, valueTransformer);
080        if (!map.isEmpty()) {
081            final Map<K, V> transformed = decorated.transformMap(map);
082            decorated.clear();
083            decorated.decorated().putAll(transformed);  // avoids double transformation
084        }
085        return decorated;
086    }
087    /**
088     * Factory method to create a transforming map.
089     * <p>
090     * If there are any elements already in the map being decorated, they
091     * are NOT transformed.
092     * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}.
093     *
094     * @param <K>  the key type
095     * @param <V>  the value type
096     * @param map  the map to decorate, must not be null
097     * @param keyTransformer  the transformer to use for key conversion, null means no transformation
098     * @param valueTransformer  the transformer to use for value conversion, null means no transformation
099     * @return a new transformed map
100     * @throws NullPointerException if map is null
101     * @since 4.0
102     */
103    public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
104            final Transformer<? super K, ? extends K> keyTransformer,
105            final Transformer<? super V, ? extends V> valueTransformer) {
106        return new TransformedMap<>(map, keyTransformer, valueTransformer);
107    }
108
109    /** The transformer to use for the key */
110    protected final Transformer<? super K, ? extends K> keyTransformer;
111
112    /** The transformer to use for the value */
113    protected final Transformer<? super V, ? extends V> valueTransformer;
114
115    /**
116     * Constructor that wraps (not copies).
117     * <p>
118     * If there are any elements already in the collection being decorated, they
119     * are NOT transformed.
120     *
121     * @param map  the map to decorate, must not be null
122     * @param keyTransformer  the transformer to use for key conversion, null means no conversion
123     * @param valueTransformer  the transformer to use for value conversion, null means no conversion
124     * @throws NullPointerException if map is null
125     */
126    protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
127            final Transformer<? super V, ? extends V> valueTransformer) {
128        super(map);
129        this.keyTransformer = keyTransformer;
130        this.valueTransformer = valueTransformer;
131    }
132
133    /**
134     * Override to transform the value when using {@code setValue}.
135     *
136     * @param value  the value to transform
137     * @return the transformed value
138     * @since 3.1
139     */
140    @Override
141    protected V checkSetValue(final V value) {
142        return valueTransformer.transform(value);
143    }
144
145    /**
146     * Override to only return true when there is a value transformer.
147     *
148     * @return true if a value transformer is in use
149     * @since 3.1
150     */
151    @Override
152    protected boolean isSetValueChecking() {
153        return valueTransformer != null;
154    }
155
156    @Override
157    public V put(K key, V value) {
158        key = transformKey(key);
159        value = transformValue(value);
160        return decorated().put(key, value);
161    }
162
163    @Override
164    public void putAll(Map<? extends K, ? extends V> mapToCopy) {
165        mapToCopy = transformMap(mapToCopy);
166        decorated().putAll(mapToCopy);
167    }
168
169    /**
170     * Read the map in using a custom routine.
171     *
172     * @param in  the input stream
173     * @throws IOException if an error occurs while reading from the stream
174     * @throws ClassNotFoundException if an object read from the stream can not be loaded
175     * @since 3.1
176     */
177    @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
178    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
179        in.defaultReadObject();
180        map = (Map<K, V>) in.readObject(); // (1)
181    }
182
183    /**
184     * Transforms a key.
185     * <p>
186     * The transformer itself may throw an exception if necessary.
187     *
188     * @param object  the object to transform
189     * @return the transformed object
190     */
191    protected K transformKey(final K object) {
192        if (keyTransformer == null) {
193            return object;
194        }
195        return keyTransformer.transform(object);
196    }
197
198    /**
199     * Transforms a map.
200     * <p>
201     * The transformer itself may throw an exception if necessary.
202     *
203     * @param map  the map to transform
204     * @return the transformed object
205     */
206    @SuppressWarnings("unchecked")
207    protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
208        if (map.isEmpty()) {
209            return (Map<K, V>) map;
210        }
211        final Map<K, V> result = new LinkedMap<>(map.size());
212
213        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
214            result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
215        }
216        return result;
217    }
218
219    /**
220     * Transforms a value.
221     * <p>
222     * The transformer itself may throw an exception if necessary.
223     *
224     * @param object  the object to transform
225     * @return the transformed object
226     */
227    protected V transformValue(final V object) {
228        if (valueTransformer == null) {
229            return object;
230        }
231        return valueTransformer.transform(object);
232    }
233
234    /**
235     * Write the map out using a custom routine.
236     *
237     * @param out  the output stream
238     * @throws IOException if an error occurs while writing to the stream
239     * @since 3.1
240     */
241    private void writeObject(final ObjectOutputStream out) throws IOException {
242        out.defaultWriteObject();
243        out.writeObject(map);
244    }
245
246}