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   * @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       * 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       * </p>
94       *
95       * @param <K>  the key type
96       * @param <V>  the value type
97       * @param map  the map to decorate, must not be null
98       * @param keyTransformer  the transformer to use for key conversion, null means no transformation
99       * @param valueTransformer  the transformer to use for value conversion, null means no transformation
100      * @return a new transformed map
101      * @throws NullPointerException if map is null
102      * @since 4.0
103      */
104     public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
105             final Transformer<? super K, ? extends K> keyTransformer,
106             final Transformer<? super V, ? extends V> valueTransformer) {
107         return new TransformedMap<>(map, keyTransformer, valueTransformer);
108     }
109 
110     /** The transformer to use for the key */
111     protected final Transformer<? super K, ? extends K> keyTransformer;
112 
113     /** The transformer to use for the value */
114     protected final Transformer<? super V, ? extends V> valueTransformer;
115 
116     /**
117      * Constructor that wraps (not copies).
118      * <p>
119      * If there are any elements already in the collection being decorated, they
120      * are NOT transformed.
121      * </p>
122      *
123      * @param map  the map to decorate, must not be null
124      * @param keyTransformer  the transformer to use for key conversion, null means no conversion
125      * @param valueTransformer  the transformer to use for value conversion, null means no conversion
126      * @throws NullPointerException if map is null
127      */
128     protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
129             final Transformer<? super V, ? extends V> valueTransformer) {
130         super(map);
131         this.keyTransformer = keyTransformer;
132         this.valueTransformer = valueTransformer;
133     }
134 
135     /**
136      * Override to transform the value when using {@code setValue}.
137      *
138      * @param value  the value to transform
139      * @return the transformed value
140      * @since 3.1
141      */
142     @Override
143     protected V checkSetValue(final V value) {
144         return valueTransformer.apply(value);
145     }
146 
147     /**
148      * Override to only return true when there is a value transformer.
149      *
150      * @return true if a value transformer is in use
151      * @since 3.1
152      */
153     @Override
154     protected boolean isSetValueChecking() {
155         return valueTransformer != null;
156     }
157 
158     @Override
159     public V put(K key, V value) {
160         key = transformKey(key);
161         value = transformValue(value);
162         return decorated().put(key, value);
163     }
164 
165     @Override
166     public void putAll(Map<? extends K, ? extends V> mapToCopy) {
167         mapToCopy = transformMap(mapToCopy);
168         decorated().putAll(mapToCopy);
169     }
170 
171     /**
172      * Deserializes the map in using a custom routine.
173      *
174      * @param in  the input stream
175      * @throws IOException if an error occurs while reading from the stream
176      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
177      * @since 3.1
178      */
179     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
180     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
181         in.defaultReadObject();
182         map = (Map<K, V>) in.readObject(); // (1)
183     }
184 
185     /**
186      * Transforms a key.
187      * <p>
188      * The transformer itself may throw an exception if necessary.
189      *
190      * @param object  the object to transform
191      * @return the transformed object
192      */
193     protected K transformKey(final K object) {
194         if (keyTransformer == null) {
195             return object;
196         }
197         return keyTransformer.apply(object);
198     }
199 
200     /**
201      * Transforms a map.
202      * <p>
203      * The transformer itself may throw an exception if necessary.
204      * </p>
205      *
206      * @param map  the map to transform
207      * @return the transformed object
208      */
209     @SuppressWarnings("unchecked")
210     protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
211         if (map.isEmpty()) {
212             return (Map<K, V>) map;
213         }
214         final Map<K, V> result = new LinkedMap<>(map.size());
215 
216         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
217             result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
218         }
219         return result;
220     }
221 
222     /**
223      * Transforms a value.
224      * <p>
225      * The transformer itself may throw an exception if necessary.
226      * </p>
227      *
228      * @param object  the object to transform
229      * @return the transformed object
230      */
231     protected V transformValue(final V object) {
232         if (valueTransformer == null) {
233             return object;
234         }
235         return valueTransformer.apply(object);
236     }
237 
238     /**
239      * Serializes this object to an ObjectOutputStream.
240      *
241      * @param out the target ObjectOutputStream.
242      * @throws IOException thrown when an I/O errors occur writing to the target stream.
243      * @since 3.1
244      */
245     private void writeObject(final ObjectOutputStream out) throws IOException {
246         out.defaultWriteObject();
247         out.writeObject(map);
248     }
249 
250 }