Retain RandomAccessFile in JournalSegmentFile
[controller.git] / atomix-storage / src / main / java / io / atomix / storage / journal / JournalSegmentFile.java
1 /*
2  * Copyright 2015-2022 Open Networking Foundation and others.  All rights reserved.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package io.atomix.storage.journal;
17
18 import static java.util.Objects.requireNonNull;
19
20 import com.google.common.base.MoreObjects;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.RandomAccessFile;
24 import java.nio.channels.FileChannel;
25 import java.nio.file.Path;
26 import org.eclipse.jdt.annotation.NonNull;
27
28 /**
29  * Segment file utility.
30  *
31  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
32  */
33 final class JournalSegmentFile {
34     private static final char PART_SEPARATOR = '-';
35     private static final char EXTENSION_SEPARATOR = '.';
36     private static final String EXTENSION = "log";
37
38     private final @NonNull JournalSegmentDescriptor descriptor;
39     private final @NonNull Path path;
40
41     private final RandomAccessFile file;
42
43     private JournalSegmentFile(final Path path, final JournalSegmentDescriptor descriptor,
44             final RandomAccessFile file) {
45         this.path = requireNonNull(path);
46         this.descriptor = requireNonNull(descriptor);
47         this.file = requireNonNull(file);
48     }
49
50     static @NonNull JournalSegmentFile createNew(final String name, final File directory,
51             final JournalSegmentDescriptor descriptor) throws IOException {
52         final var file = createSegmentFile(name, directory, descriptor.id());
53         final var raf = new RandomAccessFile(file, "rw");
54         try {
55             raf.setLength(descriptor.maxSegmentSize());
56             raf.write(descriptor.toArray());
57         } catch (IOException e) {
58             raf.close();
59             throw e;
60         }
61         return new JournalSegmentFile(file.toPath(), descriptor, raf);
62     }
63
64     static @NonNull JournalSegmentFile openExisting(final Path path) throws IOException {
65         final var raf = new RandomAccessFile(path.toFile(), "rw");
66         final JournalSegmentDescriptor descriptor;
67         try {
68             // read the descriptor
69             descriptor = JournalSegmentDescriptor.readFrom(raf.getChannel());
70         } catch (IOException e) {
71             raf.close();
72             throw e;
73         }
74         return new JournalSegmentFile(path, descriptor, raf);
75     }
76
77     /**
78      * Returns the segment file.
79      *
80      * @return The segment file.
81      */
82     @NonNull Path path() {
83         return path;
84     }
85
86     /**
87      * Returns the segment descriptor.
88      *
89      * @return The segment descriptor.
90      */
91     @NonNull JournalSegmentDescriptor descriptor() {
92         return descriptor;
93     }
94
95     int maxSize() {
96         return descriptor.maxSegmentSize();
97     }
98
99     int size() throws IOException {
100         return (int) file.length();
101     }
102
103     FileChannel channel() {
104         return file.getChannel();
105     }
106
107     void close() throws IOException {
108         file.close();
109     }
110
111     @Override
112     public String toString() {
113         return MoreObjects.toStringHelper(this).add("path", path).add("descriptor", descriptor).toString();
114     }
115
116     /**
117      * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
118      *
119      * @throws NullPointerException if {@code file} is null
120      */
121     public static boolean isSegmentFile(final String name, final File file) {
122         return isSegmentFile(name, file.getName());
123     }
124
125     /**
126      * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
127      *
128      * @param journalName the name of the journal
129      * @param fileName the name of the file to check
130      * @throws NullPointerException if {@code file} is null
131      */
132     public static boolean isSegmentFile(final String journalName, final String fileName) {
133         requireNonNull(journalName, "journalName cannot be null");
134         requireNonNull(fileName, "fileName cannot be null");
135
136         int partSeparator = fileName.lastIndexOf(PART_SEPARATOR);
137         int extensionSeparator = fileName.lastIndexOf(EXTENSION_SEPARATOR);
138
139         if (extensionSeparator == -1 || partSeparator == -1 || extensionSeparator < partSeparator
140             || !fileName.endsWith(EXTENSION)) {
141             return false;
142         }
143
144         for (int i = partSeparator + 1; i < extensionSeparator; i++) {
145             if (!Character.isDigit(fileName.charAt(i))) {
146                 return false;
147             }
148         }
149
150         return fileName.startsWith(journalName);
151     }
152
153     /**
154      * Creates a segment file for the given directory, log name, segment ID, and segment version.
155      */
156     static File createSegmentFile(final String name, final File directory, final long id) {
157         return new File(directory, String.format("%s-%d.log", requireNonNull(name, "name cannot be null"), id));
158     }
159 }