001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.splitmap; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.Map; 024import java.util.Objects; 025 026import org.apache.commons.collections4.Get; 027import org.apache.commons.collections4.Put; 028import org.apache.commons.collections4.Transformer; 029import org.apache.commons.collections4.map.LinkedMap; 030import org.apache.commons.collections4.map.TransformedMap; 031 032/** 033 * Decorates another {@link Map} to transform objects that are added. 034 * <p> 035 * The Map put methods and Map.Entry setValue method are affected by this class. 036 * Thus objects must be removed or searched for using their transformed form. 037 * For example, if the transformation converts Strings to Integers, you must use 038 * the Integer form to remove objects. 039 * </p> 040 * <p> 041 * <strong>Note that TransformedMap is not synchronized and is not 042 * thread-safe.</strong> If you wish to use this map from multiple threads 043 * concurrently, you must use appropriate synchronization. The simplest approach 044 * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. 045 * This class may throw exceptions when accessed by concurrent threads without 046 * synchronization. 047 * </p> 048 * <p> 049 * The "put" and "get" type constraints of this class are mutually independent; 050 * contrast with {@link TransformedMap} which, 051 * by virtue of its implementing {@link Map}<K, V>, must be constructed in such 052 * a way that its read and write parameters are generalized to a common (super-)type. 053 * In practice this would often mean {@code >Object, Object>}, defeating 054 * much of the usefulness of having parameterized types. 055 * </p> 056 * <p> 057 * On the downside, this class is not drop-in compatible with {@link Map} 058 * but is intended to be worked with either directly or by {@link Put} and 059 * {@link Get Get} generalizations. 060 * </p> 061 * 062 * @param <J> The type of the keys to put in this map 063 * @param <K> The type of the keys to get in this map 064 * @param <U> The type of the values to put in this map 065 * @param <V> The type of the values to get in this map 066 * @since 4.0 067 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) 068 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 069 */ 070public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 071 implements Put<J, U>, Serializable { 072 073 /** Serialization version */ 074 private static final long serialVersionUID = 5966875321133456994L; 075 076 /** 077 * Factory method to create a transforming map. 078 * <p> 079 * If there are any elements already in the map being decorated, they are 080 * NOT transformed. 081 * 082 * @param <J> the input key type 083 * @param <K> the output key type 084 * @param <U> the input value type 085 * @param <V> the output value type 086 * @param map The map to decorate, must not be null 087 * @param keyTransformer The transformer to use for key conversion, must not be null 088 * @param valueTransformer The transformer to use for value conversion, must not be null 089 * @return A new transformed map 090 * @throws NullPointerException if map or either of the transformers is null 091 */ 092 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 093 final Transformer<? super J, ? extends K> keyTransformer, 094 final Transformer<? super U, ? extends V> valueTransformer) { 095 return new TransformedSplitMap<>(map, keyTransformer, valueTransformer); 096 } 097 098 /** The transformer to use for the key */ 099 private final Transformer<? super J, ? extends K> keyTransformer; 100 101 /** The transformer to use for the value */ 102 private final Transformer<? super U, ? extends V> valueTransformer; 103 104 /** 105 * Constructor that wraps (not copies). 106 * <p> 107 * If there are any elements already in the collection being decorated, they 108 * are NOT transformed. 109 * 110 * @param map The map to decorate, must not be null 111 * @param keyTransformer The transformer to use for key conversion, must not be null 112 * @param valueTransformer The transformer to use for value conversion, must not be null 113 * @throws NullPointerException if map or either of the transformers is null 114 */ 115 protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer, 116 final Transformer<? super U, ? extends V> valueTransformer) { 117 super(map); 118 this.keyTransformer = Objects.requireNonNull(keyTransformer, "keyTransformer"); 119 this.valueTransformer = Objects.requireNonNull(valueTransformer, "valueTransformer"); 120 } 121 122 /** 123 * Override to transform the value when using {@code setValue}. 124 * 125 * @param value The value to transform 126 * @return The transformed value 127 */ 128 protected V checkSetValue(final U value) { 129 return valueTransformer.apply(value); 130 } 131 132 @Override 133 public void clear() { 134 decorated().clear(); 135 } 136 137 @Override 138 public V put(final J key, final U value) { 139 return decorated().put(transformKey(key), transformValue(value)); 140 } 141 142 @Override 143 public void putAll(final Map<? extends J, ? extends U> mapToCopy) { 144 decorated().putAll(transformMap(mapToCopy)); 145 } 146 147 /** 148 * Deserializes the map in using a custom routine. 149 * 150 * @param in The input stream 151 * @throws IOException Thrown if an error occurs while reading from the stream 152 * @throws ClassNotFoundException if an object read from the stream cannot be loaded 153 * @since 3.1 154 */ 155 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 156 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 157 in.defaultReadObject(); 158 map = (Map<K, V>) in.readObject(); // (1) 159 } 160 161 /** 162 * Transforms a key. 163 * <p> 164 * The transformer itself may throw an exception if necessary. 165 * 166 * @param object The object to transform 167 * @return The transformed object 168 */ 169 protected K transformKey(final J object) { 170 return keyTransformer.apply(object); 171 } 172 173 /** 174 * Transforms a map. 175 * <p> 176 * The transformer itself may throw an exception if necessary. 177 * 178 * @param map The map to transform 179 * @return The transformed object 180 */ 181 @SuppressWarnings("unchecked") 182 protected Map<K, V> transformMap(final Map<? extends J, ? extends U> map) { 183 if (map.isEmpty()) { 184 return (Map<K, V>) map; 185 } 186 final Map<K, V> result = new LinkedMap<>(map.size()); 187 188 for (final Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { 189 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 190 } 191 return result; 192 } 193 194 /** 195 * Transforms a value. 196 * <p> 197 * The transformer itself may throw an exception if necessary. 198 * 199 * @param object The object to transform 200 * @return The transformed object 201 */ 202 protected V transformValue(final U object) { 203 return valueTransformer.apply(object); 204 } 205 206 /** 207 * Serializes this object to an ObjectOutputStream. 208 * 209 * @param out The target ObjectOutputStream. 210 * @throws IOException thrown when an I/O errors occur writing to the target stream. 211 */ 212 private void writeObject(final ObjectOutputStream out) throws IOException { 213 out.defaultWriteObject(); 214 out.writeObject(decorated()); 215 } 216}