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 * http://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.Put; 027import org.apache.commons.collections4.Transformer; 028import org.apache.commons.collections4.map.LinkedMap; 029 030/** 031 * Decorates another {@link Map} to transform objects that are added. 032 * <p> 033 * The Map put methods and Map.Entry setValue method are affected by this class. 034 * Thus objects must be removed or searched for using their transformed form. 035 * For example, if the transformation converts Strings to Integers, you must use 036 * the Integer form to remove objects. 037 * </p> 038 * <p> 039 * <strong>Note that TransformedMap is not synchronized and is not 040 * thread-safe.</strong> If you wish to use this map from multiple threads 041 * concurrently, you must use appropriate synchronization. The simplest approach 042 * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. 043 * This class may throw exceptions when accessed by concurrent threads without 044 * synchronization. 045 * </p> 046 * <p> 047 * The "put" and "get" type constraints of this class are mutually independent; 048 * contrast with {@link org.apache.commons.collections4.map.TransformedMap} which, 049 * by virtue of its implementing {@link Map}<K, V>, must be constructed in such 050 * a way that its read and write parameters are generalized to a common (super-)type. 051 * In practice this would often mean {@code >Object, Object>}, defeating 052 * much of the usefulness of having parameterized types. 053 * </p> 054 * <p> 055 * On the downside, this class is not drop-in compatible with {@link java.util.Map} 056 * but is intended to be worked with either directly or by {@link Put} and 057 * {@link org.apache.commons.collections4.Get Get} generalizations. 058 * </p> 059 * 060 * @param <J> the type of the keys to put in this map 061 * @param <K> the type of the keys to get in this map 062 * @param <U> the type of the values to put in this map 063 * @param <V> the type of the values to get in this map 064 * @since 4.0 065 * 066 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) 067 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 068 */ 069public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 070 implements Put<J, U>, Serializable { 071 072 /** Serialization version */ 073 private static final long serialVersionUID = 5966875321133456994L; 074 075 /** 076 * Factory method to create a transforming map. 077 * <p> 078 * If there are any elements already in the map being decorated, they are 079 * NOT transformed. 080 * 081 * @param <J> the input key type 082 * @param <K> the output key type 083 * @param <U> the input value type 084 * @param <V> the output value type 085 * @param map the map to decorate, must not be null 086 * @param keyTransformer the transformer to use for key conversion, must not be null 087 * @param valueTransformer the transformer to use for value conversion, must not be null 088 * @return a new transformed map 089 * @throws NullPointerException if map or either of the transformers is null 090 */ 091 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 092 final Transformer<? super J, ? extends K> keyTransformer, 093 final Transformer<? super U, ? extends V> valueTransformer) { 094 return new TransformedSplitMap<>(map, keyTransformer, valueTransformer); 095 } 096 /** The transformer to use for the key */ 097 private final Transformer<? super J, ? extends K> keyTransformer; 098 099 /** 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}