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
019
020import java.util.*;
021import java.util.function.*;
022import static org.apache.juneau.commons.utils.Shorts.*;
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&lt;String, String&gt; <jv>map</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;());
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&lt;String, String&gt; <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&lt;String, String&gt; <jv>config</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;())
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 &gt; juneau-commons</a>
083 * </ul>
084 *
085 * @param <K> The key type.
086 * @param <V> The value type.
087 */
088@SuppressWarnings({
089   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
090})
091public class FluentMap<K,V> extends AbstractMap<K,V> {
092
093   private final Map<K,V> map;
094
095   /**
096    * Constructor.
097    *
098    * @param inner The underlying map to wrap. Must not be <jk>null</jk>.
099    */
100   public FluentMap(Map<K,V> inner) {
101      this.map = reqnn("inner", inner);
102   }
103
104   /**
105    * Adds a single key-value pair to this map.
106    *
107    * <p>
108    * This is a convenience method that calls {@link #put(Object, Object)} and returns <c>this</c>
109    * for method chaining.
110    *
111    * <h5 class='section'>Example:</h5>
112    * <p class='bjava'>
113    *    FluentMap&lt;String, String&gt; <jv>map</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;());
114    *    <jv>map</jv>.a(<js>"key1"</js>, <js>"value1"</js>).a(<js>"key2"</js>, <js>"value2"</js>);
115    * </p>
116    *
117    * @param key The key to add.
118    * @param value The value to add.
119    * @return This object for method chaining.
120    */
121   public FluentMap<K,V> a(K key, V value) {
122      map.put(key, value);
123      return this;
124   }
125
126   /**
127    * Adds all entries from the specified map to this map.
128    *
129    * <p>
130    * This is a convenience method that calls {@link #putAll(Map)} and returns <c>this</c>
131    * for method chaining. If the specified map is <jk>null</jk>, this is a no-op.
132    *
133    * <h5 class='section'>Example:</h5>
134    * <p class='bjava'>
135    *    FluentMap&lt;String, String&gt; <jv>map</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;());
136    *    Map&lt;String, String&gt; <jv>other</jv> = Map.of(<js>"key1"</js>, <js>"value1"</js>, <js>"key2"</js>, <js>"value2"</js>);
137    *    <jv>map</jv>.aa(<jv>other</jv>).a(<js>"key3"</js>, <js>"value3"</js>);
138    * </p>
139    *
140    * @param m The map whose entries are to be added. Can be <jk>null</jk> (no-op).
141    * @return This object for method chaining.
142    */
143   public FluentMap<K,V> aa(Map<? extends K,? extends V> m) {
144      if (m != null)
145         map.putAll(m);
146      return this;
147   }
148
149   /**
150    * Adds a key-value pair to this map if the specified boolean condition is <jk>true</jk>.
151    *
152    * <p>
153    * This method is useful for conditionally adding entries based on runtime conditions.
154    * If the condition is <jk>false</jk>, the entry is not added and this method returns <c>this</c>
155    * without modifying the map.
156    *
157    * <h5 class='section'>Example:</h5>
158    * <p class='bjava'>
159    *    <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
160    *    <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
161    *
162    *    FluentMap&lt;String, String&gt; <jv>map</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;())
163    *       .a(<js>"host"</js>, <js>"localhost"</js>)
164    *       .ai(<jv>includeDebug</jv>, <js>"debug"</js>, <js>"true"</js>)   <jc>// Added</jc>
165    *       .ai(<jv>includeTest</jv>, <js>"test"</js>, <js>"true"</js>);    <jc>// Not added</jc>
166    * </p>
167    *
168    * @param condition The condition to evaluate. If <jk>true</jk>, the entry is added; if <jk>false</jk>, it is not.
169    * @param key The key to add if the condition is <jk>true</jk>.
170    * @param value The value to add if the condition is <jk>true</jk>.
171    * @return This object for method chaining.
172    */
173   public FluentMap<K,V> ai(boolean condition, K key, V value) {
174      if (condition)
175         map.put(key, value);
176      return this;
177   }
178
179   /**
180    * Adds a key-value pair to this map if the specified predicate returns <jk>true</jk> when applied to the value.
181    *
182    * <p>
183    * This method is useful for conditionally adding entries based on the value itself.
184    * The predicate is applied to the value, and if it returns <jk>true</jk>, the entry is added.
185    * If the predicate returns <jk>false</jk>, the entry is not added and this method returns <c>this</c>
186    * without modifying the map.
187    *
188    * <h5 class='section'>Example:</h5>
189    * <p class='bjava'>
190    *    FluentMap&lt;String, String&gt; <jv>map</jv> = <jk>new</jk> FluentMap&lt;&gt;(<jk>new</jk> LinkedHashMap&lt;&gt;())
191    *       .a(<js>"host"</js>, <js>"localhost"</js>)
192    *       .ai(s -&gt; !s.isEmpty(), <js>"debug"</js>, <js>"true"</js>)   <jc>// Added (value is not empty)</jc>
193    *       .ai(s -&gt; !s.isEmpty(), <js>"test"</js>, <js>""</js>);       <jc>// Not added (value is empty)</jc>
194    * </p>
195    *
196    * @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.
197    * @param key The key to add if the predicate returns <jk>true</jk>.
198    * @param value The value to add if the predicate returns <jk>true</jk>.
199    * @return This object for method chaining.
200    */
201   public FluentMap<K,V> ai(Predicate<V> predicate, K key, V value) {
202      if (predicate.test(value))
203         map.put(key, value);
204      return this;
205   }
206
207   @Override
208   public Set<Entry<K,V>> entrySet() {
209      return map.entrySet();
210   }
211
212   @Override
213   public V get(Object key) {
214      return map.get(key);
215   }
216
217   @Override
218   public V put(K key, V value) {
219      return map.put(key, value);
220   }
221
222   @Override
223   public V remove(Object key) {
224      return map.remove(key);
225   }
226
227   @Override
228   public void putAll(Map<? extends K,? extends V> m) {
229      map.putAll(m);
230   }
231
232   @Override
233   public void clear() {
234      map.clear();
235   }
236
237   @Override
238   public boolean containsKey(Object key) {
239      return map.containsKey(key);
240   }
241
242   @Override
243   public boolean containsValue(Object value) {
244      return map.containsValue(value);
245   }
246
247   @Override
248   public int size() {
249      return map.size();
250   }
251
252   @Override
253   public boolean isEmpty() {
254      return map.isEmpty();
255   }
256
257   @Override
258   public Set<K> keySet() {
259      return map.keySet();
260   }
261
262   @Override
263   public Collection<V> values() {
264      return map.values();
265   }
266
267   @Override
268   public String toString() {
269      return map.toString();
270   }
271
272   @Override
273   public boolean equals(Object o) {
274      return map.equals(o);
275   }
276
277   @Override
278   public int hashCode() {
279      return map.hashCode();
280   }
281}
282