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 }