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 * @param <J> the type of the keys to put in this map 056 * @param <K> the type of the keys to get in this map 057 * @param <U> the type of the values to put in this map 058 * @param <V> the type of the values to get in this map 059 * @since 4.0 060 * 061 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.Get) 062 * @see org.apache.commons.collections4.SplitMapUtils#writableMap(Put) 063 */ 064public class TransformedSplitMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> 065 implements Put<J, U>, Serializable { 066 067 /** Serialization version */ 068 private static final long serialVersionUID = 5966875321133456994L; 069 070 /** The transformer to use for the key */ 071 private final Transformer<? super J, ? extends K> keyTransformer; 072 /** The transformer to use for the value */ 073 private final Transformer<? super U, ? extends V> valueTransformer; 074 075 /** 076 * Factory method to create a transforming map. 077 * <p> 078 * If there are any elements already in the map being decorated, they are 079 * NOT transformed. 080 * 081 * @param <J> the input key type 082 * @param <K> the output key type 083 * @param <U> the input value type 084 * @param <V> the output value type 085 * @param map the map to decorate, must not be null 086 * @param keyTransformer the transformer to use for key conversion, must not be null 087 * @param valueTransformer the transformer to use for value conversion, must not be null 088 * @return a new transformed map 089 * @throws NullPointerException if map or either of the transformers is null 090 */ 091 public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map, 092 final Transformer<? super J, ? extends K> keyTransformer, 093 final Transformer<? super U, ? extends V> valueTransformer) { 094 return new TransformedSplitMap<>(map, keyTransformer, valueTransformer); 095 } 096 097 //----------------------------------------------------------------------- 098 /** 099 * Constructor that wraps (not copies). 100 * <p> 101 * If there are any elements already in the collection being decorated, they 102 * are NOT transformed. 103 * 104 * @param map the map to decorate, must not be null 105 * @param keyTransformer the transformer to use for key conversion, must not be null 106 * @param valueTransformer the transformer to use for value conversion, must not be null 107 * @throws NullPointerException if map or either of the transformers is null 108 */ 109 protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer, 110 final Transformer<? super U, ? extends V> valueTransformer) { 111 super(map); 112 if (keyTransformer == null) { 113 throw new NullPointerException("KeyTransformer must not be null."); 114 } 115 this.keyTransformer = keyTransformer; 116 if (valueTransformer == null) { 117 throw new NullPointerException("ValueTransformer must not be null."); 118 } 119 this.valueTransformer = valueTransformer; 120 } 121 122 //----------------------------------------------------------------------- 123 /** 124 * Write the map out using a custom routine. 125 * 126 * @param out the output stream 127 * @throws IOException if an error occurs while writing to the stream 128 */ 129 private void writeObject(final ObjectOutputStream out) throws IOException { 130 out.defaultWriteObject(); 131 out.writeObject(decorated()); 132 } 133 134 /** 135 * Read the map in using a custom routine. 136 * 137 * @param in the input stream 138 * @throws IOException if an error occurs while reading from the stream 139 * @throws ClassNotFoundException if an object read from the stream can not be loaded 140 * @since 3.1 141 */ 142 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 143 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 144 in.defaultReadObject(); 145 map = (Map<K, V>) in.readObject(); // (1) 146 } 147 148 //----------------------------------------------------------------------- 149 /** 150 * Transforms a key. 151 * <p> 152 * The transformer itself may throw an exception if necessary. 153 * 154 * @param object the object to transform 155 * @return the transformed object 156 */ 157 protected K transformKey(final J object) { 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 * @return the transformed object 168 */ 169 protected V transformValue(final U object) { 170 return valueTransformer.transform(object); 171 } 172 173 /** 174 * Transforms a map. 175 * <p> 176 * The transformer itself may throw an exception if necessary. 177 * 178 * @param map the map to transform 179 * @return the transformed object 180 */ 181 @SuppressWarnings("unchecked") 182 protected Map<K, V> transformMap(final Map<? extends J, ? extends U> map) { 183 if (map.isEmpty()) { 184 return (Map<K, V>) map; 185 } 186 final Map<K, V> result = new LinkedMap<>(map.size()); 187 188 for (final Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { 189 result.put(transformKey(entry.getKey()), transformValue(entry.getValue())); 190 } 191 return result; 192 } 193 194 /** 195 * Override to transform the value when using <code>setValue</code>. 196 * 197 * @param value the value to transform 198 * @return the transformed value 199 */ 200 protected V checkSetValue(final U value) { 201 return valueTransformer.transform(value); 202 } 203 204 //----------------------------------------------------------------------- 205 @Override 206 public V put(final J key, final U value) { 207 return decorated().put(transformKey(key), transformValue(value)); 208 } 209 210 @Override 211 public void putAll(final Map<? extends J, ? extends U> mapToCopy) { 212 decorated().putAll(transformMap(mapToCopy)); 213 } 214 215 @Override 216 public void clear() { 217 decorated().clear(); 218 } 219}