View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.map;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.Serializable;
23  import java.util.Map;
24  
25  import org.apache.commons.collections4.Transformer;
26  
27  /**
28   * Decorates another {@code Map} to transform objects that are added.
29   * <p>
30   * The Map put methods and Map.Entry setValue method are affected by this class.
31   * Thus objects must be removed or searched for using their transformed form.
32   * For example, if the transformation converts Strings to Integers, you must
33   * use the Integer form to remove objects.
34   * </p>
35   * <p>
36   * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
37   * If you wish to use this map from multiple threads concurrently, you must use
38   * appropriate synchronization. The simplest approach is to wrap this map
39   * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
40   * exceptions when accessed by concurrent threads without synchronization.
41   * </p>
42   * <p>
43   * This class is Serializable from Commons Collections 3.1.
44   * </p>
45   *
46   * @param <K> The type of the keys in this map
47   * @param <V> The type of the values in this map
48   * @see org.apache.commons.collections4.splitmap.TransformedSplitMap
49   * @since 3.0
50   */
51  public class TransformedMap<K, V>
52          extends AbstractInputCheckedMapDecorator<K, V>
53          implements Serializable {
54  
55      /** Serialization version */
56      private static final long serialVersionUID = 7023152376788900464L;
57  
58      /**
59       * Factory method to create a transforming map that will transform
60       * existing contents of the specified map.
61       * <p>
62       * If there are any elements already in the map being decorated, they
63       * will be transformed by this method.
64       * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}.
65       * </p>
66       *
67       * @param <K>  the key type
68       * @param <V>  the value type
69       * @param map  The map to decorate, must not be null
70       * @param keyTransformer  The transformer to use for key conversion, null means no transformation
71       * @param valueTransformer  The transformer to use for value conversion, null means no transformation
72       * @return A new transformed map
73       * @throws NullPointerException if map is null
74       * @since 4.0
75       */
76      public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map,
77              final Transformer<? super K, ? extends K> keyTransformer,
78              final Transformer<? super V, ? extends V> valueTransformer) {
79          final TransformedMap<K, V> decorated = new TransformedMap<>(map, keyTransformer, valueTransformer);
80          if (!map.isEmpty()) {
81              final Map<K, V> transformed = decorated.transformMap(map);
82              decorated.clear();
83              decorated.decorated().putAll(transformed);  // avoids double transformation
84          }
85          return decorated;
86      }
87  
88      /**
89       * Factory method to create a transforming map.
90       * <p>
91       * If there are any elements already in the map being decorated, they
92       * are NOT transformed.
93       * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}.
94       * </p>
95       *
96       * @param <K>  the key type
97       * @param <V>  the value type
98       * @param map  The map to decorate, must not be null
99       * @param keyTransformer  The transformer to use for key conversion, null means no transformation
100      * @param valueTransformer  The transformer to use for value conversion, null means no transformation
101      * @return A new transformed map
102      * @throws NullPointerException if map is null
103      * @since 4.0
104      */
105     public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
106             final Transformer<? super K, ? extends K> keyTransformer,
107             final Transformer<? super V, ? extends V> valueTransformer) {
108         return new TransformedMap<>(map, keyTransformer, valueTransformer);
109     }
110 
111     /** The transformer to use for the key */
112     protected final Transformer<? super K, ? extends K> keyTransformer;
113 
114     /** The transformer to use for the value */
115     protected final Transformer<? super V, ? extends V> valueTransformer;
116 
117     /**
118      * Constructor that wraps (not copies).
119      * <p>
120      * If there are any elements already in the collection being decorated, they
121      * are NOT transformed.
122      * </p>
123      *
124      * @param map  The map to decorate, must not be null
125      * @param keyTransformer  The transformer to use for key conversion, null means no conversion
126      * @param valueTransformer  The transformer to use for value conversion, null means no conversion
127      * @throws NullPointerException if map is null
128      */
129     protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
130             final Transformer<? super V, ? extends V> valueTransformer) {
131         super(map);
132         this.keyTransformer = keyTransformer;
133         this.valueTransformer = valueTransformer;
134     }
135 
136     /**
137      * Override to transform the value when using {@code setValue}.
138      *
139      * @param value  The value to transform
140      * @return The transformed value
141      * @since 3.1
142      */
143     @Override
144     protected V checkSetValue(final V value) {
145         return valueTransformer.apply(value);
146     }
147 
148     /**
149      * Override to only return true when there is a value transformer.
150      *
151      * @return true if a value transformer is in use
152      * @since 3.1
153      */
154     @Override
155     protected boolean isSetValueChecking() {
156         return valueTransformer != null;
157     }
158 
159     @Override
160     public V put(K key, V value) {
161         key = transformKey(key);
162         value = transformValue(value);
163         return decorated().put(key, value);
164     }
165 
166     @Override
167     public void putAll(Map<? extends K, ? extends V> mapToCopy) {
168         mapToCopy = transformMap(mapToCopy);
169         decorated().putAll(mapToCopy);
170     }
171 
172     /**
173      * Deserializes the map in using a custom routine.
174      *
175      * @param in  The input stream
176      * @throws IOException Thrown if an error occurs while reading from the stream
177      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
178      * @since 3.1
179      */
180     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
181     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
182         in.defaultReadObject();
183         map = (Map<K, V>) in.readObject(); // (1)
184     }
185 
186     /**
187      * Transforms a key.
188      * <p>
189      * The transformer itself may throw an exception if necessary.
190      *
191      * @param object  The object to transform
192      * @return The transformed object
193      */
194     protected K transformKey(final K object) {
195         if (keyTransformer == null) {
196             return object;
197         }
198         return keyTransformer.apply(object);
199     }
200 
201     /**
202      * Transforms a map.
203      * <p>
204      * The transformer itself may throw an exception if necessary.
205      * </p>
206      *
207      * @param map  The map to transform
208      * @return The transformed object
209      */
210     @SuppressWarnings("unchecked")
211     protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
212         if (map.isEmpty()) {
213             return (Map<K, V>) map;
214         }
215         final Map<K, V> result = new LinkedMap<>(map.size());
216 
217         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
218             result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
219         }
220         return result;
221     }
222 
223     /**
224      * Transforms a value.
225      * <p>
226      * The transformer itself may throw an exception if necessary.
227      * </p>
228      *
229      * @param object  The object to transform
230      * @return The transformed object
231      */
232     protected V transformValue(final V object) {
233         if (valueTransformer == null) {
234             return object;
235         }
236         return valueTransformer.apply(object);
237     }
238 
239     /**
240      * Serializes this object to an ObjectOutputStream.
241      *
242      * @param out The target ObjectOutputStream.
243      * @throws IOException thrown when an I/O errors occur writing to the target stream.
244      * @since 3.1
245      */
246     private void writeObject(final ObjectOutputStream out) throws IOException {
247         out.defaultWriteObject();
248         out.writeObject(map);
249     }
250 
251 }