001/*
002 * ============================================================================
003 * Copyright © 2002-2026 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.System.getProperty;
021import static java.lang.System.out;
022import static java.nio.file.FileVisitResult.CONTINUE;
023import static java.nio.file.Files.delete;
024import static java.nio.file.Files.walkFileTree;
025import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
026import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
027import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
028import static java.util.Arrays.asList;
029import static org.apiguardian.api.API.Status.INTERNAL;
030import static org.apiguardian.api.API.Status.STABLE;
031import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_TEMPFOLDER;
032import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_USER_NAME;
033import static org.tquadrat.foundation.lang.Objects.isNull;
034import static org.tquadrat.foundation.lang.Objects.nonNull;
035import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
036import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
037import static org.tquadrat.foundation.util.StringUtils.splitString;
038
039import java.io.File;
040import java.io.IOException;
041import java.io.OutputStream;
042import java.io.PrintStream;
043import java.io.Reader;
044import java.nio.file.FileSystems;
045import java.nio.file.FileVisitResult;
046import java.nio.file.Files;
047import java.nio.file.Path;
048import java.nio.file.PathMatcher;
049import java.nio.file.SimpleFileVisitor;
050import java.nio.file.attribute.BasicFileAttributes;
051import java.nio.file.attribute.FileAttribute;
052import java.nio.file.attribute.PosixFilePermission;
053import java.nio.file.attribute.PosixFilePermissions;
054import java.security.MessageDigest;
055import java.security.NoSuchAlgorithmException;
056import java.util.ArrayList;
057import java.util.Collection;
058import java.util.EnumSet;
059import java.util.List;
060import java.util.Set;
061import java.util.StringJoiner;
062import java.util.regex.PatternSyntaxException;
063import java.util.zip.Adler32;
064import java.util.zip.CRC32;
065import java.util.zip.Checksum;
066
067import org.apiguardian.api.API;
068import org.tquadrat.foundation.annotation.ClassVersion;
069import org.tquadrat.foundation.annotation.UtilityClass;
070import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
071import org.tquadrat.foundation.lang.CommonConstants;
072
073/**
074 *  Some I/O, file, file system and network related helper and convenience
075 *  methods.
076 *
077 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
078 *  @version $Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $
079 *  @since 0.0.5
080 *
081 *  @UMLGraph.link
082 */
083@SuppressWarnings( "ClassWithTooManyMethods" )
084@ClassVersion( sourceVersion = "$Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $" )
085@UtilityClass
086public final class IOUtils
087{
088        /*---------------*\
089    ====** Inner Classes **====================================================
090        \*---------------*/
091    /**
092     *  This implementation of an
093     *  {@link Appendable}
094     *  just swallows any data that is written to it, like the {@code /dev/null}
095     *  device of a Unix or Linux machine, or {@code NUL:} on Windows. This class
096     *  might be useful if an {@code Appendable} is required from the API, but
097     *  not applicable from the application logic.
098     *
099     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
100     *  @version $Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $
101     *  @since 0.0.5
102     *
103     *  @UMLGraph.link
104     */
105    @SuppressWarnings( "PublicInnerClass" )
106    @ClassVersion( sourceVersion = "$Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $" )
107    @API( status = STABLE, since = "0.1.0" )
108    public static class NullAppendable implements Appendable
109    {
110            /*--------------*\
111        ====** Constructors **=====================================================
112            \*--------------*/
113        /**
114         *  Creates a new instance of {@code NullAppendable}.
115         */
116        public NullAppendable() { super(); }
117
118            /*---------*\
119        ====** Methods **==========================================================
120            \*---------*/
121        /**
122         *  Appends the specified character sequence to this Appendable. In fact,
123         *  it does nothing.
124         *
125         *  @param  csq {@inheritDoc}
126         */
127        @Override
128        public final Appendable append( final CharSequence csq ) throws IOException { return this; }
129
130        /**
131         *  Appends a subsequence of the specified character sequence to this
132         *  Appendable. In fact, it does nothing.
133         *
134         *  @param  csq {@inheritDoc}
135         */
136        @Override
137        public final Appendable append( final CharSequence csq, final int start, final int end ) throws IOException { return this; }
138
139        /**
140         *  Appends the specified character to this Appendable; in fact, it does
141         *  nothing.
142         *
143         *  @param  c   {@inheritDoc}
144         */
145        @Override
146        public final Appendable append( final char c ) throws IOException { return this; }
147    }
148    //  class NullAppendable
149
150    /**
151     *  The default file attributes.
152     *
153     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
154     *  @version $Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $
155     *  @since 0.0.6
156     *
157     *  @UMLGraph.link
158     */
159    @ClassVersion( sourceVersion = "$Id: IOUtils.java 1228 2026-05-04 12:21:25Z tquadrat $" )
160    @UtilityClass
161    private static final class PosixPermissions
162    {
163            /*------------------------*\
164        ====** Static Initialisations **=======================================
165            \*------------------------*/
166        /**
167         *  The default attributes for a temporary file.
168         *
169         *  @since 0.0.6
170         */
171        @API( status = INTERNAL, since = "0.0.6" )
172        static final FileAttribute<Set<PosixFilePermission>> tempFilePermissions;
173
174        /**
175         *  The default attributes for a temporary folder.
176         *
177         *  @since 0.0.6
178         */
179        @API( status = INTERNAL, since = "0.0.6" )
180        static final FileAttribute<Set<PosixFilePermission>> tempDirPermissions;
181
182        static
183        {
184            tempFilePermissions = PosixFilePermissions.asFileAttribute( EnumSet.of( OWNER_READ, OWNER_WRITE ) );
185            tempDirPermissions = PosixFilePermissions.asFileAttribute( EnumSet.of( OWNER_READ, OWNER_WRITE, OWNER_EXECUTE ) );
186        }
187
188            /*--------------*\
189        ====** Constructors **=================================================
190            \*--------------*/
191        /**
192         *  No instance allowed for this class!
193         */
194        private PosixPermissions() { throw new PrivateConstructorForStaticClassCalledError( PosixPermissions.class ); }
195    }
196    //  class PosixPermissions
197
198        /*-----------*\
199    ====** Constants **========================================================
200        \*-----------*/
201    /**
202     *  Some methods in this class need a buffer; the size of this buffer is
203     *  defined here: {@value}.
204     */
205    @API( status = STABLE, since = "0.0.5" )
206    public static final int DEFAULT_BUFFER_SIZE = 8192;
207
208    /**
209     *  The flag that indicates if the default file systems is POSIX compliant.
210     *
211     *  @since 0.0.6
212     */
213    @API( status = STABLE, since = "0.0.6" )
214    public static final boolean DEFAULT_FILESYSTEM_IS_POSIX_COMPLIANT = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
215
216        /*--------------*\
217    ====** Constructors **=====================================================
218        \*--------------*/
219    /**
220     *  No instance allowed for this class.
221     */
222    private IOUtils() { throw new PrivateConstructorForStaticClassCalledError( IOUtils.class ); }
223
224        /*---------*\
225    ====** Methods **==========================================================
226        \*---------*/
227    /**
228     *  Unconditionally closes an object instance of a class that implements
229     *  the interface
230     *  {@link AutoCloseable}.<br>
231     *  Equivalent to
232     *  {@link AutoCloseable#close()},
233     *  except any exceptions will be ignored. This is typically used in
234     *  {@code finally} blocks.<br>
235     *  <br>Even after the introduction of {@code try-with-resources} with
236     *  Java&nbsp;7, this method can be still helpful.
237     *
238     *  @param  closeable   The {@code AutoCloseable} instance to close, can be
239     *      {@code null} or already closed.
240     */
241    @API( status = STABLE, since = "0.0.5" )
242    public static final void closeQuietly( final AutoCloseable closeable )
243    {
244        try
245        {
246            if( nonNull( closeable ) ) closeable.close();
247        }
248        catch( final Exception ignored ) { /* Deliberately ignored! */ }
249    }   //  closeQuietly()
250
251    /**
252     *  <p>{@summary Creates a new directory by creating all nonexistent parent
253     *  directories first.} Unlike the
254     *  {@link #createDirectory(File,FileAttribute...)}
255     *  method, an exception is not thrown if the directory could not be
256     *  created because it already exists.</p>
257     *  <p>The {@code attributes} parameter is optional to set
258     *  {@linkplain FileAttribute file-attributes}
259     *  atomically when creating the non-existent directories. Each file
260     *  attribute is identified by its
261     *  {@linkplain FileAttribute#name name}.
262     *  If more than one attribute of the same name is included in the array
263     *  then all but the last occurrence is ignored.</p>
264     *  <p>If this method fails, then it may do so after creating some, but
265     *  not all, of the parent directories.</p>
266     *
267     *  @param  dir The directory to create.
268     *  @param  attributes  An optional list of file attributes to set
269     *      atomically when creating the directory.
270     *  @return The directory.
271     *  @throws UnsupportedOperationException   The attributes array contains
272     *      an attribute that cannot be set atomically when creating the
273     *      directory.
274     *  @throws java.nio.file.FileAlreadyExistsException    The {@code dir}
275     *      exists but is not a directory <i>(optional specific exception)</i>.
276     *  @throws IOException An I/O error occurred.
277     *  @throws SecurityException   In the case a security manager is
278     *      installed, the
279     *      {@link SecurityManager#checkWrite(String) checkWrite()}
280     *      method is invoked prior to attempting to create a directory and its
281     *      {@link SecurityManager#checkRead(String) checkRead()}
282     *      is invoked for each parent directory that is checked. If
283     *      {@code dir} is not an absolute path then its
284     *      {@link File#getAbsoluteFile()}
285     *      method may need to be invoked to get its absolute path. This may
286     *      invoke the security manager's
287     *      {@link SecurityManager#checkPropertyAccess(String) checkPropertyAccess()}
288     *      method to check access to the system property {@code user.dir}.
289     *
290     *  @see Files#createDirectories(Path, FileAttribute...)
291     *  @see File#mkdirs()
292     *
293     *  @since 0.0.6
294     */
295    @API( status = STABLE, since = "0.0.6" )
296    public static final File createDirectories( final File dir, final FileAttribute<?>... attributes ) throws IOException
297    {
298        final var path = Files.createDirectories( requireNonNullArgument( dir, "dir" ).toPath(), requireNonNullArgument( attributes, "attributes" ) );
299        final var retValue = path.toFile();
300
301        //---* Done *----------------------------------------------------------
302        return retValue;
303    }   //  createDirectories()
304
305    /**
306     *  <p>{@summary Creates a new directory.} The check for the existence of the file and
307     *  the creation of the directory if it does not exist are a single
308     *  operation that is atomic with respect to all other filesystem
309     *  activities that might affect the directory. The
310     *  {@link #createDirectories(File, FileAttribute...) createDirectories()}
311     *  method should be used where it is required to create all nonexistent
312     *  parent directories first.</p>
313     *  <p>The {@code attributes} parameter is optional to set
314     *  {@linkplain FileAttribute file-attributes}
315     *  atomically when creating the directory. Each attribute is identified by
316     *  its
317     *  {@linkplain FileAttribute#name name}.
318     *  If more than one attribute of the same name is included in the array
319     *  then all but the last occurrence is ignored.</p>
320     *
321     *  @param  dir The directory to create.
322     *  @param  attributes  An optional list of file attributes to set
323     *      atomically when creating the directory
324     *  @return The directory.
325     *  @throws UnsupportedOperationException   The attributes array contains
326     *      an attribute that cannot be set atomically when creating the
327     *      directory.
328     *  @throws java.nio.file.FileAlreadyExistsException    A directory could
329     *      not otherwise be created because a file of that name already exists
330     *      <i>(optional specific exception)</i>.
331     *  @throws IOException An I/O error occurred or the parent directory does
332     *      not exist.
333     *  @throws SecurityException   In the case a security manager is
334     *      installed, the
335     *      {@link SecurityManager#checkWrite(String) checkWrite()}
336     *      method is invoked to check write access to the new directory.
337     *
338     *  @since 0.0.6
339     */
340    @API( status = STABLE, since = "0.0.6" )
341    public static File createDirectory( final File dir, final FileAttribute<?>... attributes ) throws IOException
342    {
343        final var path = Files.createDirectory( requireNonNullArgument( dir, "dir" ).toPath(), requireNonNullArgument( attributes, "attributes" ) );
344        final var retValue = path.toFile();
345
346        //---* Done *----------------------------------------------------------
347        return retValue;
348    }   //  createDirectory()
349
350    /**
351     *  <p>{@summary Creates a directory named after the account name of the
352     *  current user (determined by the system property
353     *  {@value CommonConstants#PROPERTY_USER_NAME})
354     *  in the default {@code temp} folder, determined by the system property
355     *  {@value CommonConstants#PROPERTY_TEMPFOLDER}.}</p>
356     *  <p>The access rights are set for the current user only (for UNIX, it
357     *  would be 700).</p>
358     *  <p>The new directory will <i>not</i> be removed automatically after
359     *  program termination.</p>
360     *  <p>The class
361     *  {@link File}
362     *  provides a static method
363     *  {@link File#createTempFile(String, String) createTempFile()}
364     *  that creates a temporary file in the default temporary folder. As this
365     *  may be not a problem on a single-user Windows system with default
366     *  configuration, it will cause security problems on UNIX-like systems.
367     *  Therefore, it is recommended, to use
368     *  {@link File#createTempFile(String, String, File)}
369     *  instead, like this:</p>
370     *  <div class="source-container"><pre>final var tempFile = File.createTempFile( "PREFIX", "EXT", createTempDirectory() );</pre></div>
371     *  <p>This will guarantee that the temporary files cannot be read by
372     *  other users.</p>
373     *
374     *  @return The new temporary directory; it is guaranteed that the
375     *      directory exists after the call to this method returned.
376     *  @throws IOException Something has gone wrong.
377     *
378     *  @since 0.0.6
379     */
380    @API( status = STABLE, since = "0.0.6" )
381    public static final File createTempDirectory() throws IOException
382    {
383        //---* Create the file object for the temp directory *-----------------
384        final var tempDir = getSystemTempFolder();
385        final var userTempDir = tempDir.resolve( getProperty( PROPERTY_USER_NAME ) );
386
387        //---* Get the file attributes *---------------------------------------
388        @SuppressWarnings( "ZeroLengthArrayAllocation" )
389        final FileAttribute<?> [] attributes = DEFAULT_FILESYSTEM_IS_POSIX_COMPLIANT ? new FileAttribute [] {PosixPermissions.tempDirPermissions} : new FileAttribute [0];
390
391        //---* Create the directory *------------------------------------------
392        final var retValue = Files.createDirectories( userTempDir, attributes ).toFile();
393
394        //---* Done *----------------------------------------------------------
395        return retValue;
396    }   //  createTempDirectory()
397
398    /**
399     *  <p>{@summary Creates a new temporary directory in the specified
400     *  directory, using the given prefix to generate its name.}</p>
401     *  <p>The details as to how the name of the directory is constructed is
402     *  implementation dependent and therefore not specified. Where possible
403     *  the {@code prefix} is used to construct candidate names.</p>
404     *  <p>The {@code attributes} parameter is optional to set
405     *  {@linkplain FileAttribute file-attributes}
406     *  atomically when creating the directory. Each attribute is identified by
407     *  its
408     *  {@linkplain FileAttribute#name() name}.
409     *  If more than one attribute of the same name is included in the array
410     *  then all but the last occurrence is ignored.</p>
411     *
412     *  @param  dir The directory in which to create the temporary directory.
413     *  @param  prefix  The prefix string to be used in generating the
414     *      directory's name; may be {@code null}.
415     *  @param  attributes  An optional list of file attributes to set
416     *      atomically when creating the directory.
417     *  @return The path to the newly created directory that did not exist
418     *      before this method was invoked.
419     *  @throws IllegalArgumentException    The prefix cannot be used to
420     *      generate a candidate directory name.
421     *  @throws UnsupportedOperationException   The attributes array contains
422     *      an attribute that cannot be set atomically when creating the
423     *      directory.
424     *  @throws IOException An I/O error occurs or {@code dir} does not exist.
425     *  @throws SecurityException   In the case a security manager is
426     *      installed, the
427     *      {@link SecurityManager#checkWrite(String) checkWrite()}
428     *      method is invoked to check write access when creating the
429     *      directory.
430     *
431     *  @see Files#createTempDirectory(Path,String,FileAttribute...)
432     *
433     *  @since 0.0.6
434     */
435    @API( status = STABLE, since = "0.0.6" )
436    public static Path createTempDirectory( final File dir, final String prefix, final FileAttribute<?>... attributes ) throws IOException
437    {
438        return Files.createTempDirectory( requireNonNullArgument( dir, "dir" ).toPath(), prefix, requireNonNullArgument( attributes, "attributes" ) );
439    }   //  createTempDirectory()
440
441    /**
442     *  <p>{@summary Creates a new directory in the default temporary-file
443     *  directory, using the given prefix to generate its name.}</p>
444     *  <p>This method works in exactly the manner specified by
445     *  {@link #createTempDirectory(File,String,FileAttribute[])}
446     *  method for the case that the {@code dir} parameter is the
447     *  temporary-file directory.</p>
448     *
449     *  @param  prefix  The prefix string to be used in generating the
450     *      directory's name; may be {@code null}.
451     *  @param  attributes  An optional list of file attributes to set
452     *      atomically when creating the directory.
453     *  @return The
454     *      {@link File}
455     *      instance to the newly created directory that did not exist before
456     *      this method was invoked.
457     *  @throws IllegalArgumentException    The prefix cannot be used to
458     *      generate a candidate directory name.
459     *  @throws UnsupportedOperationException   The {@code attributes} array
460     *      contains an attribute that cannot be set atomically when creating
461     *      the directory.
462     *  @throws IOException An I/O error occurred or the temporary-file
463     *      directory does not exist.
464     *  @throws SecurityException   In the case a security manager is
465     *      installed, the
466     *      {@link SecurityManager#checkWrite(String) checkWrite()}
467     *      method is invoked to check write access when creating the
468     *      directory.
469     *
470     *  @see Files#createTempDirectory(String, FileAttribute...)
471     *
472     *  @since 0.0.6
473     */
474    @API( status = STABLE, since = "0.0.6" )
475    public static File createTempDirectory( final String prefix, final FileAttribute<?>... attributes ) throws IOException
476    {
477        final var path = Files.createTempDirectory( prefix, requireNonNullArgument( attributes, "attributes" ) );
478        final var retValue = path.toFile();
479
480        //---* Done *----------------------------------------------------------
481        return retValue;
482    }   //  createTempDirectory()
483
484    /**
485     *  <p>{@summary Deletes the folder (or file) that is determined by the
486     *  given
487     *  {@link Path}
488     *  instance.} If the argument denotes a directory, the method will remove
489     *  its contents first, recursively.</p>
490     *
491     *  @param  folder  The folder to remove; despite the name of the argument
492     *      and the method, this can be also a plain file.
493     *  @throws IOException A problem occurred when deleting the {@code Path}.
494     */
495    public static final void deleteFolder( final Path folder ) throws IOException
496    {
497        //noinspection AnonymousInnerClass,OverlyComplexAnonymousInnerClass
498        walkFileTree( requireNonNullArgument( folder, "folder" ), new SimpleFileVisitor<>()
499        {
500            /**
501             * {@inheritDoc}
502             */
503            @Override
504            public final FileVisitResult postVisitDirectory( final Path directory, final IOException exception ) throws IOException
505            {
506                try
507                {
508                    delete( directory );
509                }
510                catch( final IOException e )
511                {
512                    if( nonNull( exception ) ) e.addSuppressed( exception );
513                    throw e;
514                }
515
516                //---* Done *--------------------------------------------------
517                return CONTINUE;
518            }   //  postVisitDirectory()
519
520            /**
521             * {@inheritDoc}
522             */
523            @Override
524            public final FileVisitResult visitFile( final Path file, final BasicFileAttributes attributes ) throws IOException
525            {
526                delete( file );
527
528                //---* Done *--------------------------------------------------
529                return CONTINUE;
530            }   //  visitFile()
531        } );
532    }   //  deleteFolder()
533
534    /**
535     *  <p>{@summary Deletes the folder (or file) that is determined by the
536     *  given
537     *  {@link File}.
538     *  instance.} If the argument denotes a directory, the method will remove
539     *  its contents first, recursively.</p>
540     *  <p>Calls
541     *  {@link #deleteFolder(Path)}
542     *  internally.</p>
543     *
544     *  @param  folder  The folder to remove; despite the name of the argument
545     *      and the method, this can be also a plain file.
546     *  @throws IOException A problem occurred when deleting the {@code Path}.
547     */
548    public static final void deleteFolder( final File folder ) throws IOException
549    {
550        deleteFolder( requireNonNullArgument( folder, "folder" ).toPath() );
551    }   //  deleteFolder()
552
553    /**
554     *  <p>{@summary Calculates the check sum for the given file, using the
555     *  algorithm with the given name.}</p>
556     *  <p>If the name is one of</p>
557     *  <ul>
558     *      <li>CRC32</li>
559     *      <li>Adler32</li>
560     *  </ul>
561     *  <p>the method uses
562     *  {@link java.util.zip.CRC32}
563     *  or
564     *  {@link java.util.zip.Adler32}
565     *  for the calculation, any other name is taken as the name for a
566     *  {@linkplain MessageDigest};
567     *  all JVMs know</p>
568     *  <ul>
569     *      <li>MD5</li>
570     *      <li>SHA1</li>
571     *  </ul>
572     *  <p>others can be added by installing additional
573     *  {@linkplain java.security.Provider security providers}.</p>
574     *  <p>This method calls
575     *  {@link #determineCheckSum(Path, String)}
576     *  internally.</p>
577     *
578     *  @param  file    The file to process.
579     *  @param  algorithm   The name for the algorithm to use for the check sum
580     *      calculation.
581     *  @return The check sum as a hex string.
582     *  @throws IOException Problems to process the file.
583     *  @throws NoSuchAlgorithmException    The provided algorithm does not
584     *      exist or the provider for it is not installed properly.
585     */
586    @API( status = STABLE, since = "0.0.5" )
587    public static final String determineCheckSum( final File file, final String algorithm ) throws IOException, NoSuchAlgorithmException
588    {
589        final var retValue = determineCheckSum( requireNonNullArgument( file, "file" ).toPath(), algorithm );
590
591        //---* Done *----------------------------------------------------------
592        return retValue;
593    }   //  determineCheckSum()
594
595    /**
596     *  <p>{@summary Calculates the check sum for the given file, using the
597     *  algorithm with the given name.}</p>
598     *  <p>If the name is one of</p>
599     *  <ul>
600     *      <li>CRC32</li>
601     *      <li>Adler32</li>
602     *  </ul>
603     *  <p>the method uses
604     *  {@link java.util.zip.CRC32}
605     *  or
606     *  {@link java.util.zip.Adler32}
607     *  for the calculation, any other name is taken as the name for a
608     *  {@linkplain MessageDigest};
609     *  all JVMs know</p>
610     *  <ul>
611     *      <li>MD5</li>
612     *      <li>SHA1</li>
613     *  </ul>
614     *  <p>others can be added by installing additional
615     *  {@linkplain java.security.Provider security providers}.</p>
616     *
617     *  @param  file    The file to process.
618     *  @param  algorithm   The name for the algorithm to use for the check sum
619     *      calculation.
620     *  @return The check sum as a hex string.
621     *  @throws IOException Problems to process the file.
622     *  @throws NoSuchAlgorithmException    The provided algorithm does not
623     *      exist or the provider for it is not installed properly.
624     */
625    @API( status = STABLE, since = "0.0.5" )
626    public static final String determineCheckSum( final Path file, final String algorithm ) throws IOException, NoSuchAlgorithmException
627    {
628        final var retValue = switch( requireNotEmptyArgument( algorithm, "algorithm" ) )
629        {
630            case "Adler32" -> Hash.create( file, new Adler32() ).toString();
631            case "CRC32" -> Hash.create( file, new CRC32() ).toString();
632            default -> Hash.create( file, MessageDigest.getInstance( algorithm ) ).toString();
633        };
634
635        //---* Done *----------------------------------------------------------
636        return retValue;
637    }   //  determineCheckSum()
638
639    /**
640     *  Calculates the check sum for the given file, using provided check sum
641     *  algorithm implementation.
642     *
643     *  @param  file    The file to process.
644     *  @param  algorithm   The check sum algorithm to use for the check sum
645     *      calculation.
646     *  @return The check sum.
647     *  @throws IOException Problems to process the file.
648     */
649    @API( status = STABLE, since = "0.0.5" )
650    public static final long determineCheckSum( final File file, final Checksum algorithm ) throws IOException
651    {
652        final var retValue = determineCheckSum( requireNonNullArgument( file, "file" ).toPath(), algorithm );
653
654        //---* Done *----------------------------------------------------------
655        return retValue;
656    }   //  determineCheckSum()
657
658    /**
659     *  Calculates the check sum for the given file, using provided check sum
660     *  algorithm implementation.
661     *
662     *  @param  file    The file to process.
663     *  @param  algorithm   The check sum algorithm to use for the check sum
664     *      calculation.
665     *  @return The check sum.
666     *  @throws IOException Problems to process the file.
667     */
668    @API( status = STABLE, since = "0.0.5" )
669    public static final long determineCheckSum( final Path file, final Checksum algorithm ) throws IOException
670    {
671        final var retValue = Hash.create( file, algorithm )
672            .number()
673            .longValue();
674
675        //---* Done *----------------------------------------------------------
676        return retValue;
677    }   //  determineCheckSum()
678
679    /**
680     *  Calculates the check sum for the given file, using the provided
681     *  {@link MessageDigest}.
682     *
683     *  @param  file    The file to process.
684     *  @param  algorithm   The {@code MessageDigest} to use for the check sum
685     *      calculation.
686     *  @return The check sum as a byte array.
687     *  @throws IOException Problems to process the file.
688     */
689    @API( status = STABLE, since = "0.0.5" )
690    public static final byte [] determineCheckSum( final File file, final MessageDigest algorithm ) throws IOException
691    {
692        final var retValue = determineCheckSum( requireNonNullArgument( file, "file" ).toPath(), algorithm );
693
694        //---* Done *----------------------------------------------------------
695        return retValue;
696    }   //  determineCheckSum()
697
698    /**
699     *  Calculates the check sum for the given file, using the provided
700     *  {@link MessageDigest}.
701     *
702     *  @param  file    The file to process.
703     *  @param  algorithm   The {@code MessageDigest} to use for the check sum
704     *      calculation.
705     *  @return The check sum as a byte array.
706     *  @throws IOException Problems to process the file.
707     */
708    @API( status = STABLE, since = "0.0.5" )
709    public static final byte [] determineCheckSum( final Path file, final MessageDigest algorithm ) throws IOException
710    {
711        final var retValue = Hash.create( file, algorithm ).bytes();
712
713        //---* Done *----------------------------------------------------------
714        return retValue;
715    }   //  determineCheckSum()
716
717    /**
718     *  Returns an
719     *  {@link Appendable}
720     *  that just swallows any data that is written to it, like the
721     *  {@code /dev/null} device of a Unix or Linux machine, or {@code NUL:}
722     *  on Windows.
723     *
724     *  @return A null appendable.
725     *
726     *  @see NullAppendable
727     */
728    @API( status = STABLE, since = "0.0.5" )
729    public static final Appendable getNullAppendable() { return new NullAppendable(); }
730
731    /**
732     *  Returns the
733     *  {@link Path}
734     *  object that represents the system's default temporary folder as
735     *  specified in
736     *  {@value CommonConstants#PROPERTY_TEMPFOLDER}.
737     *
738     *  @return The system temp folder.
739     *
740     *  @since 0.4.0
741     */
742    @API( status = STABLE, since = "0.4.0" )
743    public static final Path getSystemTempFolder() { return Path.of( getProperty( PROPERTY_TEMPFOLDER ) ); }
744
745    /**
746     *  <p>{@summary Returns a
747     *  {@link PathMatcher}
748     *  for the
749     *  {@linkplain FileSystems#getDefault() default filesystem}
750     *  that performs match operations on the String representation of
751     *  {@link Path}
752     *  objects by interpreting a given pattern.}</p>
753     *  <p>The {@code syntaxAndPattern} parameter identifies the syntax and the
754     *  pattern and takes the form:</p>
755     *  <blockquote><pre>
756     *  [<i>syntax</i><b>:</b>]<i>pattern</i>
757     *  </pre></blockquote>
758     *  <p>where <i>syntax</i> is the non-empty name of the syntax, <i>pattern</i>
759     *  is a possibly-empty pattern string, and {@code ':'} stands for
760     *  itself.</p>
761     *  <p>A
762     *  {@link java.nio.file.FileSystem FileSystem}
763     *  implementation supports the &quot;{@code glob}&quot; and
764     *  &quot;{@code regex}&quot; syntaxes, and may support others. The value
765     *  of the syntax component is compared without regard to case. If no
766     *  syntax is provided explicitly, &quot;{@code glob}&quot; is assumed.</p>
767     *  <p>When the syntax is &quot;{@code glob}&quot; then the {@code String}
768     *  representation of the path is matched using a limited pattern language
769     *  that resembles regular expressions but with a simpler syntax. For
770     *  example:</p>
771     *  <table class="striped"
772     *         style="text-align:left; margin-left:2em">
773     *      <caption style="display:none">Pattern Language</caption>
774     *      <thead>
775     *          <tr>
776     *              <th scope="col">Example</th>
777     *              <th scope="col">Description</th>
778     *          </tr>
779     *      </thead>
780     *      <tbody>
781     *          <tr>
782     *              <th scope="row">{@code *.java}</th>
783     *              <td>Matches a path that represents a file name ending in
784     *              {@code .java}</td>
785     *          </tr>
786     *          <tr>
787     *              <th scope="row">{@code *.*}</th>
788     *              <td>Matches file names containing a dot</td>
789     *          </tr>
790     *          <tr>
791     *              <th scope="row">{@code *.{java,class}}</th>
792     *              <td>Matches file names ending with {@code .java} or
793     *              {@code .class}</td>
794     *          </tr>
795     *          <tr>
796     *              <th scope="row">{@code foo.?}</th>
797     *              <td>Matches file names starting with {@code foo.} and a
798     *              single character extension</td>
799     *          </tr>
800     *          <tr>
801     *              <th scope="row"><code>&#47;home&#47;*&#47;*</code>
802     *              <td>Matches <code>&#47;home&#47;gus&#47;data</code> on
803     *              UNIX platforms</td>
804     *          </tr>
805     *          <tr>
806     *              <th scope="row"><code>&#47;home&#47;**</code>
807     *              <td>Matches <code>&#47;home&#47;gus</code> and
808     *              <code>&#47;home&#47;gus&#47;data</code> on UNIX
809     *              platforms</td>
810     *          </tr>
811     *          <tr>
812     *              <th scope="row"><code>C:&#92;&#92;*</code>
813     *              <td>Matches <code>C:&#92;foo</code> and
814     *              <code>C:&#92;bar</code> on the Windows platform (note that
815     *              the backslash is escaped; as a string literal in the Java
816     *              Language the pattern would be
817     *              <code>"C:&#92;&#92;&#92;&#92;*"</code>)</td>
818     *          </tr>
819     *      </tbody>
820     *  </table>
821     *  <p>The following rules are used to interpret glob patterns:</p>
822     *  <ul>
823     *      <li><p>The {@code *} character matches zero or more
824     *      {@link Character characters}
825     *      of a
826     *      {@link Path#getName(int) name}
827     *      component without crossing directory boundaries.</p></li>
828     *      <li><p>The {@code **} characters matches zero or more
829     *      {@link Character characters}
830     *      crossing directory boundaries.</p></li>
831     *      <li><p>The {@code ?} character matches exactly one character of a
832     *      name component.</p></li>
833     *      <li><p>The backslash character ({@code \}) is used to escape
834     *      characters that would otherwise be interpreted as special
835     *      characters. The expression &quot;{@code \\}&quot; matches a single
836     *      backslash and &quot;<code>\{</code>&quot; matches a left brace for
837     *      example.</p></li>
838     *      <li><p>The {@code [ ]} characters are a <i>bracket expression</i>
839     *      that match a single character of a name component out of a set of
840     *      characters.</p>
841     *      <p>For example, {@code [abc]} matches {@code "a"}, {@code "b"},
842     *      or {@code "c"}.</p>
843     *      <p>The hyphen ({@code -}) may be used to specify a range so
844     *      {@code [a-z]} specifies a range that matches from {@code "a"} to
845     *      {@code "z"} (inclusive).</p>
846     *      <p>These forms can be mixed so [abce-g] matches {@code "a"}, {@code "b"},
847     *      {@code "c"}, {@code "e"}, {@code "f"} or {@code "g"}. If the
848     *      character after the {@code [} is a {@code !} then it is used for
849     *      negation so {@code [!a-c]} matches any character <i>except</i>
850     *      {@code "a"}, {@code "b"}, or {@code "c"}.</p>
851     *      <p>Within a bracket expression the {@code *}, {@code ?} and
852     *      {@code \} characters match themselves. The ({@code -}) character
853     *      matches itself if it is the first character within the brackets, or
854     *      the first character after the {@code !} if negating.</p></li>
855     *      <li><p>The {@code { }} characters are a group of subpatterns, where
856     *      the group matches if any subpattern in the group matches. The
857     *      {@code ","} character is used to separate the subpatterns. Groups
858     *      cannot be nested.</p></li>
859     *      <li><p> Leading period<code>&#47;</code>dot characters in file name
860     *      are treated as regular characters in match operations. For example,
861     *      the {@code "*"} glob pattern matches file name {@code ".login"}.
862     *      The
863     *      {@link Files#isHidden}
864     *      method may be used to test whether a file is considered
865     *      hidden.</p></li>
866     *      <li><p>All other characters match themselves in an implementation
867     *      dependent manner. This includes characters representing any
868     *      {@linkplain java.nio.file.FileSystem#getSeparator name-separators}.</p></li>
869     *      <li><p>The matching of
870     *      {@link Path#getRoot root}
871     *      components is highly implementation-dependent and is not
872     *      specified.</p></li>
873     *  </ul>
874     *  <p>When the syntax is "{@code regex}" then the pattern component is a
875     *  regular expression as defined by the
876     *  {@link java.util.regex.Pattern}
877     *  class.</p>
878     *  <p>For both the {@code glob} and {@code regex} syntaxes, the matching
879     *  details, such as whether the matching is case-sensitive, are
880     *  implementation-dependent and therefore not specified.</p>
881     *
882     *  @param  syntaxAndPattern    The syntax and pattern.
883     *  @return A path matcher that may be used to match paths against the
884     *      pattern.
885     *  @throws PatternSyntaxException  The given pattern is invalid.
886     *  @throws UnsupportedOperationException   The pattern syntax is not known
887     *      to the implementation.
888     *
889     *  @see Files#newDirectoryStream(Path,String)
890     *
891     *  @since 0.25.3
892     */
893    @API( status = STABLE, since = "0.25.3" )
894    public static final PathMatcher getPathMatcher( final CharSequence syntaxAndPattern )
895    {
896        final var parts = splitString( requireNonNullArgument( syntaxAndPattern, "syntaxAndPattern" ), ':' );
897        final var fileSystem = FileSystems.getDefault();
898        final var effectiveSyntaxAndPattern = new StringJoiner( ":" );
899        if( parts.length == 1 ) effectiveSyntaxAndPattern.add( "glob" );
900        for( final var p : parts ) effectiveSyntaxAndPattern.add( p );
901        final var retValue = fileSystem.getPathMatcher( effectiveSyntaxAndPattern.toString() );
902
903        //---* Done *----------------------------------------------------------
904        return retValue;
905    }   //  getPathMatcher()
906
907    /**
908     *  <p>{@summary Returns
909     *  {@link System#out}
910     *  with a non-functional
911     *  {@link OutputStream#close()}
912     *  method.}</p>
913     *  <p>Assume the following scenario:</p>
914     *  <div class="source-container"><pre>&hellip;
915     *  Optional&lt;File&gt; outputFile = &hellip;
916     *  &hellip;
917     *  PrintStream outputStream = outputFile.isPresent() ? new PrintStream( new FileOutputStream( outputFile.get() ) ) : IOUtils.getUncloseableOut();
918     *  try( outputStream )
919     *  {
920     *      &#47;**
921     *       * Print something ...
922     *       *&#47;
923     *       &hellip;
924     *  }
925     *  &hellip;</pre></div>
926     *  <p>The output stream will be close at the end of the {@code try} block;
927     *  this is desired in case of a
928     *  {@link java.io.FileOutputStream FileOutputStream},
929     *  but totally not wanted if the output stream is {@code System.out}.</p>
930     *
931     *  @return {@code System.out} without the {@code close()} method.
932     */
933    @SuppressWarnings( "ImplicitDefaultCharsetUsage" )
934    @API( status = STABLE, since = "0.0.7" )
935    public static final PrintStream getUncloseableOut()
936    {
937        @SuppressWarnings( {"AnonymousInnerClass", "UseOfSystemOutOrSystemErr"} )
938        final var retValue = new PrintStream( out )
939        {
940            /**
941             *  {@inheritDoc}
942             */
943            @Override
944            public final void close() { /* Does nothing */ }
945        };
946
947        //---* Done *----------------------------------------------------------
948        return retValue;
949    }   //  getUncloseableOut()
950
951    /**
952     *  <p>{@summary Reads the complete content of the provided
953     *  {@link Reader}
954     *  into a
955     *  {@link String}.}</p>
956     *  <p>Obviously this method is feasible only for files with a limited
957     *  size.</p>
958     *
959     *  @param  reader  The {@code Reader} instance.
960     *  @return The content of the provided {@code Reader}.
961     *  @throws IOException Problems on reading from the {@code Reader}.
962     */
963    @API( status = STABLE, since = "0.0.5" )
964    public static final String loadToString( final Reader reader ) throws IOException
965    {
966        final var builder = new StringBuilder( DEFAULT_BUFFER_SIZE );
967        final var buffer = new char [DEFAULT_BUFFER_SIZE];
968        var bytesRead = requireNonNullArgument( reader, "reader" ).read( buffer );
969        while( bytesRead > 0 )
970        {
971            builder.append( buffer, 0, bytesRead );
972            bytesRead = reader.read( buffer );
973        }
974        final var retValue = builder.toString();
975
976        //---* Done *----------------------------------------------------------
977        return retValue;
978    }   //  loadToString()
979
980    /**
981     *  <p>{@summary Parses a list of file name patterns to a list of
982     *  {@link PathMatcher}
983     *  instances.}</p>
984     *  <p>The syntax for the patterns is described in the documentation for
985     *  {@link #getPathMatcher(CharSequence) getPathMatcher()}.</p>
986     *
987     *  @param  patterns    The list of patterns.
988     *  @return  The unmodifiable list of
989     *      {@link PathMatcher}
990     *      instances.
991     */
992    public static final Collection<PathMatcher> parseFilePatterns( final CharSequence... patterns )
993    {
994        final var retValue = parseFilePatterns( asList( requireNonNullArgument( patterns, "patterns" ) ) );
995
996        //---* Done *----------------------------------------------------------
997        return retValue;
998    }   //  parseFilePatterns()
999
1000    /**
1001     *  <p>{@summary Parses a list of file name patterns to a list of
1002     *  {@link PathMatcher}
1003     *  instances.}</p>
1004     *  <p>The syntax for the patterns is described in the documentation for
1005     *  {@link #getPathMatcher(CharSequence) getPathMatcher()}.</p>
1006     *
1007     *  @param  patterns    The list of patterns.
1008     *  @return  The unmodifiable list of
1009     *      {@link PathMatcher}
1010     *      instances.
1011     */
1012    public static final Collection<PathMatcher> parseFilePatterns( final Collection<? extends CharSequence> patterns )
1013    {
1014        final Collection<PathMatcher> buffer = new ArrayList<>();
1015        for( final var pattern : requireNonNullArgument( patterns, "patterns" ) )
1016        {
1017            if( isNull( pattern ) ) throw new IllegalArgumentException( "A pattern is null" );
1018            try
1019            {
1020                final var matcher = getPathMatcher( pattern.toString() );
1021                buffer.add( matcher );
1022            }
1023            catch( final PatternSyntaxException | UnsupportedOperationException e )
1024            {
1025                throw new IllegalArgumentException( "Invalid Pattern: %s".formatted( pattern ), e );
1026            }
1027        }
1028
1029        final var retValue = List.copyOf( buffer );
1030
1031        //---* Done *----------------------------------------------------------
1032        return retValue;
1033    }   //  parseFilePatterns()
1034}
1035//  class IOUtils
1036
1037/*
1038 *  End of File
1039 */