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.map; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.Map; 024 025import org.apache.commons.collections4.Transformer; 026 027/** 028 * Decorates another <code>Map</code> to transform objects that are added. 029 * <p> 030 * The Map put methods and Map.Entry setValue method are affected by this class. 031 * Thus objects must be removed or searched for using their transformed form. 032 * For example, if the transformation converts Strings to Integers, you must 033 * use the Integer form to remove objects. 034 * <p> 035 * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong> 036 * If you wish to use this map from multiple threads concurrently, you must use 037 * appropriate synchronization. The simplest approach is to wrap this map 038 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 039 * exceptions when accessed by concurrent threads without synchronization. 040 * <p> 041 * This class is Serializable from Commons Collections 3.1. 042 * <p> 043 * 044 * @param <K> the type of the keys in this map 045 * @param <V> the type of the values in this map 046 * 047 * @see org.apache.commons.collections4.splitmap.TransformedSplitMap 048 * @since 3.0 049 */ 050public class TransformedMap<K, V> 051 extends AbstractInputCheckedMapDecorator<K, V> 052 implements Serializable { 053 054 /** Serialization version */ 055 private static final long serialVersionUID = 7023152376788900464L; 056 057 /** The transformer to use for the key */ 058 protected final Transformer<? super K, ? extends K> keyTransformer; 059 /** The transformer to use for the value */ 060 protected final Transformer<? super V, ? extends V> valueTransformer; 061 062 /** 063 * Factory method to create a transforming map. 064 * <p> 065 * If there are any elements already in the map being decorated, they 066 * are NOT transformed. 067 * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}. 068 * 069 * @param <K> the key type 070 * @param <V> the value type 071 * @param map the map to decorate, must not be null 072 * @param keyTransformer the transformer to use for key conversion, null means no transformation 073 * @param valueTransformer the transformer to use for value conversion, null means no transformation 074 * @return a new transformed map 075 * @throws NullPointerException if map is null 076 * @since 4.0 077 */ 078 public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map, 079 final Transformer<? super K, ? extends K> keyTransformer, 080 final Transformer<? super V, ? extends V> valueTransformer) { 081 return new TransformedMap<>(map, keyTransformer, valueTransformer); 082 } 083 084 /** 085 * Factory method to create a transforming map that will transform 086 * existing contents of the specified map. 087 * <p> 088 * If there are any elements already in the map being decorated, they 089 * will be transformed by this method. 090 * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}. 091 * 092 * @param <K> the key type 093 * @param <V> the value type 094 * @param map the map to decorate, must not be null 095 * @param keyTransformer the transformer to use for key conversion, null means no transformation 096 * @param valueTransformer the transformer to use for value conversion, null means no transformation 097 * @return a new transformed map 098 * @throws NullPointerException if map is null 099 * @since 4.0 100 */ 101 public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map, 102 final Transformer<? super K, ? extends K> keyTransformer, 103 final Transformer<? super V, ? extends V> valueTransformer) { 104 final TransformedMap<K, V> decorated = new TransformedMap<>(map, keyTransformer, valueTransformer); 105 if (map.size() > 0) { 106 final Map<K, V> transformed = decorated.transformMap(map); 107 decorated.clear(); 108 decorated.decorated().putAll(transformed); // avoids double transformation 109 } 110 return decorated; 111 } 112 113 //----------------------------------------------------------------------- 114 /** 115 * Constructor that wraps (not copies). 116 * <p> 117 * If there are any elements already in the collection being decorated, they 118 * are NOT transformed. 119 * 120 * @param map the map to decorate, must not be null 121 * @param keyTransformer the transformer to use for key conversion, null means no conversion 122 * @param valueTransformer the transformer to use for value conversion, null means no conversion 123 * @throws NullPointerException if map is null 124 */ 125 protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer, 126 final Transformer<? super V, ? extends V> valueTransformer) { 127 super(map); 128 this.keyTransformer = keyTransformer; 129 this.valueTransformer = valueTransformer; 130 } 131 132 //----------------------------------------------------------------------- 133 /** 134 * Write the map out using a custom routine. 135 * 136 * @param out the output stream 137 * @throws IOException if an error occurs while writing to the stream 138 * @since 3.1 139 */ 140 private void writeObject(final ObjectOutputStream out) throws IOException { 141 out.defaultWriteObject(); 142 out.writeObject(map); 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 /** 161 * Transforms a key. 162 * <p> 163 * The transformer itself may throw an exception if necessary. 164 * 165 * @param object the object to transform 166 * @return the transformed object 167 */ 168 protected K transformKey(final K object) { 169 if (keyTransformer == null) { 170 return object; 171 } 172 return keyTransformer.transform(object); 173 } 174 175 /** 176 * Transforms a value. 177 * <p> 178 * The transformer itself may throw an exception if necessary. 179 * 180 * @param object the object to transform 181 * @return the transformed object 182 */ 183 protected V transformValue(final V object) { 184 if (valueTransformer == null) { 185 return object; 186 } 187 return valueTransformer.transform(object); 188 } 189 190 /** 191 * Transforms a map. 192 * <p> 193 * The transformer itself may throw an exception if necessary. 194 * 195 * @param map the map to transform 196 * @return the transformed object 197 */ 198 @SuppressWarnings("unchecked") 199 protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) { 200 if (map.isEmpty()) { 201 return (Map<K, V>) map; 202 } 203 final Map<K, V> result = new LinkedMap<>(map.size()); 204 205 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 206 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 207 } 208 return result; 209 } 210 211 /** 212 * Override to transform the value when using <code>setValue</code>. 213 * 214 * @param value the value to transform 215 * @return the transformed value 216 * @since 3.1 217 */ 218 @Override 219 protected V checkSetValue(final V value) { 220 return valueTransformer.transform(value); 221 } 222 223 /** 224 * Override to only return true when there is a value transformer. 225 * 226 * @return true if a value transformer is in use 227 * @since 3.1 228 */ 229 @Override 230 protected boolean isSetValueChecking() { 231 return valueTransformer != null; 232 } 233 234 //----------------------------------------------------------------------- 235 @Override 236 public V put(K key, V value) { 237 key = transformKey(key); 238 value = transformValue(value); 239 return decorated().put(key, value); 240 } 241 242 @Override 243 public void putAll(Map<? extends K, ? extends V> mapToCopy) { 244 mapToCopy = transformMap(mapToCopy); 245 decorated().putAll(mapToCopy); 246 } 247 248}