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 972397 2015-11-14 15:01:49Z tn $
057 *
058 * @see org.apache.commons.collections4.SplitMapUtils#readableMap(org.apache.commons.collections4.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, must not be null
084     * @param valueTransformer the transformer to use for value conversion, must not be null
085     * @return a new transformed map
086     * @throws NullPointerException if map or either of the transformers is null
087     */
088    public static <J, K, U, V> TransformedSplitMap<J, K, U, V> transformingMap(final Map<K, V> map,
089            final Transformer<? super J, ? extends K> keyTransformer,
090            final Transformer<? super U, ? extends V> valueTransformer) {
091        return new TransformedSplitMap<J, K, U, V>(map, keyTransformer, valueTransformer);
092    }
093
094    //-----------------------------------------------------------------------
095    /**
096     * Constructor that wraps (not copies).
097     * <p>
098     * If there are any elements already in the collection being decorated, they
099     * are NOT transformed.
100     *
101     * @param map the map to decorate, must not be null
102     * @param keyTransformer the transformer to use for key conversion, must not be null
103     * @param valueTransformer the transformer to use for value conversion, must not be null
104     * @throws NullPointerException if map or either of the transformers is null
105     */
106    protected TransformedSplitMap(final Map<K, V> map, final Transformer<? super J, ? extends K> keyTransformer,
107            final Transformer<? super U, ? extends V> valueTransformer) {
108        super(map);
109        if (keyTransformer == null) {
110            throw new NullPointerException("KeyTransformer must not be null.");
111        }
112        this.keyTransformer = keyTransformer;
113        if (valueTransformer == null) {
114            throw new NullPointerException("ValueTransformer must not be null.");
115        }
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     */
126    private void writeObject(final ObjectOutputStream out) throws IOException {
127        out.defaultWriteObject();
128        out.writeObject(decorated());
129    }
130
131    /**
132     * Read the map in using a custom routine.
133     *
134     * @param in the input stream
135     * @throws IOException
136     * @throws ClassNotFoundException
137     * @since 3.1
138     */
139    @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
140    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
141        in.defaultReadObject();
142        map = (Map<K, V>) in.readObject(); // (1)
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     * @return the transformed object
153     */
154    protected K transformKey(final J object) {
155        return keyTransformer.transform(object);
156    }
157
158    /**
159     * Transforms a value.
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 V transformValue(final U object) {
167        return valueTransformer.transform(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<K, V>(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     * Override to transform the value when using <code>setValue</code>.
193     *
194     * @param value the value to transform
195     * @return the transformed value
196     */
197    protected V checkSetValue(final U value) {
198        return valueTransformer.transform(value);
199    }
200
201    //-----------------------------------------------------------------------
202    public V put(final J key, final U value) {
203        return decorated().put(transformKey(key), transformValue(value));
204    }
205
206    public void putAll(final Map<? extends J, ? extends U> mapToCopy) {
207        decorated().putAll(transformMap(mapToCopy));
208    }
209
210    public void clear() {
211        decorated().clear();
212    }
213}