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.mutableList;
019import static java.util.Objects.requireNonNull;
020
021import java.lang.reflect.Array;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.List;
029import java.util.NavigableSet;
030import java.util.Optional;
031import java.util.Set;
032import java.util.TreeSet;
033import java.util.concurrent.ConcurrentSkipListSet;
034import java.util.concurrent.CopyOnWriteArrayList;
035import java.util.concurrent.CopyOnWriteArraySet;
036import java.util.stream.Stream;
037
038import lombok.EqualsAndHashCode;
039import lombok.Getter;
040import lombok.ToString;
041
042/**
043 * <h2>Overview</h2> Builder for creating {@link java.util.Collection}s
044 * providing some convenience methods. The class writes everything through into
045 * the contained collector. Using the default constructor a newly created
046 * {@link java.util.ArrayList} will be used as collector, but you can pass you
047 * own collector as constructor-argument. Of course this should be mutable in
048 * order to work.
049 * <h3>Handling of null-values</h3>
050 * <p>
051 * As default {@code null} values are ignored. This behavior can be changed by
052 * call {@link #addNullValues(boolean)}. <em>Caution:</em> In case of using one
053 * of the {@link #copyFrom(Collection)} methods for instantiation the
054 * {@code null} values will not be checked in that way
055 * </p>
056 * <h3>Standard Usage</h3>
057 *
058 * <pre>
059 *
060 * List&lt;String&gt; result = new CollectionBuilder&lt;String&gt;().add("this").add("that").add(mutableList("on", "or an other"))
061 *         .toImmutableList();
062 * </pre>
063 *
064 * or
065 *
066 * <pre>
067 *
068 * Set&lt;String&gt; result = new CollectionBuilder&lt;String&gt;().add("this").add("that").add(mutableList("on", "or an other"))
069 *         .toMutableSet();
070 * </pre>
071 *
072 * <h3>Copy From</h3> This methods can be used for ensuring a real copy
073 *
074 * <pre>
075 *
076 * List&lt;String&gt; result = CollectionBuilder.copyFrom(mutableList("on", "or an other")).add("element").toMutableList();
077 *
078 * </pre>
079 *
080 * <h3>Sorting</h3>
081 * <p>
082 * The contained {@link java.util.Collection} can be sorted any time by calling
083 * {@link #sort(Comparator)}
084 * </p>
085 *
086 * @author Oliver Wolff
087 * @param <E> The type of the contained {@link java.util.Collection}
088 */
089@EqualsAndHashCode
090@ToString
091public final class CollectionBuilder<E> implements Iterable<E> {
092
093    private final Collection<E> collector;
094
095    /**
096     * If set to {@code true} {@code null} elements are added to the contained
097     * collector, if {@code false}, default value, {@code null} values are ignored.
098     */
099    @Getter
100    private boolean addNullValues = false;
101
102    /**
103     * <p>
104     * Constructor for CollectionBuilder.
105     * </p>
106     *
107     * @param collector to be used for storage. Must not be null
108     */
109    public CollectionBuilder(Collection<E> collector) {
110        this.collector = requireNonNull(collector);
111    }
112
113    /**
114     * Default Constructor initializing the collector with an
115     * {@link java.util.ArrayList}
116     */
117    public CollectionBuilder() {
118        this(new ArrayList<>());
119    }
120
121    /**
122     * <p>
123     * addNullValues.
124     * </p>
125     *
126     * @param addNullValues If set to {@code true} {@code null} elements are added
127     *                      to the contained collector, if {@code false}, default
128     *                      value, {@code null} values are ignored.
129     * @return the instance itself in order to use it in a fluent way.
130     */
131    public CollectionBuilder<E> addNullValues(boolean addNullValues) {
132        this.addNullValues = addNullValues;
133        return this;
134    }
135
136    /**
137     * <p>
138     * size.
139     * </p>
140     *
141     * @return the size of the contained Collection
142     */
143    public int size() {
144        return collector.size();
145    }
146
147    /**
148     * <p>
149     * isEmpty.
150     * </p>
151     *
152     * @return see {@link java.util.Collection#isEmpty()}
153     */
154    public boolean isEmpty() {
155        return collector.isEmpty();
156    }
157
158    /**
159     * <p>
160     * contains.
161     * </p>
162     *
163     * @param o a {@link java.lang.Object} object
164     * @return see {@link java.util.Collection#isEmpty()}
165     */
166    public boolean contains(Object o) {
167        return collector.contains(o);
168    }
169
170    /** {@inheritDoc} */
171    @Override
172    public Iterator<E> iterator() {
173        return collector.iterator();
174    }
175
176    /**
177     * <p>
178     * stream.
179     * </p>
180     *
181     * @return a {@link java.util.stream.Stream} on the contained objects
182     */
183    public Stream<E> stream() {
184        return collector.stream();
185    }
186
187    /**
188     * <p>
189     * add.
190     * </p>
191     *
192     * @param e the element to be added
193     * @return the instance itself in order to use it in a fluent way.
194     *         <em>Caution:</em> with this call the return value of
195     *         {@link java.util.Collection#add(Object)} will be ignored.
196     */
197    public CollectionBuilder<E> add(E e) {
198        if (addNullValues || null != e) {
199            collector.add(e);
200        }
201        return this;
202    }
203
204    /**
205     * <p>
206     * add.
207     * </p>
208     *
209     * @param elements to be added
210     * @return the instance itself in order to use it in a fluent way.
211     *         <em>Caution:</em> with this call the return value of
212     *         {@link java.util.Collection#add(Object)} will be ignored.
213     */
214    @SafeVarargs
215    public final CollectionBuilder<E> add(E... elements) {
216        if (!MoreCollections.isEmpty(elements)) {
217            for (E element : elements) {
218                add(element);
219            }
220        }
221        return this;
222    }
223
224    /**
225     * <p>
226     * add.
227     * </p>
228     *
229     * @param elements to be added
230     * @return the instance itself in order to use it in a fluent way.
231     *         <em>Caution:</em> with this call the return value of
232     *         {@link java.util.Collection#add(Object)} will be ignored.
233     */
234    public CollectionBuilder<E> add(Iterable<E> elements) {
235        if (!MoreCollections.isEmpty(elements)) {
236            elements.forEach(this::add);
237        }
238        return this;
239    }
240
241    /**
242     * <p>
243     * add.
244     * </p>
245     *
246     * @param elements to be added
247     * @return the instance itself in order to use it in a fluent way.
248     *         <em>Caution:</em> with this call the return value of
249     *         {@link java.util.Collection#add(Object)} will be ignored.
250     */
251    public CollectionBuilder<E> add(Collection<E> elements) {
252        if (!MoreCollections.isEmpty(elements)) {
253            elements.forEach(this::add);
254        }
255        return this;
256    }
257
258    /**
259     * <p>
260     * add.
261     * </p>
262     *
263     * @param element to be added if present. <em>Caution</em>: passing an
264     *                {@link java.util.Optional} parameter is a not a good thing to
265     *                do, I know, but in this context it is quite convenient: Don't
266     *                do this at home
267     * @return the instance itself in order to use it in a fluent way.
268     *         <em>Caution:</em> with this call the return value of
269     *         {@link java.util.Collection#add(Object)} will be ignored.
270     */
271    public CollectionBuilder<E> add(Optional<E> element) {
272        element.ifPresent(this::add);
273        return this;
274    }
275
276    /**
277     * <p>
278     * add.
279     * </p>
280     *
281     * @param elements to be added
282     * @return the instance itself in order to use it in a fluent way.
283     *         <em>Caution:</em> with this call the return value of
284     *         {@link java.util.Collection#add(Object)} will be ignored.
285     */
286    public CollectionBuilder<E> add(Stream<E> elements) {
287        if (!MoreCollections.isEmpty(elements)) {
288            elements.forEach(this::add);
289        }
290        return this;
291    }
292
293    /**
294     * Sorts the contained Collection.
295     *
296     * @param comparator must not be null.
297     * @return the instance itself in order to use it in a fluent way.
298     */
299    public CollectionBuilder<E> sort(Comparator<? super E> comparator) {
300        if (collector instanceof List) {
301            Collections.sort((List<E>) collector, comparator);
302        } else {
303            List<E> sorter = new ArrayList<>(collector);
304            Collections.sort(sorter, comparator);
305            collector.clear();
306            collector.addAll(sorter);
307        }
308        return this;
309    }
310
311    /**
312     * <p>
313     * toMutableList.
314     * </p>
315     *
316     * @return a mutable {@link java.util.List} representation of the builders
317     *         content, the actual implementation is an {@link java.util.ArrayList}
318     */
319    public List<E> toMutableList() {
320        return new ArrayList<>(collector);
321    }
322
323    /**
324     * <p>
325     * toImmutableList.
326     * </p>
327     *
328     * @return an immutable {@link java.util.List} representation of the builders
329     *         content, the actual implementation calls
330     *         {@link java.util.Collections#unmodifiableList(List)}. The underlying
331     *         {@link java.util.Collection} will be copied by calling
332     *         {@link #toMutableList()}
333     */
334    public List<E> toImmutableList() {
335        return Collections.unmodifiableList(toMutableList());
336    }
337
338    /**
339     * <p>
340     * toMutableSet.
341     * </p>
342     *
343     * @return a mutable {@link java.util.Set} representation of the builders
344     *         content, the actual implementation is an {@link java.util.HashSet}
345     */
346    public Set<E> toMutableSet() {
347        return new HashSet<>(collector);
348    }
349
350    /**
351     * <p>
352     * toImmutableSet.
353     * </p>
354     *
355     * @return an immutable {@link java.util.Set} representation of the builders
356     *         content, the actual implementation calls
357     *         {@link java.util.Collections#unmodifiableList(List)}. The underlying
358     *         {@link java.util.Collection} will be copied by calling
359     *         {@link #toMutableSet()}
360     */
361    public Set<E> toImmutableSet() {
362        return Collections.unmodifiableSet(toMutableSet());
363    }
364
365    /**
366     * <p>
367     * toConcurrentList.
368     * </p>
369     *
370     * @return a concurrent mutable {@link java.util.List} representation of the
371     *         builders content, the actual implementation is an
372     *         {@link java.util.concurrent.CopyOnWriteArrayList}
373     */
374    public List<E> toConcurrentList() {
375        return new CopyOnWriteArrayList<>(collector);
376    }
377
378    /**
379     * <p>
380     * toConcurrentSet.
381     * </p>
382     *
383     * @return a concurrent mutable {@link java.util.Set} representation of the
384     *         builders content, the actual implementation is an
385     *         {@link java.util.concurrent.CopyOnWriteArraySet}
386     */
387    public Set<E> toConcurrentSet() {
388        return new CopyOnWriteArraySet<>(collector);
389    }
390
391    /**
392     * <p>
393     * toMutableNavigableSet.
394     * </p>
395     *
396     * @return a mutable {@link java.util.NavigableSet} representation of the
397     *         builders content, the actual implementation is an
398     *         {@link java.util.TreeSet}. The assumption is that the Actual type is
399     *         at least {@link java.lang.Comparable}
400     */
401    public NavigableSet<E> toMutableNavigableSet() {
402        return new TreeSet<>(collector);
403    }
404
405    /**
406     * <p>
407     * toImmutableNavigableSet.
408     * </p>
409     *
410     * @return an immutable {@link java.util.NavigableSet} representation of the
411     *         builders content, the actual implementation is an
412     *         {@link java.util.TreeSet} wrapped by
413     *         {@link java.util.Collections#unmodifiableNavigableSet(NavigableSet)}.
414     *         The assumption is that the Actual type is at least
415     *         {@link java.lang.Comparable}
416     */
417    public NavigableSet<E> toImmutableNavigableSet() {
418        return Collections.unmodifiableNavigableSet(toMutableNavigableSet());
419    }
420
421    /**
422     * <p>
423     * toConcurrentNavigableSet.
424     * </p>
425     *
426     * @return a mutable {@link java.util.NavigableSet} representation of the
427     *         builders content, the actual implementation is an
428     *         {@link java.util.concurrent.ConcurrentSkipListSet}. The assumption is
429     *         that the actual type is at least {@link java.lang.Comparable}
430     */
431    public NavigableSet<E> toConcurrentNavigableSet() {
432        return new ConcurrentSkipListSet<>(collector);
433    }
434
435    /**
436     * <p>
437     * toArray.
438     * </p>
439     *
440     * @param targetType identifying the concrete ArrayType
441     * @return an array representation of the builders content
442     */
443    @SuppressWarnings("unchecked")
444    public E[] toArray(Class<? super E> targetType) {
445        if (isEmpty()) {
446            return (E[]) Array.newInstance(targetType, 0);
447        }
448        var target = (E[]) Array.newInstance(targetType, size());
449        return collector.toArray(target);
450    }
451
452    /**
453     * Clears the elements in the collector
454     *
455     * @return the instance itself in order to use it in a fluent way.
456     */
457    public CollectionBuilder<E> clear() {
458        collector.clear();
459        return this;
460    }
461
462    /**
463     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
464     * copying the content of the given source <em>Caution:</em> The given source
465     * will be used as it is, there will be no filtering as defined within
466     * {@link #addNullValues(boolean)}
467     *
468     * @param source may be null
469     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
470     * @param <E> a E class
471     */
472    public static <E> CollectionBuilder<E> copyFrom(Iterable<? extends E> source) {
473        return new CollectionBuilder<>(mutableList(source));
474    }
475
476    /**
477     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
478     * copying the content of the given source <em>Caution:</em> The given source
479     * will be used as it is, there will be no filtering as defined within
480     * {@link #addNullValues(boolean)}
481     *
482     * @param source may be null
483     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
484     * @param <E> a E class
485     */
486    public static <E> CollectionBuilder<E> copyFrom(Iterator<? extends E> source) {
487        return new CollectionBuilder<>(mutableList(source));
488    }
489
490    /**
491     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
492     * copying the content of the given source <em>Caution:</em> The given source
493     * will be used as it is, there will be no filtering as defined within
494     * {@link #addNullValues(boolean)}
495     *
496     * @param source may be null
497     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
498     * @param <E> a E class
499     */
500    public static <E> CollectionBuilder<E> copyFrom(Collection<? extends E> source) {
501        return new CollectionBuilder<>(mutableList(source));
502    }
503
504    /**
505     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
506     * copying the content of the given source <em>Caution:</em> The given source
507     * will be used as it is, there will be no filtering as defined within
508     * {@link #addNullValues(boolean)}
509     *
510     * @param source may be null
511     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
512     * @param <E> a E class
513     */
514    public static <E> CollectionBuilder<E> copyFrom(Stream<? extends E> source) {
515        return new CollectionBuilder<>(mutableList(source));
516    }
517
518    /**
519     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
520     * copying the content of the given source <em>Caution:</em> The given source
521     * will be used as it is, there will be no filtering as defined within
522     * {@link #addNullValues(boolean)}
523     *
524     * @param source may be null
525     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
526     * @param <E> a E class
527     */
528    @SafeVarargs
529    public static <E> CollectionBuilder<E> copyFrom(E... source) {
530        return new CollectionBuilder<>(mutableList(source));
531    }
532
533    /**
534     * Creates an Instance of {@link de.cuioss.tools.collect.CollectionBuilder} by
535     * copying the content of the given source <em>Caution:</em> The given source
536     * will be used as it is, there will be no filtering as defined within
537     * {@link #addNullValues(boolean)}
538     *
539     * @param source may be null
540     * @return the newly created {@link de.cuioss.tools.collect.CollectionBuilder}
541     * @param <E> a E class
542     */
543    public static <E> CollectionBuilder<E> copyFrom(E source) {
544        return new CollectionBuilder<>(mutableList(source));
545    }
546
547}