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