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 * </p> 057 * 058 * @param <K> the key type 059 * @param <V> the value type 060 * @param map the MultiValuedMap to decorate, may not be null 061 * @param keyTransformer the transformer to use for key conversion, null means no conversion 062 * @param valueTransformer the transformer to use for value conversion, null means no conversion 063 * @return a new transformed MultiValuedMap 064 * @throws NullPointerException if map is null 065 */ 066 public static <K, V> TransformedMultiValuedMap<K, V> transformedMap(final MultiValuedMap<K, V> map, 067 final Transformer<? super K, ? extends K> keyTransformer, 068 final Transformer<? super V, ? extends V> valueTransformer) { 069 final TransformedMultiValuedMap<K, V> decorated = 070 new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer); 071 if (!map.isEmpty()) { 072 final MultiValuedMap<K, V> mapCopy = new ArrayListValuedHashMap<>(map); 073 decorated.clear(); 074 decorated.putAll(mapCopy); 075 } 076 return decorated; 077 } 078 079 /** 080 * Factory method to create a transforming MultiValuedMap. 081 * <p> 082 * If there are any elements already in the map being decorated, they are 083 * NOT transformed. Contrast this with 084 * {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}. 085 * </p> 086 * 087 * @param <K> the key type 088 * @param <V> the value type 089 * @param map the MultiValuedMap to decorate, may not be null 090 * @param keyTransformer the transformer to use for key conversion, null means no conversion 091 * @param valueTransformer the transformer to use for value conversion, null means no conversion 092 * @return a new transformed MultiValuedMap 093 * @throws NullPointerException if map is null 094 */ 095 public static <K, V> TransformedMultiValuedMap<K, V> transformingMap(final MultiValuedMap<K, V> map, 096 final Transformer<? super K, ? extends K> keyTransformer, 097 final Transformer<? super V, ? extends V> valueTransformer) { 098 return new TransformedMultiValuedMap<>(map, keyTransformer, valueTransformer); 099 } 100 101 /** The key transformer */ 102 private final Transformer<? super K, ? extends K> keyTransformer; 103 104 /** The value transformer */ 105 private final Transformer<? super V, ? extends V> valueTransformer; 106 107 /** 108 * Constructor that wraps (not copies). 109 * <p> 110 * If there are any elements already in the collection being decorated, they 111 * are NOT transformed. 112 * </p> 113 * 114 * @param map the MultiValuedMap to decorate, may not be null 115 * @param keyTransformer the transformer to use for key conversion, null means no conversion 116 * @param valueTransformer the transformer to use for value conversion, null means no conversion 117 * @throws NullPointerException if map is null 118 */ 119 protected TransformedMultiValuedMap(final MultiValuedMap<K, V> map, 120 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 128 public boolean put(final K key, final V value) { 129 return decorated().put(transformKey(key), transformValue(value)); 130 } 131 132 @Override 133 public boolean putAll(final K key, final Iterable<? extends V> values) { 134 Objects.requireNonNull(values, "values"); 135 136 final Iterable<V> transformedValues = FluentIterable.of(values).transform(valueTransformer); 137 final Iterator<? extends V> it = transformedValues.iterator(); 138 return it.hasNext() && CollectionUtils.addAll(decorated().get(transformKey(key)), it); 139 } 140 141 @Override 142 public boolean putAll(final Map<? extends K, ? extends V> map) { 143 Objects.requireNonNull(map, "map"); 144 boolean changed = false; 145 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 146 changed |= put(entry.getKey(), entry.getValue()); 147 } 148 return changed; 149 } 150 151 @Override 152 public boolean putAll(final MultiValuedMap<? extends K, ? extends V> map) { 153 Objects.requireNonNull(map, "map"); 154 boolean changed = false; 155 for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) { 156 changed |= put(entry.getKey(), entry.getValue()); 157 } 158 return changed; 159 } 160 161 /** 162 * Transforms a key. 163 * <p> 164 * The transformer itself may throw an exception if necessary. 165 * </p> 166 * 167 * @param object the object to transform 168 * @return the transformed object 169 */ 170 protected K transformKey(final K object) { 171 if (keyTransformer == null) { 172 return object; 173 } 174 return keyTransformer.apply(object); 175 } 176 177 /** 178 * Transforms a value. 179 * <p> 180 * The transformer itself may throw an exception if necessary. 181 * </p> 182 * 183 * @param object the object to transform 184 * @return the transformed object 185 */ 186 protected V transformValue(final V object) { 187 if (valueTransformer == null) { 188 return object; 189 } 190 return valueTransformer.apply(object); 191 } 192 193}