2 * Copyright 2015-2022 Open Networking Foundation and others. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package io.atomix.storage.journal;
18 import static java.util.Objects.requireNonNull;
20 import com.google.common.base.MoreObjects;
21 import io.netty.buffer.ByteBufAllocator;
23 import java.io.IOException;
24 import java.io.RandomAccessFile;
25 import java.nio.channels.FileChannel;
26 import java.nio.file.Path;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
31 * Segment file utility.
33 * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
35 final class JournalSegmentFile {
36 private static final char PART_SEPARATOR = '-';
37 private static final char EXTENSION_SEPARATOR = '.';
38 private static final String EXTENSION = "log";
40 private final @NonNull JournalSegmentDescriptor descriptor;
41 private final @NonNull ByteBufAllocator allocator;
42 private final @NonNull RandomAccessFile file;
43 private final @NonNull Path path;
45 private JournalSegmentFile(final Path path, final ByteBufAllocator allocator,
46 final JournalSegmentDescriptor descriptor, final RandomAccessFile file) {
47 this.path = requireNonNull(path);
48 this.allocator = requireNonNull(allocator);
49 this.descriptor = requireNonNull(descriptor);
50 this.file = requireNonNull(file);
53 static @NonNull JournalSegmentFile createNew(final String name, final File directory,
54 final ByteBufAllocator allocator, final JournalSegmentDescriptor descriptor) throws IOException {
55 final var file = createSegmentFile(name, directory, descriptor.id());
56 final var raf = new RandomAccessFile(file, "rw");
58 raf.setLength(descriptor.maxSegmentSize());
59 raf.write(descriptor.toArray());
60 } catch (IOException e) {
64 return new JournalSegmentFile(file.toPath(), allocator, descriptor, raf);
67 static @NonNull JournalSegmentFile openExisting(final Path path, final ByteBufAllocator allocator)
69 final var raf = new RandomAccessFile(path.toFile(), "rw");
70 final JournalSegmentDescriptor descriptor;
72 // read the descriptor
73 descriptor = JournalSegmentDescriptor.readFrom(raf.getChannel());
74 } catch (IOException e) {
78 return new JournalSegmentFile(path, allocator, descriptor, raf);
82 * Returns the segment file path.
84 * @return The segment file path
86 @NonNull Path path() {
91 * Returns the {@link ByteBufAllocator} for this file.
93 * @return A {@link ByteBufAllocator}
95 @NonNull ByteBufAllocator allocator() {
100 * Returns the segment version.
102 * @return the segment version
105 return descriptor.version();
109 * Returns the segment identifier.
111 * @return the segment identifier
114 return descriptor.id();
118 * Returns the index of first entry stored in this file.
120 * @return the index of first entry stored in this file
123 return descriptor.index();
127 return descriptor.maxSegmentSize();
130 int size() throws IOException {
131 return (int) file.length();
134 FileChannel channel() {
135 return file.getChannel();
139 * Access this file using specified {@link StorageLevel} and maximum entry size.
141 * @param level a {@link StorageLevel}
142 * @param maxEntrySize maximum size of stored entry
143 * @return A {@link MappedFileAccess}
144 * @throws IOException if an I/O error occurs
147 FileAccess newAccess(final StorageLevel level, final int maxEntrySize) throws IOException {
148 return switch (level) {
149 case DISK -> new DiskFileAccess(this, maxEntrySize);
150 case MAPPED -> MappedFileAccess.of(this, maxEntrySize);
154 void close() throws IOException {
159 public String toString() {
160 return MoreObjects.toStringHelper(this).add("path", path).add("descriptor", descriptor).toString();
164 * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
166 * @throws NullPointerException if {@code file} is null
168 public static boolean isSegmentFile(final String name, final File file) {
169 return isSegmentFile(name, file.getName());
173 * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
175 * @param journalName the name of the journal
176 * @param fileName the name of the file to check
177 * @throws NullPointerException if {@code file} is null
179 public static boolean isSegmentFile(final String journalName, final String fileName) {
180 requireNonNull(journalName, "journalName cannot be null");
181 requireNonNull(fileName, "fileName cannot be null");
183 int partSeparator = fileName.lastIndexOf(PART_SEPARATOR);
184 int extensionSeparator = fileName.lastIndexOf(EXTENSION_SEPARATOR);
186 if (extensionSeparator == -1 || partSeparator == -1 || extensionSeparator < partSeparator
187 || !fileName.endsWith(EXTENSION)) {
191 for (int i = partSeparator + 1; i < extensionSeparator; i++) {
192 if (!Character.isDigit(fileName.charAt(i))) {
197 return fileName.startsWith(journalName);
201 * Creates a segment file for the given directory, log name, segment ID, and segment version.
203 static File createSegmentFile(final String name, final File directory, final long id) {
204 return new File(directory, String.format("%s-%d.log", requireNonNull(name, "name cannot be null"), id));