Do not expose descriptor from 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 version.
88      *
89      * @return the segment version
90      */
91     int version() {
92         return descriptor.version();
93     }
94
95     /**
96      * Returns the segment identifier.
97      *
98      * @return the segment identifier
99      */
100     long segmentId() {
101         return descriptor.id();
102     }
103
104     /**
105      * Returns the index of first entry stored in this file.
106      *
107      * @return the index of first entry stored in this file
108      */
109     long firstIndex() {
110         return descriptor.index();
111     }
112
113     int maxSize() {
114         return descriptor.maxSegmentSize();
115     }
116
117     int size() throws IOException {
118         return (int) file.length();
119     }
120
121     FileChannel channel() {
122         return file.getChannel();
123     }
124
125     void close() throws IOException {
126         file.close();
127     }
128
129     @Override
130     public String toString() {
131         return MoreObjects.toStringHelper(this).add("path", path).add("descriptor", descriptor).toString();
132     }
133
134     /**
135      * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
136      *
137      * @throws NullPointerException if {@code file} is null
138      */
139     public static boolean isSegmentFile(final String name, final File file) {
140         return isSegmentFile(name, file.getName());
141     }
142
143     /**
144      * Returns a boolean value indicating whether the given file appears to be a parsable segment file.
145      *
146      * @param journalName the name of the journal
147      * @param fileName the name of the file to check
148      * @throws NullPointerException if {@code file} is null
149      */
150     public static boolean isSegmentFile(final String journalName, final String fileName) {
151         requireNonNull(journalName, "journalName cannot be null");
152         requireNonNull(fileName, "fileName cannot be null");
153
154         int partSeparator = fileName.lastIndexOf(PART_SEPARATOR);
155         int extensionSeparator = fileName.lastIndexOf(EXTENSION_SEPARATOR);
156
157         if (extensionSeparator == -1 || partSeparator == -1 || extensionSeparator < partSeparator
158             || !fileName.endsWith(EXTENSION)) {
159             return false;
160         }
161
162         for (int i = partSeparator + 1; i < extensionSeparator; i++) {
163             if (!Character.isDigit(fileName.charAt(i))) {
164                 return false;
165             }
166         }
167
168         return fileName.startsWith(journalName);
169     }
170
171     /**
172      * Creates a segment file for the given directory, log name, segment ID, and segment version.
173      */
174     static File createSegmentFile(final String name, final File directory, final long id) {
175         return new File(directory, String.format("%s-%d.log", requireNonNull(name, "name cannot be null"), id));
176     }
177 }