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    *      http://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   *
49   * @see org.apache.commons.collections4.splitmap.TransformedSplitMap
50   * @since 3.0
51   */
52  public class TransformedMap<K, V>
53          extends AbstractInputCheckedMapDecorator<K, V>
54          implements Serializable {
55  
56      /** Serialization version */
57      private static final long serialVersionUID = 7023152376788900464L;
58  
59      /**
60       * Factory method to create a transforming map that will transform
61       * existing contents of the specified map.
62       * <p>
63       * If there are any elements already in the map being decorated, they
64       * will be transformed by this method.
65       * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}.
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       * Factory method to create a transforming map.
89       * <p>
90       * If there are any elements already in the map being decorated, they
91       * are NOT transformed.
92       * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}.
93       *
94       * @param <K>  the key type
95       * @param <V>  the value type
96       * @param map  the map to decorate, must not be null
97       * @param keyTransformer  the transformer to use for key conversion, null means no transformation
98       * @param valueTransformer  the transformer to use for value conversion, null means no transformation
99       * @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 }