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 * @see org.apache.commons.collections4.splitmap.TransformedSplitMap 044 * 045 * @since 3.0 046 * @version $Id: TransformedMap.html 972421 2015-11-14 20:00:04Z tn $ 047 */ 048public class TransformedMap<K, V> 049 extends AbstractInputCheckedMapDecorator<K, V> 050 implements Serializable { 051 052 /** Serialization version */ 053 private static final long serialVersionUID = 7023152376788900464L; 054 055 /** The transformer to use for the key */ 056 protected final Transformer<? super K, ? extends K> keyTransformer; 057 /** The transformer to use for the value */ 058 protected final Transformer<? super V, ? extends V> valueTransformer; 059 060 /** 061 * Factory method to create a transforming map. 062 * <p> 063 * If there are any elements already in the map being decorated, they 064 * are NOT transformed. 065 * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}. 066 * 067 * @param <K> the key type 068 * @param <V> the value type 069 * @param map the map to decorate, must not be null 070 * @param keyTransformer the transformer to use for key conversion, null means no transformation 071 * @param valueTransformer the transformer to use for value conversion, null means no transformation 072 * @return a new transformed map 073 * @throws IllegalArgumentException if map is null 074 * @since 4.0 075 */ 076 public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map, 077 final Transformer<? super K, ? extends K> keyTransformer, 078 final Transformer<? super V, ? extends V> valueTransformer) { 079 return new TransformedMap<K, V>(map, keyTransformer, valueTransformer); 080 } 081 082 /** 083 * Factory method to create a transforming map that will transform 084 * existing contents of the specified map. 085 * <p> 086 * If there are any elements already in the map being decorated, they 087 * will be transformed by this method. 088 * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}. 089 * 090 * @param <K> the key type 091 * @param <V> the value type 092 * @param map the map to decorate, must not be null 093 * @param keyTransformer the transformer to use for key conversion, null means no transformation 094 * @param valueTransformer the transformer to use for value conversion, null means no transformation 095 * @return a new transformed map 096 * @throws IllegalArgumentException if map is null 097 * @since 4.0 098 */ 099 public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map, 100 final Transformer<? super K, ? extends K> keyTransformer, 101 final Transformer<? super V, ? extends V> valueTransformer) { 102 final TransformedMap<K, V> decorated = new TransformedMap<K, V>(map, keyTransformer, valueTransformer); 103 if (map.size() > 0) { 104 final Map<K, V> transformed = decorated.transformMap(map); 105 decorated.clear(); 106 decorated.decorated().putAll(transformed); // avoids double transformation 107 } 108 return decorated; 109 } 110 111 //----------------------------------------------------------------------- 112 /** 113 * Constructor that wraps (not copies). 114 * <p> 115 * If there are any elements already in the collection being decorated, they 116 * are NOT transformed. 117 * 118 * @param map the map to decorate, must not be null 119 * @param keyTransformer the transformer to use for key conversion, null means no conversion 120 * @param valueTransformer the transformer to use for value conversion, null means no conversion 121 * @throws IllegalArgumentException if map is null 122 */ 123 protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer, 124 final Transformer<? super V, ? extends V> valueTransformer) { 125 super(map); 126 this.keyTransformer = keyTransformer; 127 this.valueTransformer = valueTransformer; 128 } 129 130 //----------------------------------------------------------------------- 131 /** 132 * Write the map out using a custom routine. 133 * 134 * @param out the output stream 135 * @throws IOException 136 * @since 3.1 137 */ 138 private void writeObject(final ObjectOutputStream out) throws IOException { 139 out.defaultWriteObject(); 140 out.writeObject(map); 141 } 142 143 /** 144 * Read the map in using a custom routine. 145 * 146 * @param in the input stream 147 * @throws IOException 148 * @throws ClassNotFoundException 149 * @since 3.1 150 */ 151 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 152 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 153 in.defaultReadObject(); 154 map = (Map<K, V>) in.readObject(); // (1) 155 } 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 K object) { 167 if (keyTransformer == null) { 168 return object; 169 } 170 return keyTransformer.transform(object); 171 } 172 173 /** 174 * Transforms a value. 175 * <p> 176 * The transformer itself may throw an exception if necessary. 177 * 178 * @param object the object to transform 179 * @return the transformed object 180 */ 181 protected V transformValue(final V object) { 182 if (valueTransformer == null) { 183 return object; 184 } 185 return valueTransformer.transform(object); 186 } 187 188 /** 189 * Transforms a map. 190 * <p> 191 * The transformer itself may throw an exception if necessary. 192 * 193 * @param map the map to transform 194 * @return the transformed object 195 */ 196 @SuppressWarnings("unchecked") 197 protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) { 198 if (map.isEmpty()) { 199 return (Map<K, V>) map; 200 } 201 final Map<K, V> result = new LinkedMap<K, V>(map.size()); 202 203 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 204 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 205 } 206 return result; 207 } 208 209 /** 210 * Override to transform the value when using <code>setValue</code>. 211 * 212 * @param value the value to transform 213 * @return the transformed value 214 * @since 3.1 215 */ 216 @Override 217 protected V checkSetValue(final V value) { 218 return valueTransformer.transform(value); 219 } 220 221 /** 222 * Override to only return true when there is a value transformer. 223 * 224 * @return true if a value transformer is in use 225 * @since 3.1 226 */ 227 @Override 228 protected boolean isSetValueChecking() { 229 return valueTransformer != null; 230 } 231 232 //----------------------------------------------------------------------- 233 @Override 234 public V put(K key, V value) { 235 key = transformKey(key); 236 value = transformValue(value); 237 return decorated().put(key, value); 238 } 239 240 @Override 241 public void putAll(Map<? extends K, ? extends V> mapToCopy) { 242 mapToCopy = transformMap(mapToCopy); 243 decorated().putAll(mapToCopy); 244 } 245 246}