All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.evitadb.utils.FileUtils Maven / Gradle / Ivy

There is a newer version: 2024.9.3
Show newest version
/*
 *
 *                         _ _        ____  ____
 *               _____   _(_) |_ __ _|  _ \| __ )
 *              / _ \ \ / / | __/ _` | | | |  _ \
 *             |  __/\ V /| | || (_| | |_| | |_) |
 *              \___| \_/ |_|\__\__,_|____/|____/
 *
 *   Copyright (c) 2023-2024
 *
 *   Licensed under the Business Source License, Version 1.1 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   https://github.com/FgForrest/evitaDB/blob/main/LICENSE
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package io.evitadb.utils;

import io.evitadb.exception.UnexpectedIOException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * FileUtils contains various utility methods for work with file system.
 *
 * We know these functions are available in Apache Commons, but we try to keep our transitive dependencies as low as
 * possible, so we rather went through duplication of the code.
 *
 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils {
	private static final Path[] EMPTY_PATHS = new Path[0];

	/**
	 * Returns list of folders in Evita directory. Each folder is considered to be Evita catalog - name of the folder
	 * must be the name of catalog itself.
	 */
	@Nonnull
	public static Path[] listDirectories(@Nonnull Path directory) {
		if (directory.toFile().exists()) {
			try (final DirectoryStream dirStream = Files.newDirectoryStream(directory, entry -> entry.toFile().isDirectory())) {
				return StreamSupport.stream(dirStream.spliterator(), false).toArray(Path[]::new);
			} catch (IOException ex) {
				throw new UnexpectedIOException(
					"Failed to read directory: " + directory,
					"Failed to read directory!", ex
				);
			}
		} else {
			return EMPTY_PATHS;
		}
	}

	/**
	 * Method deletes directory along with its contents.
	 */
	public static void deleteDirectory(@Nonnull Path directory) {
		if (directory.toFile().exists()) {
			try (final Stream stream = Files.list(directory)) {
				stream.forEach(it -> {
					if (it.toFile().isDirectory()) {
						deleteDirectory(it);
					} else {
						if (!it.toFile().delete()) {
							throw new UnexpectedIOException(
								"Failed to delete file: " + it,
								"Failed to delete file!"
							);
						}
					}
				});

				if (!directory.toFile().delete()) {
					throw new UnexpectedIOException(
						"Failed to delete directory: " + directory,
						"Failed to delete directory!"
					);
				}
			} catch (IOException ex) {
				throw new UnexpectedIOException(
					"Failed to delete directory: " + directory,
					"Failed to delete directory!", ex
				);
			}
		}
	}

	/**
	 * Checks whether the directory is empty or contains any file.
	 *
	 * @param path Path to directory
	 * @return True if directory is empty, false otherwise
	 */
	public static boolean isDirectoryEmpty(@Nonnull Path path) {
		try (final DirectoryStream dirStream = Files.newDirectoryStream(path, entry -> entry.toFile().isFile())) {
			return !dirStream.iterator().hasNext();
		} catch (IOException ex) {
			throw new UnexpectedIOException(
				"Failed to read directory: " + path,
				"Failed to read directory!", ex
			);
		}
	}

	/**
	 * Moves a source file to a target file, replacing the target file if it already exists.
	 * This method ensures atomic move if supported by the underlying file system.
	 *
	 * @param sourceFile the path of the source file to be moved
	 * @param targetFile the path of the target file where the source file should be moved to
	 * @throws UnexpectedIOException if an unexpected I/O error occurs during the file movement
	 */
	public static void rewriteTargetFileAtomically(
		@Nonnull Path sourceFile,
		@Nonnull Path targetFile
	) {
		try {
			Files.move(
				sourceFile, targetFile,
				StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE
			);
		} catch (AtomicMoveNotSupportedException e) {
			try {
				Files.move(
					sourceFile, targetFile,
					StandardCopyOption.REPLACE_EXISTING
				);
			} catch (Exception fallbackException) {
				throw new UnexpectedIOException(
					"Failed to move temporary bootstrap file to the original location!",
					"Failed to move temporary bootstrap file!",
					fallbackException
				);
			}
		} catch (Exception e) {
			throw new UnexpectedIOException(
				"Failed to move temporary bootstrap file to the original location!",
				"Failed to move temporary bootstrap file!",
				e
			);
		}
	}

	/**
	 * Deletes the specified folder if it is empty.
	 *
	 * @param parentDirectory The path to the parent directory.
	 * @throws UnexpectedIOException If the empty folder cannot be deleted.
	 */
	public static void deleteFolderIfEmpty(@Nonnull Path parentDirectory) {
		try {
			if (parentDirectory.toFile().exists()) {
				try (final Stream fileStream = Files.list(parentDirectory)) {
					if (fileStream.findFirst().isEmpty()) {
						Files.delete(parentDirectory);
					}
				}
			}
		} catch (IOException e) {
			throw new UnexpectedIOException(
				"Cannot delete empty folder: " + parentDirectory,
				"Cannot delete empty folder!",
				e
			);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy