/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.driver.block;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jnode.util.ByteBufferUtils;
/**
* @author gvt
* <p/>
* We need a picture to understand this little evil:
* <p/>
* |----------------|- n ------------------------------------> start
* | | |
* | ^^^^ HEAD ^^^^ |^^^^^^^^^^^^^^^^^^^^^^^^^^^> offset |
* | ^ ^ | | |
* |----------------|- n+1 | |
* | ^ ^ | | |
* . . | |
* . BODY . | |
* | |
* | ^ ^ | length lA
* |----------------|- q-1 | |
* | ^ ^ | | |
* | ^ BODY ^ | | |
* | ^ ^ | | |
* |----------------|- q = (n+m+k) | |
* | ^ ^ | | |
* | ^^^^ TAIL ^^^^ |^^^^^^^^^^^^^^^^^^^^^^^^^^^> lst |
* | | |
* |----------------|- q+1 = (n+m+k+p) = n+nA ----------------> end
* <p/>
* <p/>
* where: offset >= 0, length >= 0, align > 0 are given and
* lst = (offset+length).
* <p/>
* The "Buffer" is defined, in terms of offsets of bytes that
* have to be written to or read from the device, by the set:
* <p/>
* U = { i | offset <= i < lst }
* <p/>
* and the "Aligned Buffer", that have to be handled on the device
* is defined by:
* <p/>
* A = { i | start <= i < end }
* <p/>
* where
* <p/>
* start = max { i>=0 | i <= offset, i % align = 0 }
* <p/>
* end = min { i>=0 | i >= lst, i % align = 0 }
* <p/>
* "A" is the "smallest" aligned buffer that contains U, i.e.
* start = min(A) and end = max(A) are multiples of "align"
* and there are no "smallest sets" (in terms of the number
* of elements o(A)) that contains "U".
* <p/>
* Let me define:
* <p/>
* n = offset / align ofsR = offset % align
* m = length / align lenR = length % align
* q = lst / align lstR = lst % align
* <p/>
* where "/" is the euclidean integer division and "%" is
* the euclidean integer remains (or modulo), the usual
* Java tokens for these operators.
* <p/>
* From the definition of Euclidean division we have:
* <p/>
* offset = n * align + ofsR 0 <= ofsR < align
* length = m * align + lenR 0 <= lenR < align
* lst = q * align + lstR 0 <= lstR < align
* <p/>
* It is very easy to see that:
* <p/>
* start = offset - ofsR = n * align
* <p/>
* end = lst - lstR + p * align = (n+m+k+p) * align
* <p/>
* where
* / 0, lstR = 0 / 0, (ofsR+lenR) < align
* p = | k = |
* \ 1, lstR > 0 \ 1, (ofsR+lenR) >= align
* <p/>
* The last equation come from this fact:
* <p/>
* lst = offset + length = (n+m)*align + (ofsR+lenR) =
* = (n+m)*align + (k*align + lstR) =
* = (n+m+k)*align + lstR i.e. q = n + m + k.
* <p/>
* Hence, the "order of A", o(A), the number of elements in A is:
* <p/>
* lA = o(A) = (end - start) = (n + m + k + p) * align
* <p/>
* and the number of "aligned blocks" in "A" is:
* <p/>
* nA = lA/align = n + m + k + p
* <p/>
* that is the "order" o(AA), of the set:
* <p/>
* AA = { i/align | i % align = 0, start <= i < end } =
* = { n, n+1, n+2, ..., n+m+k+p-1 }
* <p/>
* These considerations establish the basic quantities I need
* to correcly decide how the input buffer have to be read or
* written to the device, device that is supposed to be able
* to handle only aligned blocks, i.e. offsets and length
* that are intger multiples of "align".
* <p/>
* Now, we want to define what are the HEAD "H", the BODY "B" and the
* TAIL "T" of the "Aligned Buffer". Again they will defined as
* sets, "0" will be the "empty set" and we will use
* the notation nH=o(H), nB=o(B), nT=o(T) for the number of
* their elements.
* <p/>
* Let me to get rid of the "EMPTY" trivial, where nothing have to
* be done (length=0): we define H=0, B=0, T=0. So nB=nA=0.
* <p/>
* Although it can be handled as a "BODY" only buffer, we get
* rid of the "ALIGNED" trivial case too, (ofsR=0 and lstR=0),
* definining H=0, defining H=0, B=AA, T=0. So nB=nA=m.
* <p/>
* Anyway the EMPTY and the ALIGNED cases have to be handled
* separately in the code for efficiency purpouses.
* <p/>
* From now we can suppose (length>0) and (ofsR>0 or lstR>0).
* <p/>
* Again we have to handle a special case, the case where
* the "Buffer" is completely (and properly) "CONTAINED"
* inside a "single aligned block". The case is identified
* by the condition "lst <= start + align" and for the assumptions
* we have done "0 < length < align". For this case it doesn't
* make any sense to define HEAD, BODY and TAIL but we define
* as an artifact, for the sake of generality, H=0, B=0, T=0.
* We have "nB = 0 and nA = 1", so "nB = nA - 1".
* <p/>
* From now we can assume that "lst > start + align", i.e.
* the "Buffer" is "CROSSED", it "cross" the boundary of
* at least "one aligned block". We can now define:
* <p/>
* H = { i/align | i not in U, i % align = 0,
* start <= i < start + align }
* <p/>
* B = { i/align | i not in U, i % align = 0,
* start + align <= i < end - align }
* <p/>
* T = { i/align | i not in U, i % align = 0,
* end - align <= i < end }
* <p/>
* It is easy to see that "AA" is the union of the mutually disjoint
* sets "H", "B" and "T". So "nA = nH + nB + nT" where:
* <p/>
* 0 <= nH <= 1 0 <= nT <= 1 0 <= nB <= nA
* <p/>
* and that "nB = nA - nH - nT".
* <p/>
* The "Aligned Buffer" will:
* <p/>
* has an HEAD (nH=1) if and only if ofsR > 0
* has a TAIL (nT=1) if and only if lstR > 0
* <p/>
* and because we excluded the "ALIGNED" case, it has to have
* the HEAD or the TAIL because ofsR>0 or lstR>0.
* <p/>
* As an example if the "Aligned Buffer" is CROSSED, has an HEAD,
* a non empty BODY and a TAIL ( k=1, p=1, m>=1 ):
* <p/>
* H = { n }, B = { n+1, n+2, ..., n+m }, T = { n+m+1 }
* A = { n, n+1, n+2, ..., n+m, n+m+1 }
* <p/>
* Then:
* / nA-1, (HEAD) and (no TAIL), ofsR>0 and lstR>0
* |
* nB = | nA-1, (no HEAD) and (TAIL), ofsR=0 and lstR=0
* |
* \ nA-2, (HEAD) and (TAIL), ofsR>0 and lstR>0
* <p/>
* In general, using the positions we have done for the EMPTY and
* ALIGNED case:
* <p/>
* / nA=0, EMPTY , length = 0
* | nA=m, ALIGNED , ofsR = 0, lstR = 0
* nB = | nA-1, CONTAINED , lst <= start + align
* | nA-1, CROSSED(H) , lst > start + align, ofsR>0, lstR=0
* | nA-1, CROSSED(T) , lst > start + align, ofsR=0, lstR>0
* \ nA-2, CROSSED(HT) , lst > start + align, ofsR
* <p/>
* Now you can ask "why all of this semi math stuff?" ... ;-) ...
* well it helps ... believe me ... it actually is a "proof" that
* the code inside the "BufferType" inner class does a correct job ...
* I hope ... the constructor of the helper "BufferType" class
* uses the "(offset,length)" couple to compute some of this
* quantities and use them to decide if the "Buffer" at the given
* offset is EMPTY, ALIGNED, CONTAINED or CROSSED and correctly
* compute nB for all the cases.
* <p/>
* The code of the "read", "write" methods uses the type of the
* buffer, ofsR, lstR and nB to decide how it has to
* handle the "alignment" of the blocks.
* <p/>
* Infact, speaking roughly, beside the trivial "EMPTY", "ALIGNED" cases
* and the "CONTAINED" case, in the general "CROSSED" case we can optimize
* read and write for "BODY" of the buffer. We cannot do anything for
* the CONTAINED case and for the HEAD and the TAIL of the buffer.
* <p/>
* The basic idea is to reduce the number of "buffercopy" (read method)
* and the number of "read/buffercopy" (write method) to the minimum
* possible number (that actually is nA-nB). At most it will "buffercopy"
* or "read/buffercopy" 2 blocks (because 0<=nA-nB<=2).
* <p/>
* Hope this long comment will help to understand and verify the code
* and to understand how bad my brain is working ... ;-) ...
* <p/>
* gvt
*/
public class BlockAlignmentSupport implements BlockDeviceAPI {
private static final int EMPTY = 0;
private static final int CONTAINED = 1;
private static final int CROSSED = 2;
private static final int ALIGNED = 3;
private final BlockDeviceAPI parentApi;
private int alignment;
/**
* Constructor for BlockAlignmentSupport
*
* @param parentApi
* @param alignment
*/
public BlockAlignmentSupport(BlockDeviceAPI parentApi, int alignment) {
this.parentApi = parentApi;
this.alignment = alignment;
if (alignment <= 0) {
throw new IllegalArgumentException("alignment <= 0");
}
}
/**
* @return The length
* @throws IOException
* @see BlockDeviceAPI#getLength()
*/
public long getLength() throws IOException {
return parentApi.getLength();
}
/**
* @param offset
* @param dst
* @throws IOException
* @see BlockDeviceAPI#read(long, ByteBuffer)
*/
public void read(long offset, ByteBuffer dst)
throws IOException {
if (offset < 0)
throw new IllegalArgumentException("offset < 0");
final int limit = dst.limit();
final int length = dst.remaining();
final int ofsR = (int) (offset % alignment);
final long lst = offset + length;
final int lstR = (int) (lst % alignment);
final long start = offset - ofsR;
final long end = (lstR == 0) ? lst : lst - lstR + alignment;
final int lA = (int) (end - start);
final int nA = lA / alignment;
final int nB;
final int lB;
final long sB;
final int type;
int n = nA;
long s = start;
if (length == 0) { // EMPTY
type = EMPTY;
} else {
if ((ofsR != 0) || (lstR != 0)) {
if (lst <= (start + alignment)) { // CONTAINED
type = CONTAINED;
n--;
} else { // CROSSED
type = CROSSED;
if (ofsR != 0) { // HEAD
n--;
s += alignment;
}
if (lstR != 0) // TAIL
n--;
}
} else { // ALIGNED
type = ALIGNED;
}
}
nB = n;
lB = nB * alignment;
sB = s;
ByteBuffer buf = null;
try {
switch (type) {
case EMPTY:
break;
case CONTAINED:
buf = ByteBuffer.allocate(alignment);
parentApi.read(start, buf);
ByteBufferUtils.buffercopy(buf, ofsR, dst, dst.position(), length);
break;
case CROSSED:
// HEAD
if (ofsR != 0) {
buf = ByteBuffer.allocate(alignment);
parentApi.read(start, buf);
ByteBufferUtils.buffercopy(buf, ofsR, dst, dst.position(), alignment - ofsR);
}
// BODY
if (lB != 0) {
dst.limit(dst.position() + lB);
parentApi.read(sB, dst);
dst.limit(limit);
}
// TAIL
if (lstR != 0) {
if (buf == null)
buf = ByteBuffer.allocate(alignment);
else
buf.clear();
parentApi.read(end - alignment, buf);
ByteBufferUtils.buffercopy(buf, 0, dst, dst.position(), lstR);
}
break;
case ALIGNED:
parentApi.read(offset, dst);
break;
default:
throw new IllegalArgumentException("no type: shouldn't happen");
}
} finally {
dst.limit(limit);
}
buf = null;
}
/**
* @param offset
* @param src
* @throws IOException
* @see BlockDeviceAPI#write(long, ByteBuffer)
*/
public void write(long offset, ByteBuffer src)
throws IOException {
if (offset < 0)
throw new IllegalArgumentException("offset < 0");
final int limit = src.limit();
final int length = src.remaining();
final int ofsR = (int) (offset % alignment);
final long lst = offset + length;
final int lstR = (int) (lst % alignment);
final long start = offset - ofsR;
final long end = (lstR == 0) ? lst : lst - lstR + alignment;
final int lA = (int) (end - start);
final int nA = lA / alignment;
final int nB;
final int lB;
final long sB;
final int type;
int n = nA;
long s = start;
if (length == 0) { // EMPTY
type = EMPTY;
} else {
if ((ofsR != 0) || (lstR != 0)) {
if (lst <= (start + alignment)) { // CONTAINED
type = CONTAINED;
n--;
} else { // CROSSED
type = CROSSED;
if (ofsR != 0) { // HEAD
n--;
s += alignment;
}
if (lstR != 0) // TAIL
n--;
}
} else { // ALIGNED
type = ALIGNED;
}
}
nB = n;
lB = nB * alignment;
sB = s;
ByteBuffer buf = null;
try {
switch (type) {
case EMPTY:
break;
case CONTAINED:
buf = ByteBuffer.allocate(alignment);
parentApi.read(start, buf);
ByteBufferUtils.buffercopy(src, src.position(), buf, ofsR, length);
buf.clear();
parentApi.write(start, buf);
break;
case CROSSED:
// HEAD
if (ofsR != 0) {
buf = ByteBuffer.allocate(alignment);
parentApi.read(start, buf);
ByteBufferUtils.buffercopy(src, src.position(), buf, ofsR, alignment - ofsR);
buf.clear();
parentApi.write(start, buf);
}
// BODY
if (lB != 0) {
src.limit(src.position() + lB);
parentApi.write(sB, src);
src.limit(limit);
}
// TAIL
if (lstR != 0) {
if (buf == null)
buf = ByteBuffer.allocate(alignment);
else
buf.clear();
parentApi.read(end - alignment, buf);
ByteBufferUtils.buffercopy(src, src.position(), buf, 0, lstR);
buf.clear();
parentApi.write(end - alignment, buf);
}
break;
case ALIGNED:
parentApi.write(offset, src);
break;
default:
throw new IllegalArgumentException("no type: shouldn't happen");
}
} finally {
src.limit(limit);
}
buf = null;
}
/**
* @throws IOException
* @see BlockDeviceAPI#flush()
*/
public void flush() throws IOException {
parentApi.flush();
}
/**
* Gets the alignment value
*
* @return alignment
*/
public int getAlignment() {
return alignment;
}
/**
* @param i
*/
public void setAlignment(int i) {
alignment = i;
}
}