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 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 "{@code glob}" and 764 * "{@code regex}" 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, "{@code glob}" is assumed.</p> 767 * <p>When the syntax is "{@code glob}" 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>/home/*/*</code> 802 * <td>Matches <code>/home/gus/data</code> on 803 * UNIX platforms</td> 804 * </tr> 805 * <tr> 806 * <th scope="row"><code>/home/**</code> 807 * <td>Matches <code>/home/gus</code> and 808 * <code>/home/gus/data</code> on UNIX 809 * platforms</td> 810 * </tr> 811 * <tr> 812 * <th scope="row"><code>C:\\*</code> 813 * <td>Matches <code>C:\foo</code> and 814 * <code>C:\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:\\\\*"</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 "{@code \\}" matches a single 836 * backslash and "<code>\{</code>" 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>/</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>… 915 * Optional<File> outputFile = … 916 * … 917 * PrintStream outputStream = outputFile.isPresent() ? new PrintStream( new FileOutputStream( outputFile.get() ) ) : IOUtils.getUncloseableOut(); 918 * try( outputStream ) 919 * { 920 * /** 921 * * Print something ... 922 * */ 923 * … 924 * } 925 * …</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 */