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 }