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.multimap;
18
19 import java.util.Iterator;
20 import java.util.Map;
21 import java.util.Objects;
22
23 import org.apache.commons.collections4.CollectionUtils;
24 import org.apache.commons.collections4.FluentIterable;
25 import org.apache.commons.collections4.MultiValuedMap;
26 import org.apache.commons.collections4.Transformer;
27
28 /**
29 * Decorates another {@code MultiValuedMap} to transform objects that are added.
30 * <p>
31 * This class affects the MultiValuedMap put methods. Thus objects must be
32 * removed or searched for using their transformed form. For example, if the
33 * transformation converts Strings to Integers, you must use the Integer form to
34 * remove objects.
35 * </p>
36 * <p>
37 * <strong>Note that TransformedMultiValuedMap is not synchronized and is not thread-safe.</strong>
38 * </p>
39 *
40 * @param <K> the type of the keys in this map
41 * @param <V> the type of the values in this map
42 * @since 4.1
43 */
44 public class TransformedMultiValuedMap<K, V> extends AbstractMultiValuedMapDecorator<K, V> {
45
46 /** Serialization Version */
47 private static final long serialVersionUID = 20150612L;
48
49 /**
50 * Factory method to create a transforming MultiValuedMap that will
51 * transform existing contents of the specified map.
52 * <p>
53 * If there are any elements already in the map being decorated, they will
54 * be transformed by this method. Contrast this with
55 * {@link #transformingMap(MultiValuedMap, Transformer, Transformer)}.
56 * </p>
57 *
58 * @param <K> the key type
59 * @param <V> the value type
60 * @param map the MultiValuedMap to decorate, may not be null
61 * @param keyTransformer the transformer to use for key conversion, null means no conversion
62 * @param valueTransformer the transformer to use for value conversion, null means no conversion
63 * @return a new transformed MultiValuedMap
64 * @throws NullPointerException if map is null
65 */
66 public static <K, V> TransformedMultiValuedMap<K, V> transformedMap(final MultiValuedMap<K, V> map,
67 final Transformer<? super K, ? extends K> keyTransformer,
68 final Transformer<? super V, ? extends V> valueTransformer) {
69 final TransformedMultiValuedMap<K, V> decorated =
70 new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer);
71 if (!map.isEmpty()) {
72 final MultiValuedMap<K, V> mapCopy = new ArrayListValuedHashMap<>(map);
73 decorated.clear();
74 decorated.putAll(mapCopy);
75 }
76 return decorated;
77 }
78
79 /**
80 * Factory method to create a transforming MultiValuedMap.
81 * <p>
82 * If there are any elements already in the map being decorated, they are
83 * NOT transformed. Contrast this with
84 * {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}.
85 * </p>
86 *
87 * @param <K> the key type
88 * @param <V> the value type
89 * @param map the MultiValuedMap to decorate, may not be null
90 * @param keyTransformer the transformer to use for key conversion, null means no conversion
91 * @param valueTransformer the transformer to use for value conversion, null means no conversion
92 * @return a new transformed MultiValuedMap
93 * @throws NullPointerException if map is null
94 */
95 public static <K, V> TransformedMultiValuedMap<K, V> transformingMap(final MultiValuedMap<K, V> map,
96 final Transformer<? super K, ? extends K> keyTransformer,
97 final Transformer<? super V, ? extends V> valueTransformer) {
98 return new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer);
99 }
100
101 /** The key transformer */
102 private final Transformer<? super K, ? extends K> keyTransformer;
103
104 /** The value transformer */
105 private final Transformer<? super V, ? extends V> valueTransformer;
106
107 /**
108 * Constructor that wraps (not copies).
109 * <p>
110 * If there are any elements already in the collection being decorated, they
111 * are NOT transformed.
112 * </p>
113 *
114 * @param map the MultiValuedMap to decorate, may not be null
115 * @param keyTransformer the transformer to use for key conversion, null means no conversion
116 * @param valueTransformer the transformer to use for value conversion, null means no conversion
117 * @throws NullPointerException if map is null
118 */
119 protected TransformedMultiValuedMap(final MultiValuedMap<K, V> map,
120 final Transformer<? super K, ? extends K> keyTransformer,
121 final Transformer<? super V, ? extends V> valueTransformer) {
122 super(map);
123 this.keyTransformer = keyTransformer;
124 this.valueTransformer = valueTransformer;
125 }
126
127 @Override
128 public boolean put(final K key, final V value) {
129 return decorated().put(transformKey(key), transformValue(value));
130 }
131
132 @Override
133 public boolean putAll(final K key, final Iterable<? extends V> values) {
134 Objects.requireNonNull(values, "values");
135
136 final Iterable<V> transformedValues = FluentIterable.of(values).transform(valueTransformer);
137 final Iterator<? extends V> it = transformedValues.iterator();
138 return it.hasNext() && CollectionUtils.addAll(decorated().get(transformKey(key)), it);
139 }
140
141 @Override
142 public boolean putAll(final Map<? extends K, ? extends V> map) {
143 Objects.requireNonNull(map, "map");
144 boolean changed = false;
145 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
146 changed |= put(entry.getKey(), entry.getValue());
147 }
148 return changed;
149 }
150
151 @Override
152 public boolean putAll(final MultiValuedMap<? extends K, ? extends V> map) {
153 Objects.requireNonNull(map, "map");
154 boolean changed = false;
155 for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) {
156 changed |= put(entry.getKey(), entry.getValue());
157 }
158 return changed;
159 }
160
161 /**
162 * Transforms a key.
163 * <p>
164 * The transformer itself may throw an exception if necessary.
165 * </p>
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.apply(object);
175 }
176
177 /**
178 * Transforms a value.
179 * <p>
180 * The transformer itself may throw an exception if necessary.
181 * </p>
182 *
183 * @param object the object to transform
184 * @return the transformed object
185 */
186 protected V transformValue(final V object) {
187 if (valueTransformer == null) {
188 return object;
189 }
190 return valueTransformer.apply(object);
191 }
192
193 }