001/*
002 * ============================================================================
003 * Copyright © 2002-2023 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 * Licensed to the public under the agreements of the GNU Lesser General Public
007 * License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *      http://www.gnu.org/licenses/lgpl.html
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, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017
018package org.tquadrat.foundation.util;
019
020import static java.lang.String.format;
021import static java.lang.System.arraycopy;
022import static java.util.Arrays.asList;
023import static java.util.Arrays.stream;
024import static java.util.Comparator.naturalOrder;
025import static java.util.stream.Collectors.joining;
026import static org.apiguardian.api.API.Status.STABLE;
027import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
028import static org.tquadrat.foundation.lang.Objects.nonNull;
029import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
030
031import java.lang.reflect.Array;
032import java.util.Comparator;
033import java.util.stream.IntStream;
034
035import org.apiguardian.api.API;
036import org.tquadrat.foundation.annotation.ClassVersion;
037import org.tquadrat.foundation.annotation.UtilityClass;
038import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
039import org.tquadrat.foundation.lang.Objects;
040
041/**
042 *  This class provides some utility functions that extends
043 *  {@link java.util.Arrays}
044 *  to some extent. All methods are static, no objects of this class are
045 *  allowed.
046 *
047 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
048 *  @version $Id: ArrayUtils.java 1060 2023-09-24 19:21:40Z tquadrat $
049 *  @since 0.0.5
050 *
051 *  @UMLGraph.link
052 */
053@SuppressWarnings( "ClassWithTooManyMethods" )
054@UtilityClass
055@ClassVersion( sourceVersion = "$Id: ArrayUtils.java 1060 2023-09-24 19:21:40Z tquadrat $" )
056public final class ArrayUtils
057{
058        /*-----------*\
059    ====** Constants **========================================================
060        \*-----------*/
061    /**
062     *  The illegal index message.
063     */
064    @API( status = STABLE, since = "0.0.5" )
065    public static final String MSG_IllegalIndex = "The starting index '%1$d' is invalid";
066
067    /**
068     *  "The message indicates that the given value is not an array."
069     */
070    @API( status = STABLE, since = "0.0.5" )
071    public static final String MSG_NoArray = "The argument is not an Array: %1$s";
072
073    /**
074     *  The message indicates that the given value set is empty.
075     */
076    @API( status = STABLE, since = "0.0.5" )
077    public static final String MSG_NoValues = "No values were provided";
078
079        /*--------------*\
080    ====** Constructors **=====================================================
081        \*--------------*/
082    /**
083     *  Prevent the creation of any object of this class.
084     */
085    private ArrayUtils() { throw new PrivateConstructorForStaticClassCalledError( ArrayUtils.class ); }
086
087        /*---------*\
088    ====** Methods **==========================================================
089        \*---------*/
090    /**
091     *  Translates the given array to an array of element type
092     *  {@link Object}.
093     *  Primitive types will be automatically converted into the respective
094     *  Wrapper types.<br>
095     *  <br>The array elements will not be copied.
096     *
097     *  @param  array   The array to translate.
098     *  @return The translated array.
099     */
100    @API( status = STABLE, since = "0.0.5" )
101    public static Object [] toObjectArray( final Object array )
102    {
103        if( !requireNonNullArgument( array, "array" ).getClass().isArray() )
104        {
105            throw new IllegalArgumentException( format( MSG_NoArray, array.getClass().getName() ) );
106        }
107
108        final var length = Array.getLength( array );
109        final var retValue = IntStream.range( 0, length ).mapToObj( i -> Array.get( array, i ) ).toArray();
110
111        //---* Done *----------------------------------------------------------
112        return retValue;
113    }   //  toObjectArray()
114
115    /**
116     *  This method checks if the array of objects that is provided as the
117     *  second parameter contains an object that is equal to that one provided
118     *  as the first parameter. The test is made using
119     *  {@link Comparable#compareTo(Object) compareTo()}.
120     *  For a test on object identity, using {@code ==}, use
121     *  {@link #isIn(Object,Object...) isIn()}.
122     *
123     *  @param <T>  The class for the objects.
124     *  @param  o   The object to look for.
125     *  @param  list    The list of object to look into.
126     *  @return {@code true} if the object is in the list,
127     *      {@code false} otherwise.
128     *  @throws NullPointerException At least one entry of {@code list} is
129     *      {@code null}.
130     */
131    @SafeVarargs
132    @API( status = STABLE, since = "0.0.5" )
133    public static <T> boolean isComparableIn( final Comparable<? super T> o, final T... list )
134    {
135        requireNonNullArgument( o, "o" );
136        final var retValue = stream( requireNonNullArgument( list, "list" ) ).anyMatch( e -> o.compareTo( e ) == 0 );
137
138        //---* Done *----------------------------------------------------------
139        return retValue;
140    }   //  isComparableIn()
141
142    /**
143     *  This method checks if the array of objects that is provided as the
144     *  second parameter contains an object that is equal to that one provided
145     *  as the first parameter. The test is made using
146     *  {@link Object#equals(Object) equals()}.
147     *  For a test on object identity, using {@code ==}, use
148     *  {@link #isIn(Object, Object...) isIn()}.
149     *
150     *  @param <T>  The class for the objects.
151     *  @param  o   The object to look for.
152     *  @param  list    The list of object to look into.
153     *  @return {@code true} if the object is in the list,
154     *      {@code false} otherwise.
155     */
156    @SafeVarargs
157    @API( status = STABLE, since = "0.0.5" )
158    public static <T> boolean isEqualIn( final T o, final T... list )
159    {
160        requireNonNullArgument( o, "o" );
161        final var retValue = asList( requireNonNullArgument( list, "list" ) ).contains( o );
162
163        //---* Done *----------------------------------------------------------
164        return retValue;
165    }   //  isEqualIn()
166
167    /**
168     *  This method checks if the given object is in the list of objects that
169     *  is provided as the second parameter. The test is made on object
170     *  identity, using {@code ==}. For a test with
171     *  {@link Object#equals(Object) equals()},
172     *  use
173     *  {@link #isEqualIn(Object, Object...) isEqualIn()}
174     *
175     *  @param <T>  The class for the objects.
176     *  @param  o   The object to look for; may be {@code null}.
177     *  @param  list    The list of object to look into.
178     *  @return {@code true} if the object is in the list,
179     *      {@code false} otherwise.
180     */
181    @SafeVarargs
182    @API( status = STABLE, since = "0.0.5" )
183    public static <T> boolean isIn( final T o, final T... list )
184    {
185        final var retValue = stream( requireNonNullArgument( list, "list" ) ).anyMatch( e -> o == e );
186
187        //---* Done *----------------------------------------------------------
188        return retValue;
189    }   //  isIn()
190
191    /**
192     *  This method checks if the given value is at least once in the list that
193     *  is provided as the second parameter.
194     *
195     *  @param  v   The value to look for.
196     *  @param  list    The list of values to look into.
197     *  @return {@code true} if the value is in the list,
198     *      {@code false} otherwise.
199     */
200    @SuppressWarnings( "ImplicitNumericConversion" )
201    @API( status = STABLE, since = "0.0.5" )
202    public static boolean isIn( final byte v, final byte... list )
203    {
204        requireNonNullArgument( list, "list" );
205        var retValue = false;
206        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
207        { /* Does nothing! */ }
208
209        //---* Done *----------------------------------------------------------
210        return retValue;
211    }   //  isIn()
212
213    /**
214     *  This method checks if the given value is at least once in the list that
215     *  is provided as the second parameter.
216     *
217     *  @param  v   The value to look for.
218     *  @param  list    The list of values to look into.
219     *  @return {@code true} if the value is in the list,
220     *      {@code false} otherwise.
221     */
222    @API( status = STABLE, since = "0.0.5" )
223    public static boolean isIn( final char v, final char... list )
224    {
225        requireNonNullArgument( list, "list" );
226        var retValue = false;
227        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
228        { /* Does nothing! */ }
229
230        //---* Done *----------------------------------------------------------
231        return retValue;
232    }   //  isIn()
233
234    /**
235     *  This method checks if the given value is at least once in the list that
236     *  is provided as the second parameter. The test is made on identity,
237     *  using {@code ==}; this means, due to the nature of {@code double}, that
238     *  values may not be found, although they <i>appear</i> to be equal.
239     *
240     *  @param  v   The value to look for.
241     *  @param  list    The list of values to look into.
242     *  @return {@code true} if the value is in the list,
243     *      {@code false} otherwise.
244     */
245    @SuppressWarnings( "FloatingPointEquality" )
246    @API( status = STABLE, since = "0.0.5" )
247    public static boolean isIn( final double v, final double... list )
248    {
249        requireNonNullArgument( list, "list" );
250        var retValue = false;
251        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
252        { /* Does nothing! */ }
253
254        //---* Done *----------------------------------------------------------
255        return retValue;
256    }   //  isIn()
257
258    /**
259     *  This method checks if the given value is at least once in the list that
260     *  is provided as the second parameter. The test is made on identity,
261     *  using {@code ==}; this means, due to the nature of {@code float}, that
262     *  values may not be found, although they <i>appear</i> to be equal.
263     *
264     *  @param  v   The value to look for.
265     *  @param  list    The list of values to look into.
266     *  @return {@code true} if the value is in the list,
267     *      {@code false} otherwise.
268     */
269    @SuppressWarnings( "FloatingPointEquality" )
270    @API( status = STABLE, since = "0.0.5" )
271    public static boolean isIn( final float v, final float... list )
272    {
273        requireNonNullArgument( list, "list" );
274        var retValue = false;
275        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
276        { /* Does nothing! */ }
277
278        //---* Done *----------------------------------------------------------
279        return retValue;
280    }   //  isIn()
281
282    /**
283     *  This method checks if the given value is at least once in the list that
284     *  is provided as the second parameter.
285     *
286     *  @param  v   The value to look for.
287     *  @param  list    The list of values to look into.
288     *  @return {@code true} if the value is in the list,
289     *      {@code false} otherwise.
290     */
291    @API( status = STABLE, since = "0.0.5" )
292    public static boolean isIn( final int v, final int... list )
293    {
294        requireNonNullArgument( list, "list" );
295        var retValue = false;
296        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
297        { /* Does nothing! */ }
298
299        //---* Done *----------------------------------------------------------
300        return retValue;
301    }   //  isIn()
302
303    /**
304     *  This method checks if the given value is at least once in the list that
305     *  is provided as the second parameter.
306     *
307     *  @param  v   The value to look for.
308     *  @param  list    The list of values to look into.
309     *  @return {@code true} if the value is in the list,
310     *      {@code false} otherwise.
311     */
312    @API( status = STABLE, since = "0.0.5" )
313    public static boolean isIn( final long v, final long... list )
314    {
315        requireNonNullArgument( list, "list" );
316        var retValue = false;
317        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
318        { /* Does nothing! */ }
319
320        //---* Done *----------------------------------------------------------
321        return retValue;
322    }   //  isIn()
323
324    /**
325     *  This method checks if the given value is at least once in the list that
326     *  is provided as the second parameter.
327     *
328     *  @param  v   The value to look for.
329     *  @param  list    The list of values to look into.
330     *  @return {@code true} if the value is in the list,
331     *      {@code false} otherwise.
332     */
333    @SuppressWarnings( "ImplicitNumericConversion" )
334    @API( status = STABLE, since = "0.0.5" )
335    public static boolean isIn( final short v, final short... list )
336    {
337        requireNonNullArgument( list, "list" );
338        var retValue = false;
339        for( var i = 0; (i < list.length) && !retValue; retValue = v == list [i++] )
340        { /* Does nothing! */ }
341
342        //---* Done *----------------------------------------------------------
343        return retValue;
344    }   //  isIn()
345
346    /**
347     *  Determines the greatest value in the given array, based on the given
348     *  {@link Comparator}.
349     *
350     *  @param <T>  The class for the objects.
351     *  @param  comparator  The comparator to use.
352     *  @param  list  The values.
353     *  @return The greatest value in the list.
354     */
355    @SafeVarargs
356    @API( status = STABLE, since = "0.1.0" )
357    public static <T> T max( final Comparator<? super T> comparator, final T... list )
358    {
359        requireNonNullArgument( comparator, "comparator" );
360        final var retValue = stream( requireNonNullArgument( list, "list" ) ).max( comparator ).orElse( null );
361
362        //---* Done *----------------------------------------------------------
363        return retValue;
364    }   //  max()
365
366    /**
367     *  Determines the greatest value in the given array.
368     *
369     *  @param <T>  The class for the objects.
370     *  @param  list  The values.
371     *  @return The greatest value in the list.
372     */
373    @SafeVarargs
374    @API( status = STABLE, since = "0.0.5" )
375    public static <T extends Comparable<T>> T max( final T... list )
376    {
377        final var retValue = stream( requireNonNullArgument( list, "list" ) ).max( naturalOrder() ).orElse( null );
378
379        //---* Done *----------------------------------------------------------
380        return retValue;
381    }   //  max()
382
383    /**
384     *  Determines the greatest value in the given array.
385     *
386     *  @param  list  The values.
387     *  @return The greatest value in the list.
388     */
389    @SuppressWarnings( "CharacterComparison" )
390    @API( status = STABLE, since = "0.0.5" )
391    public static char max( final char... list )
392    {
393        final var len = requireNonNullArgument( list, "list" ).length;
394        if( 0 == len )
395        {
396            throw new IllegalArgumentException( MSG_NoValues );
397        }
398        var retValue = list [0];
399        for( var i = 1; i < len; ++i )
400        {
401            if( list [i] > retValue )
402            {
403                retValue = list [i];
404            }
405        }
406
407        //---* Done *----------------------------------------------------------
408        return retValue;
409    }   //  max()
410
411    /**
412     *  Determines the greatest value in the given array.
413     *
414     *  @param  list  The values.
415     *  @return The greatest value in the list.
416     */
417    @API( status = STABLE, since = "0.0.5" )
418    public static double max( final double... list )
419    {
420        final var len = requireNonNullArgument( list, "list" ).length;
421        if( 0 == len )
422        {
423            throw new IllegalArgumentException( MSG_NoValues );
424        }
425        var retValue = list [0];
426        for( var i = 1; i < len; ++i )
427        {
428            if( list [i] > retValue )
429            {
430                retValue = list [i];
431            }
432        }
433
434        //---* Done *----------------------------------------------------------
435        return retValue;
436    }   //  max()
437
438    /**
439     *  Determines the greatest value in the given array.
440     *
441     *  @param  list  The values.
442     *  @return The greatest value in the list.
443     */
444    @API( status = STABLE, since = "0.0.5" )
445    public static int max( final int... list )
446    {
447        final var len = requireNonNullArgument( list, "list" ).length;
448        if( 0 == len )
449        {
450            throw new IllegalArgumentException( MSG_NoValues );
451        }
452        var retValue = list [0];
453        for( var i = 1; i < len; ++i )
454        {
455            if( list [i] > retValue )
456            {
457                retValue = list [i];
458            }
459        }
460
461        //---* Done *----------------------------------------------------------
462        return retValue;
463    }   //  max()
464
465    /**
466     *  Determines the greatest value in the given array.
467     *
468     *  @param  list  The values.
469     *  @return The greatest value in the list.
470     */
471    @API( status = STABLE, since = "0.0.5" )
472    public static long max( final long... list )
473    {
474        final var len = requireNonNullArgument( list, "list" ).length;
475        if( 0 == len )
476        {
477            throw new IllegalArgumentException( MSG_NoValues );
478        }
479        var retValue = list [0];
480        for( var i = 1; i < len; ++i )
481        {
482            if( list [i] > retValue )
483            {
484                retValue = list [i];
485            }
486        }
487
488        //---* Done *----------------------------------------------------------
489        return retValue;
490    }   //  max()
491
492    /**
493     *  Determines the smallest value in the given array, based on the given
494     *  {@link Comparator}.
495     *
496     *  @param <T>  The class for the objects.
497     *  @param  comparator  The comparator.
498     *  @param  list  The values.
499     *  @return The smallest value in the list.
500     */
501    @SafeVarargs
502    @API( status = STABLE, since = "0.0.5" )
503    public static <T> T min( final Comparator<? super T> comparator,  final T... list )
504    {
505        requireNonNullArgument( comparator, "comparator" );
506        final var retValue = stream( requireNonNullArgument( list, "list" ) ).min( comparator ).orElse( null );
507
508        //---* Done *----------------------------------------------------------
509        return retValue;
510    }   //  min()
511
512    /**
513     *  Determines the smallest value in the given array.
514     *
515     *  @param <T>  The class for the objects.
516     *  @param  list  The values.
517     *  @return The smallest value in the list.
518     */
519    @SafeVarargs
520    @API( status = STABLE, since = "0.0.5" )
521    public static <T extends Comparable<T>> T min( final T... list )
522    {
523        final var retValue = stream( requireNonNullArgument( list, "list" ) ).min( naturalOrder() ).orElse( null );
524
525        //---* Done *----------------------------------------------------------
526        return retValue;
527    }   //  min()
528
529    /**
530     *  Determines the smallest value in the given array.
531     *
532     *  @param  list  The values.
533     *  @return The smallest value in the list.
534     */
535    @SuppressWarnings( "CharacterComparison" )
536    @API( status = STABLE, since = "0.0.5" )
537    public static char min( final char... list )
538    {
539        final var len = requireNonNullArgument( list, "list" ).length;
540        if( 0 == len )
541        {
542            throw new IllegalArgumentException( MSG_NoValues );
543        }
544        var retValue = list [0];
545        for( var i = 1; i < len; ++i )
546        {
547            if( list [i] < retValue )
548            {
549                retValue = list [i];
550            }
551        }
552
553        //---* Done *----------------------------------------------------------
554        return retValue;
555    }   //  min()
556
557    /**
558     *  Determines the smallest value in the given array.
559     *
560     *  @param  list  The values.
561     *  @return The smallest value in the list.
562     */
563    @API( status = STABLE, since = "0.0.5" )
564    public static double min( final double... list )
565    {
566        final var len = requireNonNullArgument( list, "list" ).length;
567        if( 0 == len )
568        {
569            throw new IllegalArgumentException( MSG_NoValues );
570        }
571        var retValue = list [0];
572        for( var i = 1; i < len; ++i )
573        {
574            if( list [i] < retValue )
575            {
576                retValue = list [i];
577            }
578        }
579
580        //---* Done *----------------------------------------------------------
581        return retValue;
582    }   //  min()
583
584    /**
585     *  Determines the smallest value in the given array.
586     *
587     *  @param  list  The values.
588     *  @return The smallest value in the list.
589     */
590    @API( status = STABLE, since = "0.0.5" )
591    public static int min( final int... list )
592    {
593        final var len = requireNonNullArgument( list, "list" ).length;
594        if( 0 == len )
595        {
596            throw new IllegalArgumentException( MSG_NoValues );
597        }
598        var retValue = list [0];
599        for( var i = 1; i < len; ++i )
600        {
601            if( list [i] < retValue )
602            {
603                retValue = list [i];
604            }
605        }
606
607        //---* Done *----------------------------------------------------------
608        return retValue;
609    }   //  min()
610
611    /**
612     *  Determines the smallest value in the given array.
613     *
614     *  @param  list  The values.
615     *  @return The smallest value in the list.
616     */
617    @API( status = STABLE, since = "0.0.5" )
618    public static long min( final long... list )
619    {
620        final var len = requireNonNullArgument( list, "list" ).length;
621        if( 0 == len )
622        {
623            throw new IllegalArgumentException( MSG_NoValues );
624        }
625        var retValue = list [0];
626        for( var i = 1; i < len; ++i )
627        {
628            if( list [i] < retValue )
629            {
630                retValue = list [i];
631            }
632        }
633
634        //---* Done *----------------------------------------------------------
635        return retValue;
636    }   //  min()
637
638    /**
639     *  Modifies the given array to contain the elements in reverted order.
640     *
641     *  @param  <T> The type of the array elements.
642     *  @param  a The array to revert.
643     */
644    @SafeVarargs
645    @API( status = STABLE, since = "0.0.5" )
646    public static <T> void revert( final T... a )
647    {
648        final var len = requireNonNullArgument( a, "a" ).length;
649        if( len > 1 )
650        {
651            int pos;
652            for( var i = 0; i < (len / 2); ++i )
653            {
654                pos = len - 1 - i;
655                final var temp = a [i];
656                a [i] = a [pos];
657                a [pos] = temp;
658            }
659        }
660    }   //  revert()
661
662    /**
663     *  Returns a copy of the given array with the elements in reverted order.
664     *
665     *  @param  <T> The type of the array elements.
666     *  @param  a The array to revert.
667     *  @return The copy in reverted order.
668     */
669    @SafeVarargs
670    @API( status = STABLE, since = "0.0.5" )
671    public static <T> T [] revertCopy( final T... a )
672    {
673        final var retValue = requireNonNullArgument( a, "a" ).clone();
674        revert( retValue );
675
676        //---* Done *----------------------------------------------------------
677        return retValue;
678    }   //  revertCopy()
679
680    /**
681     *  Returns a part of the given array as a copy.<br>
682     *  <br>The semantics of this method are slightly different from that in
683     *  {@link java.util.Arrays}.
684     *
685     *  @param  <T> The type of the array elements.
686     *  @param  source  The source array.
687     *  @param  start   The start index.
688     *  @param  len The number of elements for the partial array.
689     *  @return The new array.
690     *
691     *  @see java.util.Arrays#copyOfRange(Object[], int, int)
692     */
693    @API( status = STABLE, since = "0.0.5" )
694    public static <T> T [] subArray( final T [] source, final int start, final int len )
695    {
696        if( start < 0 ) throw new IndexOutOfBoundsException( format( MSG_IllegalIndex, start ) );
697
698        final Class<?> sourceClass = requireNonNullArgument( source, "source" ).getClass();
699        if( !sourceClass.isArray() ) throw new IllegalArgumentException( format( MSG_NoArray, sourceClass.getName() ) );
700        final var size = Math.min( len, source.length - start );
701        if( size < 0 ) throw new IndexOutOfBoundsException( format( MSG_IllegalIndex, size ) );
702        @SuppressWarnings( "unchecked" )
703        final var retValue = (T []) Array.newInstance( sourceClass.getComponentType(), size );
704        arraycopy( source, start, retValue, 0, size );
705
706        //---* Done *----------------------------------------------------------
707        return retValue;
708    }   //  subArray()
709
710    /**
711     *  Returns a part of the given array as a copy, beginning with the given
712     *  start position to the end of the array.
713     *
714     *  @param  <T> The type of the array elements.
715     *  @param  source  The source array.
716     *  @param  start   The start index.
717     *  @return The new array.
718     */
719    @API( status = STABLE, since = "0.0.5" )
720    public static <T> T [] subArray( final T [] source, final int start )
721    {
722        final var retValue = subArray( source, start, Integer.MAX_VALUE );
723
724        //---* Done *----------------------------------------------------------
725        return retValue;
726    }   //  subArray()
727
728    /**
729     *  Renders the given array to a single string where the elements are
730     *  separated by the given character sequence.<br>
731     *  <br>This method allows to control the output a bit better than
732     *  {@link java.util.Arrays#toString(Object[])},
733     *  {@link java.util.Objects#toString(Object)},
734     *  or
735     *  {@link org.tquadrat.foundation.lang.Objects#toString(Object)}.
736     *  Especially it does not frame the output with those square brackets ...
737     *
738     *  @param  array   The input array.
739     *  @param  separator   The separator character sequence; if
740     *      {@code null}, the empty string is used.
741     *  @return The concatenated string.
742     */
743    @API( status = STABLE, since = "0.0.5" )
744    public static String toString( final Object [] array, final CharSequence separator )
745    {
746        var retValue = EMPTY_STRING;
747        if( array.length > 0 )
748        {
749            @SuppressWarnings( "LocalVariableNamingConvention" )
750            final var s = array.length > 1
751                ? nonNull( separator )
752                    ? separator.toString()
753                    : EMPTY_STRING
754                : null;
755            retValue = IntStream.range( 1, array.length )
756                .mapToObj( i -> s + Objects.toString( array [i] ) )
757                .collect( joining( "", Objects.toString( array [0] ), "" ) );
758        }
759
760        //---* Done *----------------------------------------------------------
761        return retValue;
762    }   //  toString()
763}
764//  class ArrayUtils
765
766/*
767 *    End of File
768 */