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.multimap; 018 019import java.util.Iterator; 020import java.util.Map; 021import java.util.Objects; 022 023import org.apache.commons.collections4.CollectionUtils; 024import org.apache.commons.collections4.FluentIterable; 025import org.apache.commons.collections4.MultiValuedMap; 026import org.apache.commons.collections4.Transformer; 027 028/** 029 * Decorates another {@code MultiValuedMap} to transform objects that are added. 030 * <p> 031 * This class affects the MultiValuedMap put methods. Thus objects must be 032 * removed or searched for using their transformed form. For example, if the 033 * transformation converts Strings to Integers, you must use the Integer form to 034 * remove objects. 035 * </p> 036 * <p> 037 * <strong>Note that TransformedMultiValuedMap is not synchronized and is not thread-safe.</strong> 038 * </p> 039 * 040 * @param <K> the type of the keys in this map 041 * @param <V> the type of the values in this map 042 * @since 4.1 043 */ 044public class TransformedMultiValuedMap<K, V> extends AbstractMultiValuedMapDecorator<K, V> { 045 046 /** Serialization Version */ 047 private static final long serialVersionUID = 20150612L; 048 049 /** 050 * Factory method to create a transforming MultiValuedMap that will 051 * transform existing contents of the specified map. 052 * <p> 053 * If there are any elements already in the map being decorated, they will 054 * be transformed by this method. Contrast this with 055 * {@link #transformingMap(MultiValuedMap, Transformer, Transformer)}. 056 * 057 * @param <K> the key type 058 * @param <V> the value type 059 * @param map the MultiValuedMap to decorate, may not be null 060 * @param keyTransformer the transformer to use for key conversion, null means no conversion 061 * @param valueTransformer the transformer to use for value conversion, null means no conversion 062 * @return a new transformed MultiValuedMap 063 * @throws NullPointerException if map is null 064 */ 065 public static <K, V> TransformedMultiValuedMap<K, V> transformedMap(final MultiValuedMap<K, V> map, 066 final Transformer<? super K, ? extends K> keyTransformer, 067 final Transformer<? super V, ? extends V> valueTransformer) { 068 final TransformedMultiValuedMap<K, V> decorated = 069 new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer); 070 if (!map.isEmpty()) { 071 final MultiValuedMap<K, V> mapCopy = new ArrayListValuedHashMap<>(map); 072 decorated.clear(); 073 decorated.putAll(mapCopy); 074 } 075 return decorated; 076 } 077 078 /** 079 * Factory method to create a transforming MultiValuedMap. 080 * <p> 081 * If there are any elements already in the map being decorated, they are 082 * NOT transformed. Contrast this with 083 * {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}. 084 * 085 * @param <K> the key type 086 * @param <V> the value type 087 * @param map the MultiValuedMap to decorate, may not be null 088 * @param keyTransformer the transformer to use for key conversion, null means no conversion 089 * @param valueTransformer the transformer to use for value conversion, null means no conversion 090 * @return a new transformed MultiValuedMap 091 * @throws NullPointerException if map is null 092 */ 093 public static <K, V> TransformedMultiValuedMap<K, V> transformingMap(final MultiValuedMap<K, V> map, 094 final Transformer<? super K, ? extends K> keyTransformer, 095 final Transformer<? super V, ? extends V> valueTransformer) { 096 return new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer); 097 } 098 099 /** The key transformer */ 100 private final Transformer<? super K, ? extends K> keyTransformer; 101 102 /** The value transformer */ 103 private final Transformer<? super V, ? extends V> valueTransformer; 104 105 /** 106 * Constructor that wraps (not copies). 107 * <p> 108 * If there are any elements already in the collection being decorated, they 109 * are NOT transformed. 110 * 111 * @param map the MultiValuedMap to decorate, may not be null 112 * @param keyTransformer the transformer to use for key conversion, null means no conversion 113 * @param valueTransformer the transformer to use for value conversion, null means no conversion 114 * @throws NullPointerException if map is null 115 */ 116 protected TransformedMultiValuedMap(final MultiValuedMap<K, V> map, 117 final Transformer<? super K, ? extends K> keyTransformer, 118 final Transformer<? super V, ? extends V> valueTransformer) { 119 super(map); 120 this.keyTransformer = keyTransformer; 121 this.valueTransformer = valueTransformer; 122 } 123 124 @Override 125 public boolean put(final K key, final V value) { 126 return decorated().put(transformKey(key), transformValue(value)); 127 } 128 129 @Override 130 public boolean putAll(final K key, final Iterable<? extends V> values) { 131 Objects.requireNonNull(values, "values"); 132 133 final Iterable<V> transformedValues = FluentIterable.of(values).transform(valueTransformer); 134 final Iterator<? extends V> it = transformedValues.iterator(); 135 return it.hasNext() && CollectionUtils.addAll(decorated().get(transformKey(key)), it); 136 } 137 138 @Override 139 public boolean putAll(final Map<? extends K, ? extends V> map) { 140 Objects.requireNonNull(map, "map"); 141 boolean changed = false; 142 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 143 changed |= put(entry.getKey(), entry.getValue()); 144 } 145 return changed; 146 } 147 148 @Override 149 public boolean putAll(final MultiValuedMap<? extends K, ? extends V> map) { 150 Objects.requireNonNull(map, "map"); 151 boolean changed = false; 152 for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) { 153 changed |= put(entry.getKey(), entry.getValue()); 154 } 155 return changed; 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 K object) { 167 if (keyTransformer == null) { 168 return object; 169 } 170 return keyTransformer.transform(object); 171 } 172 173 /** 174 * Transforms a value. 175 * <p> 176 * The transformer itself may throw an exception if necessary. 177 * 178 * @param object the object to transform 179 * @return the transformed object 180 */ 181 protected V transformValue(final V object) { 182 if (valueTransformer == null) { 183 return object; 184 } 185 return valueTransformer.transform(object); 186 } 187 188}