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.collections.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.collections.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   * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
36   * If you wish to use this map from multiple threads concurrently, you must use
37   * appropriate synchronization. The simplest approach is to wrap this map
38   * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
39   * exceptions when accessed by concurrent threads without synchronization.
40   * <p>
41   * This class is Serializable from Commons Collections 3.1.
42   * <p>
43   * @see org.apache.commons.collections.splitmap.TransformedMap
44   *
45   * @since 3.0
46   * @version $Id: TransformedMap.java 1429905 2013-01-07 17:15:14Z ggregory $
47   */
48  public class TransformedMap<K, V>
49          extends AbstractInputCheckedMapDecorator<K, V>
50          implements Serializable {
51  
52      /** Serialization version */
53      private static final long serialVersionUID = 7023152376788900464L;
54  
55      /** The transformer to use for the key */
56      protected final Transformer<? super K, ? extends K> keyTransformer;
57      /** The transformer to use for the value */
58      protected final Transformer<? super V, ? extends V> valueTransformer;
59  
60      /**
61       * Factory method to create a transforming map.
62       * <p>
63       * If there are any elements already in the map being decorated, they
64       * are NOT transformed.
65       * Contrast this with {@link #transformedMap(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 IllegalArgumentException if map is null
74       */
75      public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
76              final Transformer<? super K, ? extends K> keyTransformer,
77              final Transformer<? super V, ? extends V> valueTransformer) {
78          return new TransformedMap<K, V>(map, keyTransformer, valueTransformer);
79      }
80  
81      /**
82       * Factory method to create a transforming map that will transform
83       * existing contents of the specified map.
84       * <p>
85       * If there are any elements already in the map being decorated, they
86       * will be transformed by this method.
87       * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}.
88       *
89       * @param <K>  the key type
90       * @param <V>  the value type
91       * @param map  the map to decorate, must not be null
92       * @param keyTransformer  the transformer to use for key conversion, null means no transformation
93       * @param valueTransformer  the transformer to use for value conversion, null means no transformation
94       * @return a new transformed map
95       * @throws IllegalArgumentException if map is null
96       * @since 3.2
97       */
98      public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map,
99              final Transformer<? super K, ? extends K> keyTransformer,
100             final Transformer<? super V, ? extends V> valueTransformer) {
101         final TransformedMap<K, V> decorated = new TransformedMap<K, V>(map, keyTransformer, valueTransformer);
102         if (map.size() > 0) {
103             final Map<K, V> transformed = decorated.transformMap(map);
104             decorated.clear();
105             decorated.decorated().putAll(transformed);  // avoids double transformation
106         }
107         return decorated;
108     }
109 
110     //-----------------------------------------------------------------------
111     /**
112      * Constructor that wraps (not copies).
113      * <p>
114      * If there are any elements already in the collection being decorated, they
115      * are NOT transformed.
116      *
117      * @param map  the map to decorate, must not be null
118      * @param keyTransformer  the transformer to use for key conversion, null means no conversion
119      * @param valueTransformer  the transformer to use for value conversion, null means no conversion
120      * @throws IllegalArgumentException if map is null
121      */
122     protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
123             final Transformer<? super V, ? extends V> valueTransformer) {
124         super(map);
125         this.keyTransformer = keyTransformer;
126         this.valueTransformer = valueTransformer;
127     }
128 
129     //-----------------------------------------------------------------------
130     /**
131      * Write the map out using a custom routine.
132      *
133      * @param out  the output stream
134      * @throws IOException
135      * @since 3.1
136      */
137     private void writeObject(final ObjectOutputStream out) throws IOException {
138         out.defaultWriteObject();
139         out.writeObject(map);
140     }
141 
142     /**
143      * Read the map in using a custom routine.
144      *
145      * @param in  the input stream
146      * @throws IOException
147      * @throws ClassNotFoundException
148      * @since 3.1
149      */
150     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 
151     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
152         in.defaultReadObject();
153         map = (Map<K, V>) in.readObject(); // (1)
154     }
155 
156     //-----------------------------------------------------------------------
157     /**
158      * Transforms a key.
159      * <p>
160      * The transformer itself may throw an exception if necessary.
161      *
162      * @param object  the object to transform
163      * @return the transformed object
164      */
165     protected K transformKey(final K object) {
166         if (keyTransformer == null) {
167             return object;
168         }
169         return keyTransformer.transform(object);
170     }
171 
172     /**
173      * Transforms a value.
174      * <p>
175      * The transformer itself may throw an exception if necessary.
176      *
177      * @param object  the object to transform
178      * @return the transformed object
179      */
180     protected V transformValue(final V object) {
181         if (valueTransformer == null) {
182             return object;
183         }
184         return valueTransformer.transform(object);
185     }
186 
187     /**
188      * Transforms a map.
189      * <p>
190      * The transformer itself may throw an exception if necessary.
191      *
192      * @param map  the map to transform
193      * @return the transformed object
194      */
195     @SuppressWarnings("unchecked")
196     protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
197         if (map.isEmpty()) {
198             return (Map<K, V>) map;
199         }
200         final Map<K, V> result = new LinkedMap<K, V>(map.size());
201 
202         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
203             result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
204         }
205         return result;
206     }
207 
208     /**
209      * Override to transform the value when using <code>setValue</code>.
210      *
211      * @param value  the value to transform
212      * @return the transformed value
213      * @since 3.1
214      */
215     @Override
216     protected V checkSetValue(final V value) {
217         return valueTransformer.transform(value);
218     }
219 
220     /**
221      * Override to only return true when there is a value transformer.
222      *
223      * @return true if a value transformer is in use
224      * @since 3.1
225      */
226     @Override
227     protected boolean isSetValueChecking() {
228         return valueTransformer != null;
229     }
230 
231     //-----------------------------------------------------------------------
232     @Override
233     public V put(K key, V value) {
234         key = transformKey(key);
235         value = transformValue(value);
236         return decorated().put(key, value);
237     }
238 
239     @Override
240     public void putAll(Map<? extends K, ? extends V> mapToCopy) {
241         mapToCopy = transformMap(mapToCopy);
242         decorated().putAll(mapToCopy);
243     }
244 
245 }