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; 024 025import org.apache.commons.collections4.Put; 026import org.apache.commons.collections4.Transformer; 027import org.apache.commons.collections4.map.LinkedMap; 028 029/** 030 * Decorates another {@link Map} to transform objects that are added. 031 * <p> 032 * The Map put methods and Map.Entry setValue method are affected by this class. 033 * Thus objects must be removed or searched for using their transformed form. 034 * For example, if the transformation converts Strings to Integers, you must use 035 * the Integer form to remove objects. 036 * <p> 037 * <strong>Note that TransformedMap is not synchronized and is not 038 * thread-safe.</strong> If you wish to use this map from multiple threads 039 * concurrently, you must use appropriate synchronization. The simplest approach 040 * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. 041 * This class may throw exceptions when accessed by concurrent threads without 042 * synchronization. 043 * <p> 044 * The "put" and "get" type constraints of this class are mutually independent; 045 * contrast with {@link org.apache.commons.collections4.map.TransformedMap} which, 046 * by virtue of its implementing {@link Map}<K, V>, must be constructed in such 047 * a way that its read and write parameters are generalized to a common (super-)type. 048 * In practice this would often mean <code>>Object, Object></code>, defeating 049 * much of the usefulness of having parameterized types. 050 * <p> 051 * On the downside, this class is not drop-in compatible with {@link java.util.Map} 052 * but is intended to be worked with either directly or by {@link Put} and 053 * {@link org.apache.commons.collections4.Get Get} generalizations. 054 * 055 * @since 4.0 056 * @version $Id: TransformedSplitMap.html 972421 2015-11-14 20:00:04Z tn $ 057 * 058 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(Get) 059 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 060 */ 061public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 062 implements Put<J, U>, Serializable { 063 064 /** Serialization version */ 065 private static final long serialVersionUID = 5966875321133456994L; 066 067 /** The transformer to use for the key */ 068 private final Transformer<? super J, ? extends K> keyTransformer; 069 /** The transformer to use for the value */ 070 private final Transformer<? super U, ? extends V> valueTransformer; 071 072 /** 073 * Factory method to create a transforming map. 074 * <p> 075 * If there are any elements already in the map being decorated, they are 076 * NOT transformed. 077 * 078 * @param <J> the input key type 079 * @param <K> the output key type 080 * @param <U> the input value type 081 * @param <V> the output value type 082 * @param map the map to decorate, must not be null 083 * @param keyTransformer the transformer to use for key conversion, null 084 * means no transformation 085 * @param valueTransformer the transformer to use for value conversion, null 086 * means no transformation 087 * @return a new transformed map 088 * @throws IllegalArgumentException if map is null 089 */ 090 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 091 final Transformer<? super J, ? extends K> keyTransformer, 092 final Transformer<? super U, ? extends V> valueTransformer) { 093 return new TransformedSplitMap<J, K, U, V>(map, keyTransformer, valueTransformer); 094 } 095 096 //----------------------------------------------------------------------- 097 /** 098 * Constructor that wraps (not copies). 099 * <p> 100 * If there are any elements already in the collection being decorated, they 101 * are NOT transformed. 102 * 103 * @param map the map to decorate, must not be null 104 * @param keyTransformer the transformer to use for key conversion, null 105 * means no conversion 106 * @param valueTransformer the transformer to use for value conversion, null 107 * means no conversion 108 * @throws IllegalArgumentException if map is null 109 */ 110 protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer, 111 final Transformer<? super U, ? extends V> valueTransformer) { 112 super(map); 113 if (keyTransformer == null) { 114 throw new IllegalArgumentException("keyTransformer cannot be null"); 115 } 116 this.keyTransformer = keyTransformer; 117 if (valueTransformer == null) { 118 throw new IllegalArgumentException("valueTransformer cannot be null"); 119 } 120 this.valueTransformer = valueTransformer; 121 } 122 123 //----------------------------------------------------------------------- 124 /** 125 * Write the map out using a custom routine. 126 * 127 * @param out the output stream 128 * @throws IOException 129 */ 130 private void writeObject(final ObjectOutputStream out) throws IOException { 131 out.defaultWriteObject(); 132 out.writeObject(decorated()); 133 } 134 135 /** 136 * Read the map in using a custom routine. 137 * 138 * @param in the input stream 139 * @throws IOException 140 * @throws ClassNotFoundException 141 * @since 3.1 142 */ 143 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 144 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 145 in.defaultReadObject(); 146 map = (Map<K, V>) in.readObject(); // (1) 147 } 148 149 //----------------------------------------------------------------------- 150 /** 151 * Transforms a key. 152 * <p> 153 * The transformer itself may throw an exception if necessary. 154 * 155 * @param object the object to transform 156 * @return the transformed object 157 */ 158 protected K transformKey(final J object) { 159 return keyTransformer.transform(object); 160 } 161 162 /** 163 * Transforms a value. 164 * <p> 165 * The transformer itself may throw an exception if necessary. 166 * 167 * @param object the object to transform 168 * @return the transformed object 169 */ 170 protected V transformValue(final U object) { 171 return valueTransformer.transform(object); 172 } 173 174 /** 175 * Transforms a map. 176 * <p> 177 * The transformer itself may throw an exception if necessary. 178 * 179 * @param map the map to transform 180 * @return the transformed object 181 */ 182 @SuppressWarnings("unchecked") 183 protected Map<K, V> transformMap(final Map<? extends J, ? extends U> map) { 184 if (map.isEmpty()) { 185 return (Map<K, V>) map; 186 } 187 final Map<K, V> result = new LinkedMap<K, V>(map.size()); 188 189 for (final Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { 190 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 191 } 192 return result; 193 } 194 195 /** 196 * Override to transform the value when using <code>setValue</code>. 197 * 198 * @param value the value to transform 199 * @return the transformed value 200 */ 201 protected V checkSetValue(final U value) { 202 return valueTransformer.transform(value); 203 } 204 205 //----------------------------------------------------------------------- 206 public V put(final J key, final U value) { 207 return decorated().put(transformKey(key), transformValue(value)); 208 } 209 210 public void putAll(final Map<? extends J, ? extends U> mapToCopy) { 211 decorated().putAll(transformMap(mapToCopy)); 212 } 213 214 public void clear() { 215 decorated().clear(); 216 } 217}