Java tutorial
/* * Copyright 2019 Lars Kroll * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.larskroll.common; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.util.Iterator; import java.util.concurrent.atomic.AtomicLong; /** * * @author lkroll */ public class RAFileRef implements DataRef { private final RandomAccessFile raf; private final File f; private AtomicLong rc = new AtomicLong(1); private boolean delete = false; public RAFileRef(File f, RandomAccessFile raf) { this.f = f; this.raf = raf; } public RandomAccessFile getRAF() { return this.raf; } public File getFile() { return this.f; } public void markForDeletion() { this.delete = true; //f.deleteOnExit(); // in case the VM quits } @Override public byte[] dereference() { try { byte[] data = new byte[(int) raf.length()]; raf.seek(0); raf.read(data); return data; } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public byte dereference(long i) { try { if (i >= raf.length()) { throw new IndexOutOfBoundsException("Asked for index " + i + " but length is only " + raf.length()); } raf.seek(i); return raf.readByte(); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public byte[] dereference(long start, long end) { try { if ((start >= raf.length()) || (start < 0)) { throw new IndexOutOfBoundsException( "Asked for start " + start + " but length is only " + raf.length()); } if ((end > raf.length()) || (end < start)) { throw new IndexOutOfBoundsException( "Asked for end " + end + " but length is only " + raf.length() + " and start is" + start); } long l = end - start; if (l > Integer.MAX_VALUE) { throw new IndexOutOfBoundsException("Range doesn't fit into an integer: " + l); } int li = (int) l; byte[] data = new byte[li]; raf.seek(start); raf.read(data); return data; } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void assign(long i, byte val) { try { if (i >= raf.length()) { throw new IndexOutOfBoundsException("Asked for index " + i + " but length is only " + raf.length()); } raf.seek(i); raf.writeByte(val); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void assign(long start, byte[] newData) { try { if ((start >= raf.length()) || (start < 0)) { throw new IndexOutOfBoundsException( "Asked for start " + start + " but length is only " + raf.length()); } long end = start + newData.length; if ((end > raf.length())) { throw new IndexOutOfBoundsException( "Asked for length " + newData.length + " but length is only " + raf.length()); } raf.seek(start); raf.write(newData); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void assign(long start, DataRef newData) { if (newData instanceof RAFileRef) { try { RAFileRef src = (RAFileRef) newData; raf.seek(start); //src.raf.seek(0); FileChannel sink = raf.getChannel(); FileChannel source = src.raf.getChannel(); source.transferTo(0, src.raf.length(), sink); } catch (IOException ex) { throw new RuntimeException(ex); } } else { assign(start, newData.dereference()); } } @Override public void copyTo(DataRef target, long offset) { } void copyTo(DataRef target, long offset, long start, long length) { if (target instanceof RAFileRef) { try { RAFileRef tgt = (RAFileRef) target; //tgt.raf.seek(offset); raf.seek(start); FileChannel sink = tgt.raf.getChannel(); FileChannel source = raf.getChannel(); source.transferTo(offset, length, sink); } catch (IOException ex) { throw new RuntimeException(ex); } } else { if (offset > Integer.MAX_VALUE) { throw new IndexOutOfBoundsException("offset doesn't fit into an integer: " + offset); } copyTo(target.dereference(), (int) offset); } } void copyTo(byte[] target, int offset, long start, int length) { try { if ((offset >= target.length) || (offset < 0)) { throw new IndexOutOfBoundsException( "Asked for offset " + offset + " but length is only " + target.length); } raf.seek(start); raf.read(target, offset, length); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void copyTo(byte[] target, int offset) { long length = size(); if (length > Integer.MAX_VALUE) { throw new IndexOutOfBoundsException("length doesn't fit into an integer: " + length); } copyTo(target, offset, 0, (int) length); } @Override public long size() { try { return raf.length(); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void retain() { rc.incrementAndGet(); } @Override public void release() { if (rc.decrementAndGet() == 0) { try { raf.close(); if (delete) { f.delete(); } } catch (IOException ex) { throw new RuntimeException(ex); } } if (rc.get() < 0) { throw new IllegalStateException("Object was already deallocated: " + raf); } } public long rc() { // for debugging only! return rc.get(); } @Override public void copyTo(io.netty.buffer.ByteBuf buffer) { long length = size(); if (length > Integer.MAX_VALUE) { throw new IndexOutOfBoundsException("length doesn't fit into an integer: " + length); } copyTo(buffer, 0, (int) length); } void copyTo(io.netty.buffer.ByteBuf buffer, long start, int length) { try { int written = -1; int startx = buffer.writerIndex(); /* Note: there's a weird bug here, causing the buffer to sometimes * read nothing, although there's enough to read. * As a workaround: Keep trying until we get the desired amount of data */ while (written < length) { raf.seek(start); buffer.writerIndex(startx); FileChannel source = raf.getChannel(); written = buffer.writeBytes(source, length); // if (written != length) { // System.err.println("Read from " + raf + " of size " + raf.length() // + " starting at " + start + " leaving " + (raf.length()-start) // + " to read " + length); // throw new RuntimeException("Buffer didn't write required bytes!"); // } } } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public Iterable<DataRef> split(long numberOfChunks, int chunkSize) { return new RAFRIterator(chunkSize); } public class RAFRIterator implements Iterator<DataRef>, Iterable<DataRef> { public long pos = 0; private final int chunkSize; private final long length; private RAFRIterator(int chunkSize) { this.chunkSize = chunkSize; this.length = size(); } @Override public boolean hasNext() { return length > pos; } @Override public DataRef next() { int chunkLength = (int) Math.min((long) chunkSize, length - pos); // the smaller one must be int sized PartialFileRef subarea = new PartialFileRef(pos, chunkLength, RAFileRef.this); pos += chunkLength; return subarea; } @Override public Iterator<DataRef> iterator() { return this; } @Override public void remove() { throw new UnsupportedOperationException("Not yet implemented!"); } } }