2 * Copyright 2015-present Open Networking Foundation
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.buffer;
18 import io.atomix.utils.memory.Memory;
21 import java.io.IOException;
22 import java.io.RandomAccessFile;
23 import java.nio.ByteOrder;
24 import java.nio.MappedByteBuffer;
25 import java.nio.channels.FileChannel;
26 import java.nio.file.Files;
31 * File bytes wrap a simple {@link RandomAccessFile} instance to provide random access to a randomAccessFile on local disk. All
32 * operations are delegated directly to the {@link RandomAccessFile} interface, and limitations are dependent on the
33 * semantics of the underlying randomAccessFile.
35 * Bytes are always stored in the underlying randomAccessFile in {@link ByteOrder#BIG_ENDIAN} order.
36 * To flip the byte order to read or write to/from a randomAccessFile in {@link ByteOrder#LITTLE_ENDIAN} order use
37 * {@link Bytes#order(ByteOrder)}.
39 * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
41 public class FileBytes extends AbstractBytes {
42 static final String DEFAULT_MODE = "rw";
45 * Allocates a randomAccessFile buffer of unlimited count.
47 * The buffer will be allocated with {@link Long#MAX_VALUE} bytes. As bytes are written to the buffer, the underlying
48 * {@link RandomAccessFile} will expand.
50 * @param file The randomAccessFile to allocate.
51 * @return The allocated buffer.
53 public static FileBytes allocate(File file) {
54 return allocate(file, DEFAULT_MODE, Integer.MAX_VALUE);
58 * Allocates a randomAccessFile buffer.
60 * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
62 * @param file The randomAccessFile to allocate.
63 * @param size The count of the bytes to allocate.
64 * @return The allocated buffer.
66 public static FileBytes allocate(File file, int size) {
67 return allocate(file, DEFAULT_MODE, size);
71 * Allocates a randomAccessFile buffer.
73 * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
75 * @param file The randomAccessFile to allocate.
76 * @param mode The mode in which to open the underlying {@link RandomAccessFile}.
77 * @param size The count of the bytes to allocate.
78 * @return The allocated buffer.
80 public static FileBytes allocate(File file, String mode, int size) {
81 return new FileBytes(file, mode, (int) Math.min(Memory.Util.toPow2(size), Integer.MAX_VALUE));
84 private static final int PAGE_SIZE = 1024 * 4;
85 private static final byte[] BLANK_PAGE = new byte[PAGE_SIZE];
87 private final File file;
88 private final String mode;
89 private final RandomAccessFile randomAccessFile;
92 FileBytes(File file, String mode, int size) {
94 throw new NullPointerException("file cannot be null");
100 throw new IllegalArgumentException("size must be positive");
107 this.randomAccessFile = new RandomAccessFile(file, mode);
108 if (size > randomAccessFile.length()) {
109 randomAccessFile.setLength(size);
111 } catch (IOException e) {
112 throw new RuntimeException(e);
117 * Returns the underlying file object.
119 * @return The underlying file.
126 * Returns the file mode.
128 * @return The file mode.
130 public String mode() {
140 public Bytes resize(int newSize) {
141 if (newSize < size) {
142 throw new IllegalArgumentException("cannot decrease file bytes size; use zero() to decrease file size");
144 int oldSize = this.size;
147 long length = randomAccessFile.length();
148 if (newSize > length) {
149 randomAccessFile.setLength(newSize);
152 } catch (IOException e) {
153 throw new RuntimeException(e);
159 public boolean isFile() {
164 * Maps a portion of the randomAccessFile into memory in {@link FileChannel.MapMode#READ_WRITE} mode and returns
165 * a {@link MappedBytes} instance.
167 * @param offset The offset from which to map the randomAccessFile into memory.
168 * @param size The count of the bytes to map into memory.
169 * @return The mapped bytes.
170 * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
171 * {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
173 public MappedBytes map(int offset, int size) {
174 return map(offset, size, parseMode(mode));
178 * Maps a portion of the randomAccessFile into memory and returns a {@link MappedBytes} instance.
180 * @param offset The offset from which to map the randomAccessFile into memory.
181 * @param size The count of the bytes to map into memory.
182 * @param mode The mode in which to map the randomAccessFile into memory.
183 * @return The mapped bytes.
184 * @throws IllegalArgumentException If {@code count} is greater than the maximum allowed
185 * {@link java.nio.MappedByteBuffer} count: {@link Integer#MAX_VALUE}
187 public MappedBytes map(int offset, int size, FileChannel.MapMode mode) {
188 MappedByteBuffer mappedByteBuffer = mapFile(randomAccessFile, offset, size, mode);
189 return new MappedBytes(file, randomAccessFile, mappedByteBuffer, mode);
192 private static MappedByteBuffer mapFile(RandomAccessFile randomAccessFile, int offset, int size, FileChannel.MapMode mode) {
194 return randomAccessFile.getChannel().map(mode, offset, size);
195 } catch (IOException e) {
196 throw new RuntimeException(e);
200 private static FileChannel.MapMode parseMode(String mode) {
203 return FileChannel.MapMode.READ_ONLY;
206 return FileChannel.MapMode.READ_WRITE;
211 public ByteOrder order() {
212 return ByteOrder.BIG_ENDIAN;
216 * Seeks to the given offset.
218 private void seekToOffset(int offset) throws IOException {
219 if (randomAccessFile.getFilePointer() != offset) {
220 randomAccessFile.seek(offset);
225 public Bytes zero() {
227 randomAccessFile.setLength(0);
228 randomAccessFile.setLength(size);
229 for (int i = 0; i < size; i += PAGE_SIZE) {
230 randomAccessFile.write(BLANK_PAGE, 0, Math.min(size - i, PAGE_SIZE));
232 } catch (IOException e) {
233 throw new RuntimeException(e);
239 public Bytes zero(int offset) {
241 int length = Math.max(offset, size);
242 randomAccessFile.setLength(offset);
243 randomAccessFile.setLength(length);
244 seekToOffset(offset);
245 for (int i = offset; i < length; i += PAGE_SIZE) {
246 randomAccessFile.write(BLANK_PAGE, 0, Math.min(length - i, PAGE_SIZE));
248 } catch (IOException e) {
249 throw new RuntimeException(e);
255 public Bytes zero(int offset, int length) {
256 for (int i = offset; i < offset + length; i++) {
257 writeByte(i, (byte) 0);
263 public Bytes read(int position, Bytes bytes, int offset, int length) {
264 checkRead(position, length);
265 if (bytes instanceof WrappedBytes) {
266 bytes = ((WrappedBytes) bytes).root();
268 if (bytes.hasArray()) {
270 seekToOffset(position);
271 randomAccessFile.readFully(bytes.array(), (int) offset, (int) length);
272 } catch (IOException e) {
273 throw new RuntimeException(e);
277 seekToOffset(position);
278 byte[] readBytes = new byte[(int) length];
279 randomAccessFile.readFully(readBytes);
280 bytes.write(offset, readBytes, 0, length);
281 } catch (IOException e) {
282 throw new RuntimeException(e);
289 public Bytes read(int position, byte[] bytes, int offset, int length) {
290 checkRead(position, length);
292 seekToOffset(position);
293 randomAccessFile.readFully(bytes, (int) offset, (int) length);
294 } catch (IOException e) {
295 throw new RuntimeException(e);
301 public int readByte(int offset) {
302 checkRead(offset, BYTE);
304 seekToOffset(offset);
305 return randomAccessFile.readByte();
306 } catch (IOException e) {
307 throw new RuntimeException(e);
312 public char readChar(int offset) {
313 checkRead(offset, CHARACTER);
315 seekToOffset(offset);
316 return randomAccessFile.readChar();
317 } catch (IOException e) {
318 throw new RuntimeException(e);
323 public short readShort(int offset) {
324 checkRead(offset, SHORT);
326 seekToOffset(offset);
327 return randomAccessFile.readShort();
328 } catch (IOException e) {
329 throw new RuntimeException(e);
334 public int readInt(int offset) {
335 checkRead(offset, INTEGER);
337 seekToOffset(offset);
338 return randomAccessFile.readInt();
339 } catch (IOException e) {
340 throw new RuntimeException(e);
345 public long readLong(int offset) {
346 checkRead(offset, LONG);
348 seekToOffset(offset);
349 return randomAccessFile.readLong();
350 } catch (IOException e) {
351 throw new RuntimeException(e);
356 public float readFloat(int offset) {
357 checkRead(offset, FLOAT);
359 seekToOffset(offset);
360 return randomAccessFile.readFloat();
361 } catch (IOException e) {
362 throw new RuntimeException(e);
367 public double readDouble(int offset) {
368 checkRead(offset, DOUBLE);
370 seekToOffset(offset);
371 return randomAccessFile.readDouble();
372 } catch (IOException e) {
373 throw new RuntimeException(e);
378 public Bytes write(int position, Bytes bytes, int offset, int length) {
379 checkWrite(position, length);
380 if (bytes instanceof WrappedBytes) {
381 bytes = ((WrappedBytes) bytes).root();
383 if (bytes.hasArray()) {
385 seekToOffset(position);
386 randomAccessFile.write(bytes.array(), (int) offset, (int) length);
387 } catch (IOException e) {
388 throw new RuntimeException(e);
392 seekToOffset(position);
393 byte[] writeBytes = new byte[(int) length];
394 bytes.read(offset, writeBytes, 0, length);
395 randomAccessFile.write(writeBytes);
396 } catch (IOException e) {
397 throw new RuntimeException(e);
404 public Bytes write(int position, byte[] bytes, int offset, int length) {
405 checkWrite(position, length);
407 seekToOffset(position);
408 randomAccessFile.write(bytes, (int) offset, (int) length);
409 } catch (IOException e) {
410 throw new RuntimeException(e);
416 public Bytes writeByte(int offset, int b) {
417 checkWrite(offset, BYTE);
419 seekToOffset(offset);
420 randomAccessFile.writeByte(b);
421 } catch (IOException e) {
422 throw new RuntimeException(e);
428 public Bytes writeChar(int offset, char c) {
429 checkWrite(offset, CHARACTER);
431 seekToOffset(offset);
432 randomAccessFile.writeChar(c);
433 } catch (IOException e) {
434 throw new RuntimeException(e);
440 public Bytes writeShort(int offset, short s) {
441 checkWrite(offset, SHORT);
443 seekToOffset(offset);
444 randomAccessFile.writeShort(s);
445 } catch (IOException e) {
446 throw new RuntimeException(e);
452 public Bytes writeInt(int offset, int i) {
453 checkWrite(offset, INTEGER);
455 seekToOffset(offset);
456 randomAccessFile.writeInt(i);
457 } catch (IOException e) {
458 throw new RuntimeException(e);
464 public Bytes writeLong(int offset, long l) {
465 checkWrite(offset, LONG);
467 seekToOffset(offset);
468 randomAccessFile.writeLong(l);
469 } catch (IOException e) {
470 throw new RuntimeException(e);
476 public Bytes writeFloat(int offset, float f) {
477 checkWrite(offset, FLOAT);
479 seekToOffset(offset);
480 randomAccessFile.writeFloat(f);
481 } catch (IOException e) {
482 throw new RuntimeException(e);
488 public Bytes writeDouble(int offset, double d) {
489 checkWrite(offset, DOUBLE);
491 seekToOffset(offset);
492 randomAccessFile.writeDouble(d);
493 } catch (IOException e) {
494 throw new RuntimeException(e);
500 public Bytes flush() {
502 randomAccessFile.getFD().sync();
503 } catch (IOException e) {
504 throw new RuntimeException(e);
510 public void close() {
512 randomAccessFile.close();
513 } catch (IOException e) {
514 throw new RuntimeException(e);
520 * Deletes the underlying file.
522 public void delete() {
525 Files.delete(file.toPath());
526 } catch (IOException e) {
527 throw new RuntimeException(e);