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 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) 66 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 67 */ 68 public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 69 implements Put<J, U>, Serializable { 70 71 /** Serialization version */ 72 private static final long serialVersionUID = 5966875321133456994L; 73 74 /** 75 * Factory method to create a transforming map. 76 * <p> 77 * If there are any elements already in the map being decorated, they are 78 * NOT transformed. 79 * 80 * @param <J> the input key type 81 * @param <K> the output key type 82 * @param <U> the input value type 83 * @param <V> the output value type 84 * @param map the map to decorate, must not be null 85 * @param keyTransformer the transformer to use for key conversion, must not be null 86 * @param valueTransformer the transformer to use for value conversion, must not be null 87 * @return a new transformed map 88 * @throws NullPointerException if map or either of the transformers is null 89 */ 90 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 91 final Transformer<? super J, ? extends K> keyTransformer, 92 final Transformer<? super U, ? extends V> valueTransformer) { 93 return new TransformedSplitMap<>(map, keyTransformer, valueTransformer); 94 } 95 /** The transformer to use for the key */ 96 private final Transformer<? super J, ? extends K> keyTransformer; 97 98 /** The transformer to use for the value */ 99 private final Transformer<? super U, ? extends V> valueTransformer; 100 101 /** 102 * Constructor that wraps (not copies). 103 * <p> 104 * If there are any elements already in the collection being decorated, they 105 * are NOT transformed. 106 * 107 * @param map the map to decorate, must not be null 108 * @param keyTransformer the transformer to use for key conversion, must not be null 109 * @param valueTransformer the transformer to use for value conversion, must not be null 110 * @throws NullPointerException if map or either of the transformers is null 111 */ 112 protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer, 113 final Transformer<? super U, ? extends V> valueTransformer) { 114 super(map); 115 this.keyTransformer = Objects.requireNonNull(keyTransformer, "keyTransformer"); 116 this.valueTransformer = Objects.requireNonNull(valueTransformer, "valueTransformer"); 117 } 118 119 /** 120 * Override to transform the value when using {@code setValue}. 121 * 122 * @param value the value to transform 123 * @return the transformed value 124 */ 125 protected V checkSetValue(final U value) { 126 return valueTransformer.apply(value); 127 } 128 129 @Override 130 public void clear() { 131 decorated().clear(); 132 } 133 134 @Override 135 public V put(final J key, final U value) { 136 return decorated().put(transformKey(key), transformValue(value)); 137 } 138 139 @Override 140 public void putAll(final Map<? extends J, ? extends U> mapToCopy) { 141 decorated().putAll(transformMap(mapToCopy)); 142 } 143 144 /** 145 * Deserializes the map in using a custom routine. 146 * 147 * @param in the input stream 148 * @throws IOException if an error occurs while reading from the stream 149 * @throws ClassNotFoundException if an object read from the stream cannot be loaded 150 * @since 3.1 151 */ 152 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 153 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 154 in.defaultReadObject(); 155 map = (Map<K, V>) in.readObject(); // (1) 156 } 157 158 /** 159 * Transforms a key. 160 * <p> 161 * The transformer itself may throw an exception if necessary. 162 * 163 * @param object the object to transform 164 * @return the transformed object 165 */ 166 protected K transformKey(final J object) { 167 return keyTransformer.apply(object); 168 } 169 170 /** 171 * Transforms a map. 172 * <p> 173 * The transformer itself may throw an exception if necessary. 174 * 175 * @param map the map to transform 176 * @return the transformed object 177 */ 178 @SuppressWarnings("unchecked") 179 protected Map<K, V> transformMap(final Map<? extends J, ? extends U> map) { 180 if (map.isEmpty()) { 181 return (Map<K, V>) map; 182 } 183 final Map<K, V> result = new LinkedMap<>(map.size()); 184 185 for (final Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { 186 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 187 } 188 return result; 189 } 190 191 /** 192 * Transforms a value. 193 * <p> 194 * The transformer itself may throw an exception if necessary. 195 * 196 * @param object the object to transform 197 * @return the transformed object 198 */ 199 protected V transformValue(final U object) { 200 return valueTransformer.apply(object); 201 } 202 203 /** 204 * Serializes this object to an ObjectOutputStream. 205 * 206 * @param out the target ObjectOutputStream. 207 * @throws IOException thrown when an I/O errors occur writing to the target stream. 208 */ 209 private void writeObject(final ObjectOutputStream out) throws IOException { 210 out.defaultWriteObject(); 211 out.writeObject(decorated()); 212 } 213 }