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.splitmap; 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 import java.util.Objects; 25 26 import org.apache.commons.collections4.Put; 27 import org.apache.commons.collections4.Transformer; 28 import org.apache.commons.collections4.map.LinkedMap; 29 30 /** 31 * Decorates another {@link Map} to transform objects that are added. 32 * <p> 33 * The Map put methods and Map.Entry setValue method are affected by this class. 34 * Thus objects must be removed or searched for using their transformed form. 35 * For example, if the transformation converts Strings to Integers, you must use 36 * the Integer form to remove objects. 37 * </p> 38 * <p> 39 * <strong>Note that TransformedMap is not synchronized and is not 40 * thread-safe.</strong> If you wish to use this map from multiple threads 41 * concurrently, you must use appropriate synchronization. The simplest approach 42 * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. 43 * This class may throw exceptions when accessed by concurrent threads without 44 * synchronization. 45 * </p> 46 * <p> 47 * The "put" and "get" type constraints of this class are mutually independent; 48 * contrast with {@link org.apache.commons.collections4.map.TransformedMap} which, 49 * by virtue of its implementing {@link Map}<K, V>, must be constructed in such 50 * a way that its read and write parameters are generalized to a common (super-)type. 51 * In practice this would often mean {@code >Object, Object>}, defeating 52 * much of the usefulness of having parameterized types. 53 * </p> 54 * <p> 55 * On the downside, this class is not drop-in compatible with {@link java.util.Map} 56 * but is intended to be worked with either directly or by {@link Put} and 57 * {@link org.apache.commons.collections4.Get Get} generalizations. 58 * </p> 59 * 60 * @param <J> the type of the keys to put in this map 61 * @param <K> the type of the keys to get in this map 62 * @param <U> the type of the values to put in this map 63 * @param <V> the type of the values to get in this map 64 * @since 4.0 65 * 66 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) 67 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 68 */ 69 public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 70 implements Put<J, U>, Serializable { 71 72 /** Serialization version */ 73 private static final long serialVersionUID = 5966875321133456994L; 74 75 /** 76 * Factory method to create a transforming map. 77 * <p> 78 * If there are any elements already in the map being decorated, they are 79 * NOT transformed. 80 * 81 * @param <J> the input key type 82 * @param <K> the output key type 83 * @param <U> the input value type 84 * @param <V> the output value type 85 * @param map the map to decorate, must not be null 86 * @param keyTransformer the transformer to use for key conversion, must not be null 87 * @param valueTransformer the transformer to use for value conversion, must not be null 88 * @return a new transformed map 89 * @throws NullPointerException if map or either of the transformers is null 90 */ 91 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 92 final Transformer<? super J, ? extends K> keyTransformer, 93 final Transformer<? super U, ? extends V> valueTransformer) { 94 return new TransformedSplitMap<>(map, keyTransformer, valueTransformer); 95 } 96 /** The transformer to use for the key */ 97 private final Transformer<? super J, ? extends K> keyTransformer; 98 99 /** The transformer to use for the value */ 100 private final Transformer<? super U, ? extends V> valueTransformer; 101 102 /** 103 * Constructor that wraps (not copies). 104 * <p> 105 * If there are any elements already in the collection being decorated, they 106 * are NOT transformed. 107 * 108 * @param map the map to decorate, must not be null 109 * @param keyTransformer the transformer to use for key conversion, must not be null 110 * @param valueTransformer the transformer to use for value conversion, must not be null 111 * @throws NullPointerException if map or either of the transformers is null 112 */ 113 protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer, 114 final Transformer<? super U, ? extends V> valueTransformer) { 115 super(map); 116 this.keyTransformer = Objects.requireNonNull(keyTransformer, "keyTransformer"); 117 this.valueTransformer = Objects.requireNonNull(valueTransformer, "valueTransformer"); 118 } 119 120 /** 121 * Override to transform the value when using {@code setValue}. 122 * 123 * @param value the value to transform 124 * @return the transformed value 125 */ 126 protected V checkSetValue(final U value) { 127 return valueTransformer.transform(value); 128 } 129 130 @Override 131 public void clear() { 132 decorated().clear(); 133 } 134 135 @Override 136 public V put(final J key, final U value) { 137 return decorated().put(transformKey(key), transformValue(value)); 138 } 139 140 @Override 141 public void putAll(final Map<? extends J, ? extends U> mapToCopy) { 142 decorated().putAll(transformMap(mapToCopy)); 143 } 144 145 /** 146 * Read the map in using a custom routine. 147 * 148 * @param in the input stream 149 * @throws IOException if an error occurs while reading from the stream 150 * @throws ClassNotFoundException if an object read from the stream can not be loaded 151 * @since 3.1 152 */ 153 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 154 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 155 in.defaultReadObject(); 156 map = (Map<K, V>) in.readObject(); // (1) 157 } 158 159 /** 160 * Transforms a key. 161 * <p> 162 * The transformer itself may throw an exception if necessary. 163 * 164 * @param object the object to transform 165 * @return the transformed object 166 */ 167 protected K transformKey(final J object) { 168 return keyTransformer.transform(object); 169 } 170 171 /** 172 * Transforms a map. 173 * <p> 174 * The transformer itself may throw an exception if necessary. 175 * 176 * @param map the map to transform 177 * @return the transformed object 178 */ 179 @SuppressWarnings("unchecked") 180 protected Map<K, V> transformMap(final Map<? extends J, ? extends U> map) { 181 if (map.isEmpty()) { 182 return (Map<K, V>) map; 183 } 184 final Map<K, V> result = new LinkedMap<>(map.size()); 185 186 for (final Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { 187 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 188 } 189 return result; 190 } 191 192 /** 193 * Transforms a value. 194 * <p> 195 * The transformer itself may throw an exception if necessary. 196 * 197 * @param object the object to transform 198 * @return the transformed object 199 */ 200 protected V transformValue(final U object) { 201 return valueTransformer.transform(object); 202 } 203 204 /** 205 * Write the map out using a custom routine. 206 * 207 * @param out the output stream 208 * @throws IOException if an error occurs while writing to the stream 209 */ 210 private void writeObject(final ObjectOutputStream out) throws IOException { 211 out.defaultWriteObject(); 212 out.writeObject(decorated()); 213 } 214 }