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}&lt;K, V&gt;, 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>&gt;Object, Object&gt;</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 887892 2013-11-24 13:43:45Z 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}