Plug atomix into the build
[controller.git] / third-party / atomix / storage / src / main / java / io / atomix / storage / buffer / FileBytes.java
1 /*
2  * Copyright 2015-present Open Networking Foundation
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.buffer;
17
18 import io.atomix.utils.memory.Memory;
19
20 import java.io.File;
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;
27
28 /**
29  * File bytes.
30  * <p>
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.
34  * <p>
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)}.
38  *
39  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
40  */
41 public class FileBytes extends AbstractBytes {
42   static final String DEFAULT_MODE = "rw";
43
44   /**
45    * Allocates a randomAccessFile buffer of unlimited count.
46    * <p>
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.
49    *
50    * @param file The randomAccessFile to allocate.
51    * @return The allocated buffer.
52    */
53   public static FileBytes allocate(File file) {
54     return allocate(file, DEFAULT_MODE, Integer.MAX_VALUE);
55   }
56
57   /**
58    * Allocates a randomAccessFile buffer.
59    * <p>
60    * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
61    *
62    * @param file The randomAccessFile to allocate.
63    * @param size The count of the bytes to allocate.
64    * @return The allocated buffer.
65    */
66   public static FileBytes allocate(File file, int size) {
67     return allocate(file, DEFAULT_MODE, size);
68   }
69
70   /**
71    * Allocates a randomAccessFile buffer.
72    * <p>
73    * If the underlying randomAccessFile is empty, the randomAccessFile count will expand dynamically as bytes are written to the randomAccessFile.
74    *
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.
79    */
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));
82   }
83
84   private static final int PAGE_SIZE = 1024 * 4;
85   private static final byte[] BLANK_PAGE = new byte[PAGE_SIZE];
86
87   private final File file;
88   private final String mode;
89   private final RandomAccessFile randomAccessFile;
90   private int size;
91
92   FileBytes(File file, String mode, int size) {
93     if (file == null) {
94       throw new NullPointerException("file cannot be null");
95     }
96     if (mode == null) {
97       mode = DEFAULT_MODE;
98     }
99     if (size < 0) {
100       throw new IllegalArgumentException("size must be positive");
101     }
102
103     this.file = file;
104     this.mode = mode;
105     this.size = size;
106     try {
107       this.randomAccessFile = new RandomAccessFile(file, mode);
108       if (size > randomAccessFile.length()) {
109         randomAccessFile.setLength(size);
110       }
111     } catch (IOException e) {
112       throw new RuntimeException(e);
113     }
114   }
115
116   /**
117    * Returns the underlying file object.
118    *
119    * @return The underlying file.
120    */
121   public File file() {
122     return file;
123   }
124
125   /**
126    * Returns the file mode.
127    *
128    * @return The file mode.
129    */
130   public String mode() {
131     return mode;
132   }
133
134   @Override
135   public int size() {
136     return size;
137   }
138
139   @Override
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");
143     }
144     int oldSize = this.size;
145     this.size = newSize;
146     try {
147       long length = randomAccessFile.length();
148       if (newSize > length) {
149         randomAccessFile.setLength(newSize);
150         zero(oldSize);
151       }
152     } catch (IOException e) {
153       throw new RuntimeException(e);
154     }
155     return this;
156   }
157
158   @Override
159   public boolean isFile() {
160     return true;
161   }
162
163   /**
164    * Maps a portion of the randomAccessFile into memory in {@link FileChannel.MapMode#READ_WRITE} mode and returns
165    * a {@link MappedBytes} instance.
166    *
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}
172    */
173   public MappedBytes map(int offset, int size) {
174     return map(offset, size, parseMode(mode));
175   }
176
177   /**
178    * Maps a portion of the randomAccessFile into memory and returns a {@link MappedBytes} instance.
179    *
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}
186    */
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);
190   }
191
192   private static MappedByteBuffer mapFile(RandomAccessFile randomAccessFile, int offset, int size, FileChannel.MapMode mode) {
193     try {
194       return randomAccessFile.getChannel().map(mode, offset, size);
195     } catch (IOException e) {
196       throw new RuntimeException(e);
197     }
198   }
199
200   private static FileChannel.MapMode parseMode(String mode) {
201     switch (mode) {
202       case "r":
203         return FileChannel.MapMode.READ_ONLY;
204       case "rw":
205       default:
206         return FileChannel.MapMode.READ_WRITE;
207     }
208   }
209
210   @Override
211   public ByteOrder order() {
212     return ByteOrder.BIG_ENDIAN;
213   }
214
215   /**
216    * Seeks to the given offset.
217    */
218   private void seekToOffset(int offset) throws IOException {
219     if (randomAccessFile.getFilePointer() != offset) {
220       randomAccessFile.seek(offset);
221     }
222   }
223
224   @Override
225   public Bytes zero() {
226     try {
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));
231       }
232     } catch (IOException e) {
233       throw new RuntimeException(e);
234     }
235     return this;
236   }
237
238   @Override
239   public Bytes zero(int offset) {
240     try {
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));
247       }
248     } catch (IOException e) {
249       throw new RuntimeException(e);
250     }
251     return this;
252   }
253
254   @Override
255   public Bytes zero(int offset, int length) {
256     for (int i = offset; i < offset + length; i++) {
257       writeByte(i, (byte) 0);
258     }
259     return this;
260   }
261
262   @Override
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();
267     }
268     if (bytes.hasArray()) {
269       try {
270         seekToOffset(position);
271         randomAccessFile.readFully(bytes.array(), (int) offset, (int) length);
272       } catch (IOException e) {
273         throw new RuntimeException(e);
274       }
275     } else {
276       try {
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);
283       }
284     }
285     return this;
286   }
287
288   @Override
289   public Bytes read(int position, byte[] bytes, int offset, int length) {
290     checkRead(position, length);
291     try {
292       seekToOffset(position);
293       randomAccessFile.readFully(bytes, (int) offset, (int) length);
294     } catch (IOException e) {
295       throw new RuntimeException(e);
296     }
297     return this;
298   }
299
300   @Override
301   public int readByte(int offset) {
302     checkRead(offset, BYTE);
303     try {
304       seekToOffset(offset);
305       return randomAccessFile.readByte();
306     } catch (IOException e) {
307       throw new RuntimeException(e);
308     }
309   }
310
311   @Override
312   public char readChar(int offset) {
313     checkRead(offset, CHARACTER);
314     try {
315       seekToOffset(offset);
316       return randomAccessFile.readChar();
317     } catch (IOException e) {
318       throw new RuntimeException(e);
319     }
320   }
321
322   @Override
323   public short readShort(int offset) {
324     checkRead(offset, SHORT);
325     try {
326       seekToOffset(offset);
327       return randomAccessFile.readShort();
328     } catch (IOException e) {
329       throw new RuntimeException(e);
330     }
331   }
332
333   @Override
334   public int readInt(int offset) {
335     checkRead(offset, INTEGER);
336     try {
337       seekToOffset(offset);
338       return randomAccessFile.readInt();
339     } catch (IOException e) {
340       throw new RuntimeException(e);
341     }
342   }
343
344   @Override
345   public long readLong(int offset) {
346     checkRead(offset, LONG);
347     try {
348       seekToOffset(offset);
349       return randomAccessFile.readLong();
350     } catch (IOException e) {
351       throw new RuntimeException(e);
352     }
353   }
354
355   @Override
356   public float readFloat(int offset) {
357     checkRead(offset, FLOAT);
358     try {
359       seekToOffset(offset);
360       return randomAccessFile.readFloat();
361     } catch (IOException e) {
362       throw new RuntimeException(e);
363     }
364   }
365
366   @Override
367   public double readDouble(int offset) {
368     checkRead(offset, DOUBLE);
369     try {
370       seekToOffset(offset);
371       return randomAccessFile.readDouble();
372     } catch (IOException e) {
373       throw new RuntimeException(e);
374     }
375   }
376
377   @Override
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();
382     }
383     if (bytes.hasArray()) {
384       try {
385         seekToOffset(position);
386         randomAccessFile.write(bytes.array(), (int) offset, (int) length);
387       } catch (IOException e) {
388         throw new RuntimeException(e);
389       }
390     } else {
391       try {
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);
398       }
399     }
400     return this;
401   }
402
403   @Override
404   public Bytes write(int position, byte[] bytes, int offset, int length) {
405     checkWrite(position, length);
406     try {
407       seekToOffset(position);
408       randomAccessFile.write(bytes, (int) offset, (int) length);
409     } catch (IOException e) {
410       throw new RuntimeException(e);
411     }
412     return this;
413   }
414
415   @Override
416   public Bytes writeByte(int offset, int b) {
417     checkWrite(offset, BYTE);
418     try {
419       seekToOffset(offset);
420       randomAccessFile.writeByte(b);
421     } catch (IOException e) {
422       throw new RuntimeException(e);
423     }
424     return this;
425   }
426
427   @Override
428   public Bytes writeChar(int offset, char c) {
429     checkWrite(offset, CHARACTER);
430     try {
431       seekToOffset(offset);
432       randomAccessFile.writeChar(c);
433     } catch (IOException e) {
434       throw new RuntimeException(e);
435     }
436     return this;
437   }
438
439   @Override
440   public Bytes writeShort(int offset, short s) {
441     checkWrite(offset, SHORT);
442     try {
443       seekToOffset(offset);
444       randomAccessFile.writeShort(s);
445     } catch (IOException e) {
446       throw new RuntimeException(e);
447     }
448     return this;
449   }
450
451   @Override
452   public Bytes writeInt(int offset, int i) {
453     checkWrite(offset, INTEGER);
454     try {
455       seekToOffset(offset);
456       randomAccessFile.writeInt(i);
457     } catch (IOException e) {
458       throw new RuntimeException(e);
459     }
460     return this;
461   }
462
463   @Override
464   public Bytes writeLong(int offset, long l) {
465     checkWrite(offset, LONG);
466     try {
467       seekToOffset(offset);
468       randomAccessFile.writeLong(l);
469     } catch (IOException e) {
470       throw new RuntimeException(e);
471     }
472     return this;
473   }
474
475   @Override
476   public Bytes writeFloat(int offset, float f) {
477     checkWrite(offset, FLOAT);
478     try {
479       seekToOffset(offset);
480       randomAccessFile.writeFloat(f);
481     } catch (IOException e) {
482       throw new RuntimeException(e);
483     }
484     return this;
485   }
486
487   @Override
488   public Bytes writeDouble(int offset, double d) {
489     checkWrite(offset, DOUBLE);
490     try {
491       seekToOffset(offset);
492       randomAccessFile.writeDouble(d);
493     } catch (IOException e) {
494       throw new RuntimeException(e);
495     }
496     return this;
497   }
498
499   @Override
500   public Bytes flush() {
501     try {
502       randomAccessFile.getFD().sync();
503     } catch (IOException e) {
504       throw new RuntimeException(e);
505     }
506     return this;
507   }
508
509   @Override
510   public void close() {
511     try {
512       randomAccessFile.close();
513     } catch (IOException e) {
514       throw new RuntimeException(e);
515     }
516     super.close();
517   }
518
519   /**
520    * Deletes the underlying file.
521    */
522   public void delete() {
523     try {
524       close();
525       Files.delete(file.toPath());
526     } catch (IOException e) {
527       throw new RuntimeException(e);
528     }
529   }
530
531 }