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</code> 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      /** The transformer to use for the key */
60      protected final Transformer<? super K, ? extends K> keyTransformer;
61      /** The transformer to use for the value */
62      protected final Transformer<? super V, ? extends V> valueTransformer;
63  
64      /**
65       * Factory method to create a transforming map.
66       * <p>
67       * If there are any elements already in the map being decorated, they
68       * are NOT transformed.
69       * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}.
70       *
71       * @param <K>  the key type
72       * @param <V>  the value type
73       * @param map  the map to decorate, must not be null
74       * @param keyTransformer  the transformer to use for key conversion, null means no transformation
75       * @param valueTransformer  the transformer to use for value conversion, null means no transformation
76       * @return a new transformed map
77       * @throws NullPointerException if map is null
78       * @since 4.0
79       */
80      public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
81              final Transformer<? super K, ? extends K> keyTransformer,
82              final Transformer<? super V, ? extends V> valueTransformer) {
83          return new TransformedMap<>(map, keyTransformer, valueTransformer);
84      }
85  
86      /**
87       * Factory method to create a transforming map that will transform
88       * existing contents of the specified map.
89       * <p>
90       * If there are any elements already in the map being decorated, they
91       * will be transformed by this method.
92       * Contrast this with {@link #transformingMap(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> transformedMap(final Map<K, V> map,
104             final Transformer<? super K, ? extends K> keyTransformer,
105             final Transformer<? super V, ? extends V> valueTransformer) {
106         final TransformedMap<K, V> decorated = new TransformedMap<>(map, keyTransformer, valueTransformer);
107         if (map.size() > 0) {
108             final Map<K, V> transformed = decorated.transformMap(map);
109             decorated.clear();
110             decorated.decorated().putAll(transformed);  // avoids double transformation
111         }
112         return decorated;
113     }
114 
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      *
122      * @param map  the map to decorate, must not be null
123      * @param keyTransformer  the transformer to use for key conversion, null means no conversion
124      * @param valueTransformer  the transformer to use for value conversion, null means no conversion
125      * @throws NullPointerException if map is null
126      */
127     protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
128             final Transformer<? super V, ? extends V> valueTransformer) {
129         super(map);
130         this.keyTransformer = keyTransformer;
131         this.valueTransformer = valueTransformer;
132     }
133 
134     //-----------------------------------------------------------------------
135     /**
136      * Write the map out using a custom routine.
137      *
138      * @param out  the output stream
139      * @throws IOException if an error occurs while writing to the stream
140      * @since 3.1
141      */
142     private void writeObject(final ObjectOutputStream out) throws IOException {
143         out.defaultWriteObject();
144         out.writeObject(map);
145     }
146 
147     /**
148      * Read the map in using a custom routine.
149      *
150      * @param in  the input stream
151      * @throws IOException if an error occurs while reading from the stream
152      * @throws ClassNotFoundException if an object read from the stream can not be loaded
153      * @since 3.1
154      */
155     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
156     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
157         in.defaultReadObject();
158         map = (Map<K, V>) in.readObject(); // (1)
159     }
160 
161     //-----------------------------------------------------------------------
162     /**
163      * Transforms a key.
164      * <p>
165      * The transformer itself may throw an exception if necessary.
166      *
167      * @param object  the object to transform
168      * @return the transformed object
169      */
170     protected K transformKey(final K object) {
171         if (keyTransformer == null) {
172             return object;
173         }
174         return keyTransformer.transform(object);
175     }
176 
177     /**
178      * Transforms a value.
179      * <p>
180      * The transformer itself may throw an exception if necessary.
181      *
182      * @param object  the object to transform
183      * @return the transformed object
184      */
185     protected V transformValue(final V object) {
186         if (valueTransformer == null) {
187             return object;
188         }
189         return valueTransformer.transform(object);
190     }
191 
192     /**
193      * Transforms a map.
194      * <p>
195      * The transformer itself may throw an exception if necessary.
196      *
197      * @param map  the map to transform
198      * @return the transformed object
199      */
200     @SuppressWarnings("unchecked")
201     protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
202         if (map.isEmpty()) {
203             return (Map<K, V>) map;
204         }
205         final Map<K, V> result = new LinkedMap<>(map.size());
206 
207         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
208             result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
209         }
210         return result;
211     }
212 
213     /**
214      * Override to transform the value when using <code>setValue</code>.
215      *
216      * @param value  the value to transform
217      * @return the transformed value
218      * @since 3.1
219      */
220     @Override
221     protected V checkSetValue(final V value) {
222         return valueTransformer.transform(value);
223     }
224 
225     /**
226      * Override to only return true when there is a value transformer.
227      *
228      * @return true if a value transformer is in use
229      * @since 3.1
230      */
231     @Override
232     protected boolean isSetValueChecking() {
233         return valueTransformer != null;
234     }
235 
236     //-----------------------------------------------------------------------
237     @Override
238     public V put(K key, V value) {
239         key = transformKey(key);
240         value = transformValue(value);
241         return decorated().put(key, value);
242     }
243 
244     @Override
245     public void putAll(Map<? extends K, ? extends V> mapToCopy) {
246         mapToCopy = transformMap(mapToCopy);
247         decorated().putAll(mapToCopy);
248     }
249 
250 }