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

org.agrona.IoUtil Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2025 Real Logic Limited.
 *
 * Licensed 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
 *
 * https://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.agrona;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.function.BiConsumer;

import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
import static java.nio.channels.FileChannel.MapMode.READ_WRITE;
import static java.nio.file.StandardOpenOption.*;

/**
 * Collection of IO utilities for dealing with files, especially mapping and un-mapping.
 */
@SuppressWarnings({ "deprecation", "removal" })
public final class IoUtil
{
    /**
     * Size in bytes of a file page.
     */
    public static final int BLOCK_SIZE = 4 * 1024;

    private static final byte[] FILLER = new byte[BLOCK_SIZE];
    private static final int MAP_READ_ONLY = 0;
    private static final int MAP_READ_WRITE = 1;
    private static final int MAP_PRIVATE = 2;

    private IoUtil()
    {
    }

    /**
     * Fill region of a file with a given byte value.
     *
     * @param fileChannel to fill.
     * @param position    at which to start writing.
     * @param length      of the region to write.
     * @param value       to fill the region with.
     */
    public static void fill(final FileChannel fileChannel, final long position, final long length, final byte value)
    {
        try
        {
            final byte[] filler;
            if (0 != value)
            {
                filler = new byte[BLOCK_SIZE];
                Arrays.fill(filler, value);
            }
            else
            {
                filler = FILLER;
            }

            final ByteBuffer byteBuffer = ByteBuffer.wrap(filler);
            fileChannel.position(position);

            final int blocks = (int)(length / BLOCK_SIZE);
            final int blockRemainder = (int)(length % BLOCK_SIZE);

            for (int i = 0; i < blocks; i++)
            {
                byteBuffer.position(0);
                fileChannel.write(byteBuffer);
            }

            if (blockRemainder > 0)
            {
                byteBuffer.position(0);
                byteBuffer.limit(blockRemainder);
                fileChannel.write(byteBuffer);
            }
        }
        catch (final IOException ex)
        {
            LangUtil.rethrowUnchecked(ex);
        }
    }

    /**
     * Recursively delete a file or directory tree.
     *
     * @param file           to be deleted.
     * @param ignoreFailures don't throw an exception if delete fails.
     */
    public static void delete(final File file, final boolean ignoreFailures)
    {
        if (file.exists())
        {
            if (file.isDirectory())
            {
                final File[] files = file.listFiles();
                if (null != files)
                {
                    for (final File f : files)
                    {
                        delete(f, ignoreFailures);
                    }
                }
            }

            if (!file.delete() && !ignoreFailures)
            {
                try
                {
                    Files.delete(file.toPath());
                }
                catch (final IOException ex)
                {
                    LangUtil.rethrowUnchecked(ex);
                }
            }
        }
    }

    /**
     * Recursively delete a file or directory tree.
     *
     * @param file         to be deleted.
     * @param errorHandler to delegate errors to on exception.
     */
    public static void delete(final File file, final ErrorHandler errorHandler)
    {
        try
        {
            if (file.exists())
            {
                if (file.isDirectory())
                {
                    final File[] files = file.listFiles();
                    if (null != files)
                    {
                        for (final File f : files)
                        {
                            delete(f, errorHandler);
                        }
                    }
                }

                if (!file.delete())
                {
                    Files.delete(file.toPath());
                }
            }
        }
        catch (final Exception ex)
        {
            errorHandler.onError(ex);
        }
    }

    /**
     * Create a directory if it doesn't already exist.
     *
     * @param directory        the directory which definitely exists after this method call.
     * @param descriptionLabel to associate with the directory for any exceptions.
     */
    public static void ensureDirectoryExists(final File directory, final String descriptionLabel)
    {
        if (!directory.exists())
        {
            if (!directory.mkdirs())
            {
                throw new IllegalArgumentException("could not create " + descriptionLabel + " directory: " + directory);
            }
        }
    }

    /**
     * Create a directory, removing previous directory if it already exists.
     * 

* Call callback if it does exist. * * @param directory the directory which definitely exists after this method call. * @param descriptionLabel to associate with the directory for any exceptions and callback. * @param callback to call if directory exists passing back absolute path and descriptionLabel. */ public static void ensureDirectoryIsRecreated( final File directory, final String descriptionLabel, final BiConsumer callback) { if (directory.exists()) { delete(directory, false); callback.accept(directory.getAbsolutePath(), descriptionLabel); } if (!directory.mkdirs()) { throw new IllegalArgumentException("could not create " + descriptionLabel + " directory: " + directory); } } /** * Delete file only if it already exists. * * @param file to delete. */ public static void deleteIfExists(final File file) { try { Files.deleteIfExists(file.toPath()); } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } } /** * Delete file only if it already exists. * * @param file to delete. * @param errorHandler to delegate error to on exception. */ public static void deleteIfExists(final File file, final ErrorHandler errorHandler) { try { Files.deleteIfExists(file.toPath()); } catch (final Exception ex) { errorHandler.onError(ex); } } /** * Create an empty file, fill with 0s, and return the {@link FileChannel}. * * @param file to create. * @param length of the file to create. * @return {@link java.nio.channels.FileChannel} for the file. */ public static FileChannel createEmptyFile(final File file, final long length) { return createEmptyFile(file, length, true); } /** * Create an empty file, and optionally fill with 0s, and return the {@link FileChannel}. * * @param file to create. * @param length of the file to create. * @param fillWithZeros to the length of the file to force allocation. * @return {@link java.nio.channels.FileChannel} for the file. */ public static FileChannel createEmptyFile(final File file, final long length, final boolean fillWithZeros) { ensureDirectoryExists(file.getParentFile(), file.getParent()); FileChannel templateFile = null; try { final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); randomAccessFile.setLength(length); templateFile = randomAccessFile.getChannel(); if (fillWithZeros) { fill(templateFile, 0, length, (byte)0); } } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } return templateFile; } /** * Check that file exists, open file, and return MappedByteBuffer for entire file as * {@link java.nio.channels.FileChannel.MapMode#READ_WRITE}. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to map. * @param descriptionLabel to be associated for any exceptions. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapExistingFile(final File location, final String descriptionLabel) { return mapExistingFile(location, READ_WRITE, descriptionLabel); } /** * Check that file exists, open file, and return MappedByteBuffer for only region specified as * {@link java.nio.channels.FileChannel.MapMode#READ_WRITE}. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to map. * @param descriptionLabel to be associated for an exceptions. * @param offset offset to start mapping at. * @param length length to map region. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapExistingFile( final File location, final String descriptionLabel, final long offset, final long length) { return mapExistingFile(location, READ_WRITE, descriptionLabel, offset, length); } /** * Check that file exists, open file, and return MappedByteBuffer for entire file for a given * {@link java.nio.channels.FileChannel.MapMode}. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to map. * @param mapMode for the mapping. * @param descriptionLabel to be associated for any exceptions. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapExistingFile( final File location, final FileChannel.MapMode mapMode, final String descriptionLabel) { checkFileExists(location, descriptionLabel); MappedByteBuffer mappedByteBuffer = null; try (RandomAccessFile file = new RandomAccessFile(location, getFileMode(mapMode)); FileChannel channel = file.getChannel()) { mappedByteBuffer = channel.map(mapMode, 0, channel.size()); } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } return mappedByteBuffer; } /** * Check that file exists, open file, and return MappedByteBuffer for only region specified for a given * {@link java.nio.channels.FileChannel.MapMode}. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to map. * @param mapMode for the mapping. * @param descriptionLabel to be associated for an exceptions. * @param offset offset to start mapping at. * @param length length to map region. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapExistingFile( final File location, final FileChannel.MapMode mapMode, final String descriptionLabel, final long offset, final long length) { checkFileExists(location, descriptionLabel); MappedByteBuffer mappedByteBuffer = null; try (RandomAccessFile file = new RandomAccessFile(location, getFileMode(mapMode)); FileChannel channel = file.getChannel()) { mappedByteBuffer = channel.map(mapMode, offset, length); } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } return mappedByteBuffer; } /** * Create a new file, fill with 0s, and return a {@link java.nio.MappedByteBuffer} for the file. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to create and map. * @param length of the file to create and map. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapNewFile(final File location, final long length) { return mapNewFile(location, length, true); } /** * Create a new file, and optionally fill with 0s, and return a {@link java.nio.MappedByteBuffer} for the file. *

* The file itself will be closed, but the mapping will persist. * * @param location of the file to create and map. * @param length of the file to create and map. * @param fillWithZeros to force allocation. * @return {@link java.nio.MappedByteBuffer} for the file. */ public static MappedByteBuffer mapNewFile(final File location, final long length, final boolean fillWithZeros) { MappedByteBuffer mappedByteBuffer = null; try (FileChannel channel = FileChannel.open(location.toPath(), CREATE_NEW, READ, WRITE)) { mappedByteBuffer = channel.map(READ_WRITE, 0, length); } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } if (fillWithZeros) { int pos = 0; final int capacity = mappedByteBuffer.capacity(); while (pos < capacity) { mappedByteBuffer.put(pos, (byte)0); pos += BLOCK_SIZE; } } return mappedByteBuffer; } /** * Check that a file exists and throw an exception if not. * * @param file to check existence of. * @param name to associate for the exception. */ public static void checkFileExists(final File file, final String name) { if (!file.exists()) { final String msg = "missing file for " + name + " : " + file.getAbsolutePath(); throw new IllegalStateException(msg); } } /** * Unmap a {@link MappedByteBuffer} without waiting for the next GC cycle. * * @param buffer to be unmapped. * @see BufferUtil#free(ByteBuffer) */ public static void unmap(final MappedByteBuffer buffer) { BufferUtil.free(buffer); } /** * Unmap a {@link ByteBuffer} without waiting for the next GC cycle if its memory mapped. * * @param buffer to be unmapped. */ public static void unmap(final ByteBuffer buffer) { if (buffer instanceof MappedByteBuffer) { unmap((MappedByteBuffer)buffer); } } /** * Return the system property for java.io.tmpdir ensuring a {@link File#separator} is at the end. * * @return tmp directory for the runtime. */ public static String tmpDirName() { String tmpDirName = System.getProperty("java.io.tmpdir"); if (!tmpDirName.endsWith(File.separator)) { tmpDirName += File.separator; } return tmpDirName; } /** * Remove trailing slash characters from a builder leaving the remaining characters. * * @param builder to remove trailing slash characters from. */ public static void removeTrailingSlashes(final StringBuilder builder) { while (builder.length() > 1) { final int lastCharIndex = builder.length() - 1; final char c = builder.charAt(lastCharIndex); if ('/' == c || '\\' == c) { builder.setLength(lastCharIndex); } else { break; } } } private static String getFileMode(final FileChannel.MapMode mode) { return mode == READ_ONLY ? "r" : "rw"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy