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.juneau.commons.collections; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020 021import java.util.*; 022import java.util.function.Predicate; 023 024/** 025 * A fluent wrapper around an arbitrary map that provides convenient methods for adding entries. 026 * 027 * <p> 028 * This class wraps an underlying map and provides a fluent API for adding entries. All methods return 029 * <c>this</c> to allow method chaining. The underlying map can be any {@link Map} implementation. 030 * 031 * <h5 class='section'>Features:</h5> 032 * <ul class='spaced-list'> 033 * <li><b>Fluent API:</b> All methods return <c>this</c> for method chaining 034 * <li><b>Arbitrary Map Support:</b> Works with any map implementation 035 * <li><b>Conditional Adding:</b> Add entries conditionally based on boolean expressions 036 * <li><b>Transparent Interface:</b> Implements the full {@link Map} interface, so it can be used anywhere a map is expected 037 * </ul> 038 * 039 * <h5 class='section'>Usage:</h5> 040 * <p class='bjava'> 041 * <jc>// Create a FluentMap wrapping a LinkedHashMap</jc> 042 * FluentMap<String, String> <jv>map</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()); 043 * 044 * <jv>map</jv> 045 * .a(<js>"key1"</js>, <js>"value1"</js>) 046 * .a(<js>"key2"</js>, <js>"value2"</js>) 047 * .ai(<jk>true</jk>, <js>"key3"</js>, <js>"value3"</js>) <jc>// Added</jc> 048 * .ai(<jk>false</jk>, <js>"key4"</js>, <js>"value4"</js>); <jc>// Not added</jc> 049 * 050 * <jc>// Add all entries from another map</jc> 051 * Map<String, String> <jv>other</jv> = Map.of(<js>"key5"</js>, <js>"value5"</js>, <js>"key6"</js>, <js>"value6"</js>); 052 * <jv>map</jv>.aa(<jv>other</jv>); 053 * </p> 054 * 055 * <h5 class='section'>Example - Conditional Building:</h5> 056 * <p class='bjava'> 057 * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>; 058 * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>; 059 * 060 * FluentMap<String, String> <jv>config</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()) 061 * .a(<js>"host"</js>, <js>"localhost"</js>) 062 * .a(<js>"port"</js>, <js>"8080"</js>) 063 * .ai(<jv>includeDebug</jv>, <js>"debug"</js>, <js>"true"</js>) <jc>// Added</jc> 064 * .ai(<jv>includeTest</jv>, <js>"test"</js>, <js>"true"</js>); <jc>// Not added</jc> 065 * </p> 066 * 067 * <h5 class='section'>Behavior Notes:</h5> 068 * <ul class='spaced-list'> 069 * <li>All map operations are delegated to the underlying map 070 * <li>The fluent methods ({@link #a(Object, Object)}, {@link #aa(Map)}, {@link #ai(boolean, Object, Object)}) return <c>this</c> for chaining 071 * <li>If a <jk>null</jk> map is passed to {@link #aa(Map)}, it is treated as a no-op 072 * <li>The underlying map is stored by reference (not copied), so modifications affect the original map 073 * </ul> 074 * 075 * <h5 class='section'>Thread Safety:</h5> 076 * <p> 077 * This class is not thread-safe unless the underlying map is thread-safe. If thread safety is required, 078 * use a thread-safe map type (e.g., {@link java.util.concurrent.ConcurrentHashMap}). 079 * 080 * <h5 class='section'>See Also:</h5> 081 * <ul> 082 * <li class='link'><a class="doclink" href="../../../../../index.html#juneau-commons">Overview > juneau-commons</a> 083 * </ul> 084 * 085 * @param <K> The key type. 086 * @param <V> The value type. 087 */ 088public class FluentMap<K,V> extends AbstractMap<K,V> { 089 090 private final Map<K,V> map; 091 092 /** 093 * Constructor. 094 * 095 * @param inner The underlying map to wrap. Must not be <jk>null</jk>. 096 */ 097 public FluentMap(Map<K,V> inner) { 098 this.map = assertArgNotNull("inner", inner); 099 } 100 101 /** 102 * Adds a single key-value pair to this map. 103 * 104 * <p> 105 * This is a convenience method that calls {@link #put(Object, Object)} and returns <c>this</c> 106 * for method chaining. 107 * 108 * <h5 class='section'>Example:</h5> 109 * <p class='bjava'> 110 * FluentMap<String, String> <jv>map</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()); 111 * <jv>map</jv>.a(<js>"key1"</js>, <js>"value1"</js>).a(<js>"key2"</js>, <js>"value2"</js>); 112 * </p> 113 * 114 * @param key The key to add. 115 * @param value The value to add. 116 * @return This object for method chaining. 117 */ 118 public FluentMap<K,V> a(K key, V value) { 119 map.put(key, value); 120 return this; 121 } 122 123 /** 124 * Adds all entries from the specified map to this map. 125 * 126 * <p> 127 * This is a convenience method that calls {@link #putAll(Map)} and returns <c>this</c> 128 * for method chaining. If the specified map is <jk>null</jk>, this is a no-op. 129 * 130 * <h5 class='section'>Example:</h5> 131 * <p class='bjava'> 132 * FluentMap<String, String> <jv>map</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()); 133 * Map<String, String> <jv>other</jv> = Map.of(<js>"key1"</js>, <js>"value1"</js>, <js>"key2"</js>, <js>"value2"</js>); 134 * <jv>map</jv>.aa(<jv>other</jv>).a(<js>"key3"</js>, <js>"value3"</js>); 135 * </p> 136 * 137 * @param m The map whose entries are to be added. Can be <jk>null</jk> (no-op). 138 * @return This object for method chaining. 139 */ 140 public FluentMap<K,V> aa(Map<? extends K, ? extends V> m) { 141 if (m != null) 142 map.putAll(m); 143 return this; 144 } 145 146 /** 147 * Adds a key-value pair to this map if the specified boolean condition is <jk>true</jk>. 148 * 149 * <p> 150 * This method is useful for conditionally adding entries based on runtime conditions. 151 * If the condition is <jk>false</jk>, the entry is not added and this method returns <c>this</c> 152 * without modifying the map. 153 * 154 * <h5 class='section'>Example:</h5> 155 * <p class='bjava'> 156 * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>; 157 * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>; 158 * 159 * FluentMap<String, String> <jv>map</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()) 160 * .a(<js>"host"</js>, <js>"localhost"</js>) 161 * .ai(<jv>includeDebug</jv>, <js>"debug"</js>, <js>"true"</js>) <jc>// Added</jc> 162 * .ai(<jv>includeTest</jv>, <js>"test"</js>, <js>"true"</js>); <jc>// Not added</jc> 163 * </p> 164 * 165 * @param condition The condition to evaluate. If <jk>true</jk>, the entry is added; if <jk>false</jk>, it is not. 166 * @param key The key to add if the condition is <jk>true</jk>. 167 * @param value The value to add if the condition is <jk>true</jk>. 168 * @return This object for method chaining. 169 */ 170 public FluentMap<K,V> ai(boolean condition, K key, V value) { 171 if (condition) 172 map.put(key, value); 173 return this; 174 } 175 176 /** 177 * Adds a key-value pair to this map if the specified predicate returns <jk>true</jk> when applied to the value. 178 * 179 * <p> 180 * This method is useful for conditionally adding entries based on the value itself. 181 * The predicate is applied to the value, and if it returns <jk>true</jk>, the entry is added. 182 * If the predicate returns <jk>false</jk>, the entry is not added and this method returns <c>this</c> 183 * without modifying the map. 184 * 185 * <h5 class='section'>Example:</h5> 186 * <p class='bjava'> 187 * FluentMap<String, String> <jv>map</jv> = <jk>new</jk> FluentMap<>(<jk>new</jk> LinkedHashMap<>()) 188 * .a(<js>"host"</js>, <js>"localhost"</js>) 189 * .ai(s -> !s.isEmpty(), <js>"debug"</js>, <js>"true"</js>) <jc>// Added (value is not empty)</jc> 190 * .ai(s -> !s.isEmpty(), <js>"test"</js>, <js>""</js>); <jc>// Not added (value is empty)</jc> 191 * </p> 192 * 193 * @param predicate The predicate to test on the value. If it returns <jk>true</jk>, the entry is added; if <jk>false</jk>, it is not. 194 * @param key The key to add if the predicate returns <jk>true</jk>. 195 * @param value The value to add if the predicate returns <jk>true</jk>. 196 * @return This object for method chaining. 197 */ 198 public FluentMap<K,V> ai(Predicate<V> predicate, K key, V value) { 199 if (predicate.test(value)) 200 map.put(key, value); 201 return this; 202 } 203 204 @Override 205 public Set<Entry<K,V>> entrySet() { 206 return map.entrySet(); 207 } 208 209 @Override 210 public V get(Object key) { 211 return map.get(key); 212 } 213 214 @Override 215 public V put(K key, V value) { 216 return map.put(key, value); 217 } 218 219 @Override 220 public V remove(Object key) { 221 return map.remove(key); 222 } 223 224 @Override 225 public void putAll(Map<? extends K, ? extends V> m) { 226 map.putAll(m); 227 } 228 229 @Override 230 public void clear() { 231 map.clear(); 232 } 233 234 @Override 235 public boolean containsKey(Object key) { 236 return map.containsKey(key); 237 } 238 239 @Override 240 public boolean containsValue(Object value) { 241 return map.containsValue(value); 242 } 243 244 @Override 245 public int size() { 246 return map.size(); 247 } 248 249 @Override 250 public boolean isEmpty() { 251 return map.isEmpty(); 252 } 253 254 @Override 255 public Set<K> keySet() { 256 return map.keySet(); 257 } 258 259 @Override 260 public Collection<V> values() { 261 return map.values(); 262 } 263 264 @Override 265 public String toString() { 266 return map.toString(); 267 } 268 269 @Override 270 public boolean equals(Object o) { 271 return map.equals(o); 272 } 273 274 @Override 275 public int hashCode() { 276 return map.hashCode(); 277 } 278} 279