001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.tools.collect;
017
018import static de.cuioss.tools.collect.CollectionLiterals.mutableMap;
019import static java.util.Objects.requireNonNull;
020
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import lombok.EqualsAndHashCode;
027import lombok.ToString;
028
029/**
030 * <h2>Overview</h2>
031 * <p>
032 * Builder for creating {@link java.util.Map}s providing some convenience
033 * methods. The class writes everything through into the contained collector.
034 * Using the default constructor a newly created {@link java.util.HashMap} will
035 * be used as collector, but you can pass you own collector as
036 * constructor-argument. Of course this should be mutable in order to work.
037 * </p>
038 * <p>
039 * Although not being a {@link java.util.Map} itself it provides the same
040 * methods with different semantics -&gt; Builder approach.
041 * </p>
042 * <h3>Standard Usage</h3>
043 *
044 * <pre>
045 * MapBuilder&lt;String, String&gt; builder = new MapBuilder&lt;&gt;();
046 * builder.put("key1", "value1").put("key2", "value2");
047 * assertEquals(2, builder.size());
048 * assertMutable(builder.toMutableMap());
049 * assertImmutable(builder.toImmutableMap());
050 * </pre>
051 *
052 * <h3>Using from()</h3> This methods can be used for ensuring a real copy
053 *
054 * <pre>
055 * assertEquals(4, MapBuilder.from("key1", 1, "key2", 2, "key3", 3, "key4", 4).size());
056 * </pre>
057 *
058 * @author Oliver Wolff
059 * @param <K> the type of keys maintained by this map
060 * @param <V> the type of mapped values
061 */
062@EqualsAndHashCode
063@ToString
064public final class MapBuilder<K, V> {
065
066    private final Map<K, V> collector;
067
068    /**
069     * Default Constructor initializing the collector with an
070     * {@link java.util.HashMap}
071     */
072    public MapBuilder() {
073        this(new HashMap<>());
074    }
075
076    /**
077     * <p>
078     * Constructor for MapBuilder.
079     * </p>
080     *
081     * @param collector to be used for storage. Must not be null
082     */
083    public MapBuilder(Map<K, V> collector) {
084        this.collector = requireNonNull(collector);
085    }
086
087    /**
088     * <p>
089     * size.
090     * </p>
091     *
092     * @return the number of key-value mappings in this map
093     * @see Map#size()
094     */
095    public int size() {
096        return collector.size();
097    }
098
099    /**
100     * <p>
101     * isEmpty.
102     * </p>
103     *
104     * @return true if this map contains no key-value mappings
105     * @see Map#isEmpty()
106     */
107    public boolean isEmpty() {
108        return collector.isEmpty();
109    }
110
111    /**
112     * Returns {@code true} if this map contains a mapping for the specified key.
113     * More formally, returns {@code true} if and only if this map contains a
114     * mapping for a key {@code k} such that
115     * {@code (key==null ? k==null : key.equals(k))}. (There can be at most one such
116     * mapping.)
117     *
118     * @param key key whose presence in this map is to be tested
119     * @return {@code true} if this map contains a mapping for the specified key
120     * @see Map#containsKey(Object)
121     */
122    public boolean containsKey(Object key) {
123        return collector.containsKey(key);
124    }
125
126    /**
127     * Returns {@code true} if this map maps one or more keys to the specified
128     * value. More formally, returns {@code true} if and only if this map contains
129     * at least one mapping to a value {@code v} such that
130     * {@code (value==null ? v==null : value.equals(v))}. This operation will
131     * probably require time linear in the map size for most implementations of the
132     * {@code Map} interface.
133     *
134     * @param value value whose presence in this map is to be tested
135     * @return {@code true} if this map maps one or more keys to the specified value
136     * @see Map#containsValue(Object)
137     */
138    public boolean containsValue(Object value) {
139        return collector.containsValue(value);
140    }
141
142    /**
143     * <p>
144     * put.
145     * </p>
146     *
147     * @param key   to be put as key, must not be empty
148     * @param value to be put as value, must not be empty
149     * @return the instance itself in order to use it in a fluent way.
150     */
151    public MapBuilder<K, V> put(K key, V value) {
152        collector.put(key, value);
153        return this;
154    }
155
156    /**
157     * Puts the entry into the map, if the value is not {@code null}.
158     *
159     * @param key   to be put as key, must not be empty
160     * @param value to be put as value
161     * @return the instance itself in order to use it in a fluent way.
162     */
163    public MapBuilder<K, V> putIfNotNull(K key, V value) {
164        if (null != value) {
165            collector.put(key, value);
166        }
167        return this;
168    }
169
170    /**
171     * <p>
172     * put.
173     * </p>
174     *
175     * @param entry to be put, must not {@code null}
176     * @return the instance itself in order to use it in a fluent way.
177     */
178    public MapBuilder<K, V> put(Entry<? extends K, ? extends V> entry) {
179        collector.put(entry.getKey(), entry.getValue());
180        return this;
181    }
182
183    /**
184     * <p>
185     * remove.
186     * </p>
187     *
188     * @param key to be removed
189     * @return the instance itself in order to use it in a fluent way.
190     */
191    public MapBuilder<K, V> remove(Object key) {
192        collector.remove(key);
193        return this;
194    }
195
196    /**
197     * <p>
198     * putAll.
199     * </p>
200     *
201     * @param map to be put
202     * @return the instance itself in order to use it in a fluent way.
203     */
204    public MapBuilder<K, V> putAll(Map<? extends K, ? extends V> map) {
205        collector.putAll(map);
206        return this;
207    }
208
209    /**
210     * Clears the contained collector
211     *
212     * @return the instance itself in order to use it in a fluent way.
213     */
214    public MapBuilder<K, V> clear() {
215        collector.clear();
216        return this;
217    }
218
219    /**
220     * <p>
221     * toMutableMap.
222     * </p>
223     *
224     * @return a mutable {@link java.util.Map} representation of the builders
225     *         content, the actual implementation is a {@link java.util.HashMap}
226     */
227    public Map<K, V> toMutableMap() {
228        return new HashMap<>(collector);
229    }
230
231    /**
232     * <p>
233     * toImmutableMap.
234     * </p>
235     *
236     * @return an immutable {@link java.util.Map} representation of the builders
237     *         content, the actual implementation does not create a copy but
238     *         provides an unmodifiable view using
239     *         {@link java.util.Collections#unmodifiableMap(Map)}
240     */
241    public Map<K, V> toImmutableMap() {
242        return Collections.unmodifiableMap(collector);
243    }
244
245    /**
246     * <p>
247     * copyFrom.
248     * </p>
249     *
250     * @param <K>      the type of keys maintained by this map
251     * @param <V>      the type of mapped values
252     * @param original map used to initialize the contained collector
253     * @return an instance of {@link de.cuioss.tools.collect.MapBuilder} initialized
254     *         with a copy of the given Map.
255     */
256    public static <K, V> MapBuilder<K, V> copyFrom(Map<K, V> original) {
257        return new MapBuilder<>(new HashMap<>(original));
258    }
259
260    /**
261     * Shorthand for creating a {@link de.cuioss.tools.collect.MapBuilder} from a
262     * given key/value pair
263     *
264     * @param <K> the type of keys maintained by this map
265     * @param <V> the type of mapped values
266     * @param k1  key to be added
267     * @param v1  value to be added
268     * @return an instance of {@link de.cuioss.tools.collect.MapBuilder} initialized
269     *         with the given key/value pair.
270     */
271    public static <K, V> MapBuilder<K, V> from(K k1, V v1) {
272        return new MapBuilder<>(mutableMap(k1, v1));
273    }
274
275    /**
276     * Shorthand for creating a {@link de.cuioss.tools.collect.MapBuilder} from
277     * given key/value pairs
278     *
279     * @param <K> the type of keys maintained by this map
280     * @param <V> the type of mapped values
281     * @param k1  key to be added
282     * @param v1  value to be added
283     * @param k2  key to be added
284     * @param v2  value to be added
285     * @return an instance of {@link de.cuioss.tools.collect.MapBuilder} initialized
286     *         with the given key/value pairs.
287     */
288    public static <K, V> MapBuilder<K, V> from(K k1, V v1, K k2, V v2) {
289        return new MapBuilder<>(mutableMap(k1, v1, k2, v2));
290    }
291
292    /**
293     * Shorthand for creating a {@link de.cuioss.tools.collect.MapBuilder} from
294     * given key/value pairs
295     *
296     * @param <K> the type of keys maintained by this map
297     * @param <V> the type of mapped values
298     * @param k1  key to be added
299     * @param v1  value to be added
300     * @param k2  key to be added
301     * @param v2  value to be added
302     * @param k3  key to be added
303     * @param v3  value to be added
304     * @return an instance of {@link de.cuioss.tools.collect.MapBuilder} initialized
305     *         with the given key/value pairs.
306     */
307    public static <K, V> MapBuilder<K, V> from(K k1, V v1, K k2, V v2, K k3, V v3) {
308        return new MapBuilder<>(mutableMap(k1, v1, k2, v2, k3, v3));
309    }
310
311    /**
312     * Shorthand for creating a {@link de.cuioss.tools.collect.MapBuilder} from
313     * given key/value pairs
314     *
315     * @param <K> the type of keys maintained by this map
316     * @param <V> the type of mapped values
317     * @param k1  key to be added
318     * @param v1  value to be added
319     * @param k2  key to be added
320     * @param v2  value to be added
321     * @param k3  key to be added
322     * @param v3  value to be added
323     * @param k4  key to be added
324     * @param v4  value to be added
325     * @return an instance of {@link de.cuioss.tools.collect.MapBuilder} initialized
326     *         with the given key/value pairs.
327     */
328    @SuppressWarnings("squid:S00107") // owolff: Number of parameters match to the use-case
329    public static <K, V> MapBuilder<K, V> from(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
330        return new MapBuilder<>(mutableMap(k1, v1, k2, v2, k3, v3, k4, v4));
331    }
332}