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