TransformedMap.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.map;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.Map;

  23. import org.apache.commons.collections4.Transformer;

  24. /**
  25.  * Decorates another {@code Map} to transform objects that are added.
  26.  * <p>
  27.  * The Map put methods and Map.Entry setValue method are affected by this class.
  28.  * Thus objects must be removed or searched for using their transformed form.
  29.  * For example, if the transformation converts Strings to Integers, you must
  30.  * use the Integer form to remove objects.
  31.  * </p>
  32.  * <p>
  33.  * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
  34.  * If you wish to use this map from multiple threads concurrently, you must use
  35.  * appropriate synchronization. The simplest approach is to wrap this map
  36.  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
  37.  * exceptions when accessed by concurrent threads without synchronization.
  38.  * </p>
  39.  * <p>
  40.  * This class is Serializable from Commons Collections 3.1.
  41.  * </p>
  42.  *
  43.  * @param <K> the type of the keys in this map
  44.  * @param <V> the type of the values in this map
  45.  * @see org.apache.commons.collections4.splitmap.TransformedSplitMap
  46.  * @since 3.0
  47.  */
  48. public class TransformedMap<K, V>
  49.         extends AbstractInputCheckedMapDecorator<K, V>
  50.         implements Serializable {

  51.     /** Serialization version */
  52.     private static final long serialVersionUID = 7023152376788900464L;

  53.     /**
  54.      * Factory method to create a transforming map that will transform
  55.      * existing contents of the specified map.
  56.      * <p>
  57.      * If there are any elements already in the map being decorated, they
  58.      * will be transformed by this method.
  59.      * Contrast this with {@link #transformingMap(Map, Transformer, Transformer)}.
  60.      * </p>
  61.      *
  62.      * @param <K>  the key type
  63.      * @param <V>  the value type
  64.      * @param map  the map to decorate, must not be null
  65.      * @param keyTransformer  the transformer to use for key conversion, null means no transformation
  66.      * @param valueTransformer  the transformer to use for value conversion, null means no transformation
  67.      * @return a new transformed map
  68.      * @throws NullPointerException if map is null
  69.      * @since 4.0
  70.      */
  71.     public static <K, V> TransformedMap<K, V> transformedMap(final Map<K, V> map,
  72.             final Transformer<? super K, ? extends K> keyTransformer,
  73.             final Transformer<? super V, ? extends V> valueTransformer) {
  74.         final TransformedMap<K, V> decorated = new TransformedMap<>(map, keyTransformer, valueTransformer);
  75.         if (!map.isEmpty()) {
  76.             final Map<K, V> transformed = decorated.transformMap(map);
  77.             decorated.clear();
  78.             decorated.decorated().putAll(transformed);  // avoids double transformation
  79.         }
  80.         return decorated;
  81.     }
  82.     /**
  83.      * Factory method to create a transforming map.
  84.      * <p>
  85.      * If there are any elements already in the map being decorated, they
  86.      * are NOT transformed.
  87.      * Contrast this with {@link #transformedMap(Map, Transformer, Transformer)}.
  88.      * </p>
  89.      *
  90.      * @param <K>  the key type
  91.      * @param <V>  the value type
  92.      * @param map  the map to decorate, must not be null
  93.      * @param keyTransformer  the transformer to use for key conversion, null means no transformation
  94.      * @param valueTransformer  the transformer to use for value conversion, null means no transformation
  95.      * @return a new transformed map
  96.      * @throws NullPointerException if map is null
  97.      * @since 4.0
  98.      */
  99.     public static <K, V> TransformedMap<K, V> transformingMap(final Map<K, V> map,
  100.             final Transformer<? super K, ? extends K> keyTransformer,
  101.             final Transformer<? super V, ? extends V> valueTransformer) {
  102.         return new TransformedMap<>(map, keyTransformer, valueTransformer);
  103.     }

  104.     /** The transformer to use for the key */
  105.     protected final Transformer<? super K, ? extends K> keyTransformer;

  106.     /** The transformer to use for the value */
  107.     protected final Transformer<? super V, ? extends V> valueTransformer;

  108.     /**
  109.      * Constructor that wraps (not copies).
  110.      * <p>
  111.      * If there are any elements already in the collection being decorated, they
  112.      * are NOT transformed.
  113.      * </p>
  114.      *
  115.      * @param map  the map to decorate, must not be null
  116.      * @param keyTransformer  the transformer to use for key conversion, null means no conversion
  117.      * @param valueTransformer  the transformer to use for value conversion, null means no conversion
  118.      * @throws NullPointerException if map is null
  119.      */
  120.     protected TransformedMap(final Map<K, V> map, final Transformer<? super K, ? extends K> keyTransformer,
  121.             final Transformer<? super V, ? extends V> valueTransformer) {
  122.         super(map);
  123.         this.keyTransformer = keyTransformer;
  124.         this.valueTransformer = valueTransformer;
  125.     }

  126.     /**
  127.      * Override to transform the value when using {@code setValue}.
  128.      *
  129.      * @param value  the value to transform
  130.      * @return the transformed value
  131.      * @since 3.1
  132.      */
  133.     @Override
  134.     protected V checkSetValue(final V value) {
  135.         return valueTransformer.apply(value);
  136.     }

  137.     /**
  138.      * Override to only return true when there is a value transformer.
  139.      *
  140.      * @return true if a value transformer is in use
  141.      * @since 3.1
  142.      */
  143.     @Override
  144.     protected boolean isSetValueChecking() {
  145.         return valueTransformer != null;
  146.     }

  147.     @Override
  148.     public V put(K key, V value) {
  149.         key = transformKey(key);
  150.         value = transformValue(value);
  151.         return decorated().put(key, value);
  152.     }

  153.     @Override
  154.     public void putAll(Map<? extends K, ? extends V> mapToCopy) {
  155.         mapToCopy = transformMap(mapToCopy);
  156.         decorated().putAll(mapToCopy);
  157.     }

  158.     /**
  159.      * Deserializes the map in using a custom routine.
  160.      *
  161.      * @param in  the input stream
  162.      * @throws IOException if an error occurs while reading from the stream
  163.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  164.      * @since 3.1
  165.      */
  166.     @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
  167.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  168.         in.defaultReadObject();
  169.         map = (Map<K, V>) in.readObject(); // (1)
  170.     }

  171.     /**
  172.      * Transforms a key.
  173.      * <p>
  174.      * The transformer itself may throw an exception if necessary.
  175.      *
  176.      * @param object  the object to transform
  177.      * @return the transformed object
  178.      */
  179.     protected K transformKey(final K object) {
  180.         if (keyTransformer == null) {
  181.             return object;
  182.         }
  183.         return keyTransformer.apply(object);
  184.     }

  185.     /**
  186.      * Transforms a map.
  187.      * <p>
  188.      * The transformer itself may throw an exception if necessary.
  189.      * </p>
  190.      *
  191.      * @param map  the map to transform
  192.      * @return the transformed object
  193.      */
  194.     @SuppressWarnings("unchecked")
  195.     protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
  196.         if (map.isEmpty()) {
  197.             return (Map<K, V>) map;
  198.         }
  199.         final Map<K, V> result = new LinkedMap<>(map.size());

  200.         for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
  201.             result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
  202.         }
  203.         return result;
  204.     }

  205.     /**
  206.      * Transforms a value.
  207.      * <p>
  208.      * The transformer itself may throw an exception if necessary.
  209.      * </p>
  210.      *
  211.      * @param object  the object to transform
  212.      * @return the transformed object
  213.      */
  214.     protected V transformValue(final V object) {
  215.         if (valueTransformer == null) {
  216.             return object;
  217.         }
  218.         return valueTransformer.apply(object);
  219.     }

  220.     /**
  221.      * Serializes this object to an ObjectOutputStream.
  222.      *
  223.      * @param out the target ObjectOutputStream.
  224.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  225.      * @since 3.1
  226.      */
  227.     private void writeObject(final ObjectOutputStream out) throws IOException {
  228.         out.defaultWriteObject();
  229.         out.writeObject(map);
  230.     }

  231. }