blob: a25fe6bf0c5dfb5be9c633614234b74c7de44667 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.jena.atlas.io;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.util.function.Function;
import org.apache.jena.atlas.RuntimeIOException;
import org.apache.jena.atlas.logging.FmtLog;
public class IOX {
public static final Path currentDirectory = Path.of(".");
/** A Consumer that can throw {@link IOException}. */
@FunctionalInterface
public interface IOConsumer<X> {
void actionEx(X arg) throws IOException;
}
/**
* Convert an {@link IOException} into a {@link RuntimeIOException}.
* <p>
* Idiom:
*
* <pre>
* catch(IOException ex) { throw new exception(ex); }
* </pre>
*
* @param ioException
* @return RuntimeIOException
*/
public static RuntimeIOException exception(IOException ioException) {
return new RuntimeIOException(ioException);
}
/**
* Convert an {@link IOException} into a {@link RuntimeIOException}.
* <p>
* Idiom:
*
* <pre>
* catch(IOException ex) { throw new exception("Oh dear", ex); }
* </pre>
*
* @param message
* @param ioException
* @return RuntimeIOException
*/
public static RuntimeIOException exception(String message, IOException ioException) {
return new RuntimeIOException(message, ioException);
}
/** Run I/O code */
@FunctionalInterface
public interface ActionIO {
void run() throws IOException;
}
/**
* Run an action, converting an {@link IOException} into a
* {@link RuntimeIOException}.
* <p>
* Idiom:
*
* <pre>
* run(()-&gt;...));
* </pre>
*/
public static void run(ActionIO action) {
try {
action.run();
} catch (IOException e) {
throw exception(e);
}
}
/**
* Write a file safely - the change happens (the function returns true) or
* something went wrong (the function throws a runtime exception) and the file is
* not changed.
*/
public static boolean safeWrite(Path file, IOConsumer<OutputStream> writerAction) {
Path tmp = createTempFile(file.toAbsolutePath().getParent(), file.getFileName().toString(), ".tmp");
return safeWrite(file, tmp, writerAction);
}
/**
* Write a file safely - the change happens (the function returns true) or
* something went wrong (the function throws a runtime exception) and the file is
* not changed. Note that the tmpfile must be in the same directory as the actual
* file so an OS-atomic rename can be done.
*/
public static boolean safeWrite(Path file, Path tmpFile, IOConsumer<OutputStream> writerAction) {
try {
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tmpFile))) {
writerAction.actionEx(out);
}
move(tmpFile, file);
return true;
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/**
* Write a file safely, but allow system to use copy-delete if the change can not
* be done atomically. Prefer {@link #safeWrite} which requires an atomic move.
*/
public static boolean safeWriteOrCopy(Path file, Path tmpFile, IOConsumer<OutputStream> writerAction) {
try {
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tmpFile))) {
writerAction.actionEx(out);
}
moveAllowCopy(tmpFile, file);
return true;
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/** Delete a file. */
public static void delete(Path path) {
try {
Files.delete(path);
} catch (IOException ex) {
FmtLog.error(IOX.class, ex, "IOException deleting %s", path);
throw IOX.exception(ex);
}
}
/** Atomically move a file. */
public static void move(Path src, Path dst) {
try {
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException ex) {
FmtLog.error(IOX.class, ex, "IOException moving %s to %s", src, dst);
throw IOX.exception(ex);
}
}
/**
* Move a file, allowing the system to copy-delete it if it can not be moved
* atomically.
*/
public static void moveAllowCopy(Path src, Path dst) {
try {
Files.move(src, dst);
} catch (IOException ex) {
FmtLog.error(IOX.class, ex, "IOException moving %s to %s", src, dst);
throw IOX.exception(ex);
}
}
public static void deleteAll(String start) {
deleteAll(Paths.get(start));
}
/**
* Delete everything from a {@code Path} start point, including the path itself.
* Works on files or directories. Walks down the tree and deletes directories on
* the way backup.
*/
public static void deleteAll(Path start) {
if ( !Files.exists(start) )
return;
try {
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if ( e == null ) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
throw e;
}
}
});
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/**
* Copy a file, not atomic. * Can copy to a directory or over an existing file.
*
* @param srcFilename
* @param dstFilename
*/
public static void copy(String srcFilename, String dstFilename) {
Path src = Path.of(srcFilename);
if ( !Files.exists(src) )
throw new RuntimeIOException("No such file: " + srcFilename);
Path dst = Path.of(dstFilename);
if ( Files.isDirectory(dst) )
dst = dst.resolve(src.getFileName());
try {
Files.copy(src, dst);
} catch (IOException ex) {
FmtLog.error(IOX.class, ex, "IOException copying %s to %s", srcFilename, dstFilename);
throw IOX.exception(ex);
}
}
/**
* Create a directory - throw a runtime exception if there are any problems. This
* function wraps {@code Files.createDirectory}.
*/
public static void createDirectory(Path dir) {
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/** Read the whole of a file */
public static byte[] readAll(Path pathname) {
try {
return Files.readAllBytes(pathname);
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/** Write the whole of a file */
public static void writeAll(Path pathname, byte[] value) {
try {
Files.write(pathname, value, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
// /**
// * Ensure a directory exists, creating the directory and any missing
// * directories on the path to it.
// */
// public static void ensureDirectory(Path dir, FileAttribute<? >... attrs) {
// if ( Files.exists(dir) ) {
// if ( ! Files.isDirectory(dir) )
// throw new DeltaFileException("Exists but not a directory: "+dir);
// }
// try { Files.createDirectories(dir, attrs); }
// catch (IOException ex) { new DeltaFileException("Failed to create directory:
// "+dir, ex);}
// }
//
// /**
// * Ensure a file exists - create an empty one if not. This operation does
// * not create a directory path to the file.
// */
// public static void ensureFile(Path filePath, FileAttribute<? >... attrs) {
// if ( Files.exists(filePath) ) {
// if ( ! Files.isRegularFile(filePath) )
// throw new DeltaFileException("Exists but not a regular file: "+filePath);
// }
// try { Files.createFile(filePath); }
// catch (IOException ex) { new DeltaFileException("Failed to create file:
// "+filePath, ex);}
// }
/**
* Return a temporary filename path.
* <p>
* This operation is thread-safe.
*/
public static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute<? >...attrs) {
try {
return Files.createTempFile(dir, prefix, suffix, attrs);
} catch (IOException ex) {
throw IOX.exception(ex);
}
}
/**
* Generate a unique place related to path; Optionally, provide a mapping of old
* name to new namebase. This method always adds "-1", "-2" etc.
*/
public static Path uniqueDerivedPath(Path path, Function<String, String> basenameMapping) {
String base = path.getFileName().toString();
if ( basenameMapping != null )
base = basenameMapping.apply(base);
// Some large limit "just in case"
for ( int x = 0 ; x < 10_000 ; x++ ) {
String destname = base + "-" + x;
Path destpath = path.resolveSibling(destname);
if ( !Files.exists(destpath) )
return destpath;
}
return null;
}
}