package org.apache.lucene.store;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
public class LinkMMapDirectory extends LinkFSDirectory{
private boolean useUnmapHack = MMapDirectory.UNMAP_SUPPORTED;
private int chunkSizePower;
public void setUseUnmap(final boolean useUnmapHack) {
if (useUnmapHack && !MMapDirectory.UNMAP_SUPPORTED)
throw new IllegalArgumentException("Unmap hack not supported on this platform!");
this.useUnmapHack=useUnmapHack;
}
public boolean getUseUnmap() {
return useUnmapHack;
}
final void cleanMapping(final ByteBuffer buffer) throws IOException {
if (useUnmapHack) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
final Method getCleanerMethod = buffer.getClass()
.getMethod("cleaner");
getCleanerMethod.setAccessible(true);
final Object cleaner = getCleanerMethod.invoke(buffer);
if (cleaner != null) {
cleaner.getClass().getMethod("clean")
.invoke(cleaner);
}
return null;
}
});
} catch (PrivilegedActionException e) {
final IOException ioe = new IOException("unable to unmap the mapped buffer");
ioe.initCause(e.getCause());
throw ioe;
}
}
}
public final void setMaxChunkSize(final int maxChunkSize) {
if (maxChunkSize <= 0)
throw new IllegalArgumentException("Maximum chunk size for mmap must be >0");
this.chunkSizePower = 31 - Integer.numberOfLeadingZeros(maxChunkSize);
assert this.chunkSizePower >= 0 && this.chunkSizePower <= 30;
}
public final int getMaxChunkSize() {
return 1 << chunkSizePower;
}
public LinkMMapDirectory(File path, LockFactory lockFactory) throws IOException {
super(path, lockFactory);
setMaxChunkSize(MMapDirectory.DEFAULT_MAX_BUFF);
}
/** Create a new MMapDirectory for the named location and {@link NativeFSLockFactory}.
*
* @param path the path of the directory
* @throws IOException
*/
public LinkMMapDirectory(File path) throws IOException {
super(path, null);
setMaxChunkSize(MMapDirectory.DEFAULT_MAX_BUFF);
}
public IndexInput openInput(String name, int bufferSize) throws IOException {
File file=null;
if (!this.links.containsKey(name)) {
file=new File(directory, name);
}else{
file = this.links.get(name);
}
RandomAccessFile raf = new RandomAccessFile(file, "r");
try {
return new LinkMMapIndexInput("MMapIndexInput(path=\"" + file + "\")", raf, chunkSizePower);
} finally {
raf.close();
}
}
private final class LinkMMapIndexInput extends IndexInput {
private ByteBuffer[] buffers;
private final long length, chunkSizeMask, chunkSize;
private final int chunkSizePower;
private int curBufIndex;
private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex]
private boolean isClone = false;
LinkMMapIndexInput(String resourceDescription, RandomAccessFile raf, int chunkSizePower) throws IOException {
super(resourceDescription);
this.length = raf.length();
this.chunkSizePower = chunkSizePower;
this.chunkSize = 1L << chunkSizePower;
this.chunkSizeMask = chunkSize - 1L;
if (chunkSizePower < 0 || chunkSizePower > 30)
throw new IllegalArgumentException("Invalid chunkSizePower used for ByteBuffer size: " + chunkSizePower);
if ((length >>> chunkSizePower) >= Integer.MAX_VALUE)
throw new IllegalArgumentException("RandomAccessFile too big for chunk size: " + raf.toString());
// we always allocate one more buffer, the last one may be a 0 byte one
final int nrBuffers = (int) (length >>> chunkSizePower) + 1;
//System.out.println("length="+length+", chunkSizePower=" + chunkSizePower + ", chunkSizeMask=" + chunkSizeMask + ", nrBuffers=" + nrBuffers);
this.buffers = new ByteBuffer[nrBuffers];
long bufferStart = 0L;
FileChannel rafc = raf.getChannel();
for (int bufNr = 0; bufNr < nrBuffers; bufNr++) {
int bufSize = (int) ( (length > (bufferStart + chunkSize))
? chunkSize
: (length - bufferStart)
);
this.buffers[bufNr] = rafc.map(MapMode.READ_ONLY, bufferStart, bufSize);
bufferStart += bufSize;
}
seek(0L);
}
@Override
public byte readByte() throws IOException {
try {
return curBuf.get();
} catch (BufferUnderflowException e) {
do {
curBufIndex++;
if (curBufIndex >= buffers.length) {
throw new IOException("read past EOF: " + this);
}
curBuf = buffers[curBufIndex];
curBuf.position(0);
} while (!curBuf.hasRemaining());
return curBuf.get();
}
}
@Override
public void readBytes(byte[] b, int offset, int len) throws IOException {
try {
curBuf.get(b, offset, len);
} catch (BufferUnderflowException e) {
int curAvail = curBuf.remaining();
while (len > curAvail) {
curBuf.get(b, offset, curAvail);
len -= curAvail;
offset += curAvail;
curBufIndex++;
if (curBufIndex >= buffers.length) {
throw new IOException("read past EOF: " + this);
}
curBuf = buffers[curBufIndex];
curBuf.position(0);
curAvail = curBuf.remaining();
}
curBuf.get(b, offset, len);
}
}
@Override
public int readInt() throws IOException {
try {
return curBuf.getInt();
} catch (BufferUnderflowException e) {
return super.readInt();
}
}
@Override
public long readLong() throws IOException {
try {
return curBuf.getLong();
} catch (BufferUnderflowException e) {
return super.readLong();
}
}
@Override
public long getFilePointer() {
return (((long) curBufIndex) << chunkSizePower) + curBuf.position();
}
@Override
public void seek(long pos) throws IOException {
// we use >> here to preserve negative, so we will catch AIOOBE:
final int bi = (int) (pos >> chunkSizePower);
try {
final ByteBuffer b = buffers[bi];
b.position((int) (pos & chunkSizeMask));
// write values, on exception all is unchanged
this.curBufIndex = bi;
this.curBuf = b;
} catch (ArrayIndexOutOfBoundsException aioobe) {
if (pos < 0L) {
throw new IllegalArgumentException("Seeking to negative position: " + this);
}
throw new IOException("seek past EOF");
} catch (IllegalArgumentException iae) {
if (pos < 0L) {
throw new IllegalArgumentException("Seeking to negative position: " + this);
}
throw new IOException("seek past EOF: " + this);
}
}
@Override
public long length() {
return length;
}
@Override
public Object clone() {
if (buffers == null) {
throw new AlreadyClosedException("MMapIndexInput already closed: " + this);
}
final LinkMMapIndexInput clone = (LinkMMapIndexInput)super.clone();
clone.isClone = true;
clone.buffers = new ByteBuffer[buffers.length];
// Since most clones will use only one buffer, duplicate() could also be
// done lazy in clones, e.g. when adapting curBuf.
for (int bufNr = 0; bufNr < buffers.length; bufNr++) {
clone.buffers[bufNr] = buffers[bufNr].duplicate();
}
try {
clone.seek(getFilePointer());
} catch(IOException ioe) {
throw new RuntimeException("Should never happen: " + this, ioe);
}
return clone;
}
@Override
public void close() throws IOException {
try {
if (isClone || buffers == null) return;
for (int bufNr = 0; bufNr < buffers.length; bufNr++) {
try {
cleanMapping(buffers[bufNr]);
} finally {
buffers[bufNr] = null;
}
}
} finally {
buffers = null;
}
}
}
}