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 */ 017 package org.apache.commons.collections.map; 018 019 import java.io.IOException; 020 import java.io.ObjectInputStream; 021 import java.io.ObjectOutputStream; 022 import java.io.Serializable; 023 import java.util.Iterator; 024 import java.util.Map; 025 026 import org.apache.commons.collections.Transformer; 027 028 /** 029 * Decorates another <code>Map</code> to transform objects that are added. 030 * <p> 031 * The Map put methods and Map.Entry setValue method are affected by this class. 032 * Thus objects must be removed or searched for using their transformed form. 033 * For example, if the transformation converts Strings to Integers, you must 034 * use the Integer form to remove objects. 035 * <p> 036 * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong> 037 * If you wish to use this map from multiple threads concurrently, you must use 038 * appropriate synchronization. The simplest approach is to wrap this map 039 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 040 * exceptions when accessed by concurrent threads without synchronization. 041 * <p> 042 * This class is Serializable from Commons Collections 3.1. 043 * 044 * @since Commons Collections 3.0 045 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ 046 * 047 * @author Stephen Colebourne 048 */ 049 public class TransformedMap 050 extends AbstractInputCheckedMapDecorator 051 implements Serializable { 052 053 /** Serialization version */ 054 private static final long serialVersionUID = 7023152376788900464L; 055 056 /** The transformer to use for the key */ 057 protected final Transformer keyTransformer; 058 /** The transformer to use for the value */ 059 protected final Transformer valueTransformer; 060 061 /** 062 * Factory method to create a transforming map. 063 * <p> 064 * If there are any elements already in the map being decorated, they 065 * are NOT transformed. 066 * Constrast this with {@link #decorateTransform}. 067 * 068 * @param map the map to decorate, must not be null 069 * @param keyTransformer the transformer to use for key conversion, null means no transformation 070 * @param valueTransformer the transformer to use for value conversion, null means no transformation 071 * @throws IllegalArgumentException if map is null 072 */ 073 public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { 074 return new TransformedMap(map, keyTransformer, valueTransformer); 075 } 076 077 /** 078 * Factory method to create a transforming map that will transform 079 * existing contents of the specified map. 080 * <p> 081 * If there are any elements already in the map being decorated, they 082 * will be transformed by this method. 083 * Constrast this with {@link #decorate}. 084 * 085 * @param map the map to decorate, must not be null 086 * @param keyTransformer the transformer to use for key conversion, null means no transformation 087 * @param valueTransformer the transformer to use for value conversion, null means no transformation 088 * @throws IllegalArgumentException if map is null 089 * @since Commons Collections 3.2 090 */ 091 public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) { 092 TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer); 093 if (map.size() > 0) { 094 Map transformed = decorated.transformMap(map); 095 decorated.clear(); 096 decorated.getMap().putAll(transformed); // avoids double transformation 097 } 098 return decorated; 099 } 100 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, null means no conversion 110 * @param valueTransformer the transformer to use for value conversion, null means no conversion 111 * @throws IllegalArgumentException if map is null 112 */ 113 protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { 114 super(map); 115 this.keyTransformer = keyTransformer; 116 this.valueTransformer = valueTransformer; 117 } 118 119 //----------------------------------------------------------------------- 120 /** 121 * Write the map out using a custom routine. 122 * 123 * @param out the output stream 124 * @throws IOException 125 * @since Commons Collections 3.1 126 */ 127 private void writeObject(ObjectOutputStream out) throws IOException { 128 out.defaultWriteObject(); 129 out.writeObject(map); 130 } 131 132 /** 133 * Read the map in using a custom routine. 134 * 135 * @param in the input stream 136 * @throws IOException 137 * @throws ClassNotFoundException 138 * @since Commons Collections 3.1 139 */ 140 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 141 in.defaultReadObject(); 142 map = (Map) in.readObject(); 143 } 144 145 //----------------------------------------------------------------------- 146 /** 147 * Transforms a key. 148 * <p> 149 * The transformer itself may throw an exception if necessary. 150 * 151 * @param object the object to transform 152 * @throws the transformed object 153 */ 154 protected Object transformKey(Object object) { 155 if (keyTransformer == null) { 156 return object; 157 } 158 return keyTransformer.transform(object); 159 } 160 161 /** 162 * Transforms a value. 163 * <p> 164 * The transformer itself may throw an exception if necessary. 165 * 166 * @param object the object to transform 167 * @throws the transformed object 168 */ 169 protected Object transformValue(Object object) { 170 if (valueTransformer == null) { 171 return object; 172 } 173 return valueTransformer.transform(object); 174 } 175 176 /** 177 * Transforms a map. 178 * <p> 179 * The transformer itself may throw an exception if necessary. 180 * 181 * @param map the map to transform 182 * @throws the transformed object 183 */ 184 protected Map transformMap(Map map) { 185 if (map.isEmpty()) { 186 return map; 187 } 188 Map result = new LinkedMap(map.size()); 189 for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) { 190 Map.Entry entry = (Map.Entry) it.next(); 191 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 192 } 193 return result; 194 } 195 196 /** 197 * Override to transform the value when using <code>setValue</code>. 198 * 199 * @param value the value to transform 200 * @return the transformed value 201 * @since Commons Collections 3.1 202 */ 203 protected Object checkSetValue(Object value) { 204 return valueTransformer.transform(value); 205 } 206 207 /** 208 * Override to only return true when there is a value transformer. 209 * 210 * @return true if a value transformer is in use 211 * @since Commons Collections 3.1 212 */ 213 protected boolean isSetValueChecking() { 214 return (valueTransformer != null); 215 } 216 217 //----------------------------------------------------------------------- 218 public Object put(Object key, Object value) { 219 key = transformKey(key); 220 value = transformValue(value); 221 return getMap().put(key, value); 222 } 223 224 public void putAll(Map mapToCopy) { 225 mapToCopy = transformMap(mapToCopy); 226 getMap().putAll(mapToCopy); 227 } 228 229 }