content: 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;
  		      }
  		    }
  		  }
  
  }
  