Index: src/java/org/apache/lucene/index/IOContext.java
===================================================================
--- src/java/org/apache/lucene/index/IOContext.java	(revision 0)
+++ src/java/org/apache/lucene/index/IOContext.java	(revision 0)
@@ -0,0 +1,67 @@
+package org.apache.lucene.index;
+
+import org.apache.lucene.store.IOFactory;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class IOContext {
+  private final int bufferSize;
+  private IOFactory factory;
+  
+  public IOContext(int bufferSize, IOFactory factory) {
+    this.bufferSize = bufferSize;
+    this.factory = factory;
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + bufferSize;
+    result = prime * result + ((factory == null) ? 0 : factory.getClass().hashCode());
+    return result;
+  }
+  
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    IOContext other = (IOContext) obj;
+    if (bufferSize != other.bufferSize) return false;
+    if (factory == null) {
+      if (other.factory != null) return false;
+    } else if (!factory.getClass().equals(other.factory.getClass())) return false;
+    return true;
+  }
+
+
+
+  @Override
+  public String toString() {
+    return "IOContext [bufferSize=" + bufferSize + ", factory=" + factory + "]";
+  }
+
+  public IOFactory getDirectoryFactory() {
+    return factory;
+  }
+  
+  public int getBufferSize() {
+    return bufferSize;
+  }
+}
Index: src/java/org/apache/lucene/index/SegmentReader.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentReader.java	(revision 1057360)
+++ src/java/org/apache/lucene/index/SegmentReader.java	(working copy)
@@ -34,12 +34,14 @@
 import org.apache.lucene.search.Similarity;
 import org.apache.lucene.store.BufferedIndexInput;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IOFactory;
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.util.BitVector;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.CloseableThreadLocal;
 import org.apache.lucene.index.codecs.FieldsProducer;
+import org.apache.lucene.index.IndexWriter.ReaderKey;
 import org.apache.lucene.search.FieldCache; // not great (circular); used only to purge FieldCache entry on close
 import org.apache.lucene.util.BytesRef;
 
@@ -50,7 +52,7 @@
   protected boolean readOnly;
 
   private SegmentInfo si;
-  private int readBufferSize;
+  IOContext ioContext;
   private final ReaderContext readerContext = new AtomicReaderContext(this);
   CloseableThreadLocal<FieldsReader> fieldsReaderLocal = new FieldsReaderLocal();
   CloseableThreadLocal<TermVectorsReader> termVectorsLocal = new CloseableThreadLocal<TermVectorsReader>();
@@ -503,15 +505,35 @@
   public static SegmentReader get(boolean readOnly, SegmentInfo si, int termInfosIndexDivisor) throws CorruptIndexException, IOException {
     return get(readOnly, si.dir, si, BufferedIndexInput.BUFFER_SIZE, true, termInfosIndexDivisor);
   }
-
+  
   /**
    * @throws CorruptIndexException if the index is corrupt
    * @throws IOException if there is a low-level IO error
    */
   public static SegmentReader get(boolean readOnly,
+      Directory dir,
+      SegmentInfo si,
+      int readBufferSize,
+      boolean doOpenStores,
+      int termInfosIndexDivisor)
+    throws CorruptIndexException, IOException {
+    IOContext ioContext = new IOContext(readBufferSize, dir.getIOFactory());
+    return get(readOnly,
+        dir,
+        si,
+        ioContext,
+        doOpenStores,
+        termInfosIndexDivisor);
+  }
+  
+  /**
+   * @throws CorruptIndexException if the index is corrupt
+   * @throws IOException if there is a low-level IO error
+   */
+  public static SegmentReader get(boolean readOnly,
                                   Directory dir,
                                   SegmentInfo si,
-                                  int readBufferSize,
+                                  IOContext ioContext,
                                   boolean doOpenStores,
                                   int termInfosIndexDivisor)
     throws CorruptIndexException, IOException {
@@ -519,17 +541,17 @@
     SegmentReader instance = new SegmentReader();
     instance.readOnly = readOnly;
     instance.si = si;
-    instance.readBufferSize = readBufferSize;
+    instance.ioContext = ioContext;
 
     boolean success = false;
 
     try {
-      instance.core = new CoreReaders(instance, dir, si, readBufferSize, termInfosIndexDivisor);
+      instance.core = new CoreReaders(instance, dir, si, ioContext.getBufferSize(), termInfosIndexDivisor);
       if (doOpenStores) {
         instance.core.openDocStores(si);
       }
       instance.loadDeletedDocs();
-      instance.openNorms(instance.core.cfsDir, readBufferSize);
+      instance.openNorms(instance.core.cfsDir, ioContext.getBufferSize());
       success = true;
     } finally {
 
@@ -662,7 +684,7 @@
       clone.core = core;
       clone.readOnly = openReadOnly;
       clone.si = si;
-      clone.readBufferSize = readBufferSize;
+      clone.ioContext = ioContext;
       clone.pendingDeleteCount = pendingDeleteCount;
 
       if (!openReadOnly && hasChanges) {
@@ -707,7 +729,7 @@
 
       // If we are not cloning, then this will open anew
       // any norms that have changed:
-      clone.openNorms(si.getUseCompoundFile() ? core.getCFSReader() : directory(), readBufferSize);
+      clone.openNorms(si.getUseCompoundFile() ? core.getCFSReader() : directory(), ioContext.getBufferSize());
 
       success = true;
     } finally {
@@ -1250,4 +1272,8 @@
   public int getTermInfosIndexDivisor() {
     return core.termsIndexDivisor;
   }
+  
+  ReaderKey getReaderKey() {
+    return new ReaderKey(si, ioContext);
+  }
 }
Index: src/java/org/apache/lucene/index/CompoundFileReader.java
===================================================================
--- src/java/org/apache/lucene/index/CompoundFileReader.java	(revision 1057360)
+++ src/java/org/apache/lucene/index/CompoundFileReader.java	(working copy)
@@ -22,6 +22,7 @@
 import org.apache.lucene.store.BufferedIndexInput;
 import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.store.Lock;
+import org.apache.lucene.store.IOFactory;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -327,4 +328,7 @@
 
     }
     
+    public IOFactory getIOFactory() {
+      throw new UnsupportedOperationException();
+    }
 }
Index: src/java/org/apache/lucene/index/IndexWriter.java
===================================================================
--- src/java/org/apache/lucene/index/IndexWriter.java	(revision 1057360)
+++ src/java/org/apache/lucene/index/IndexWriter.java	(working copy)
@@ -367,13 +367,13 @@
 
   class ReaderPool {
 
-    private final Map<SegmentInfo,SegmentReader> readerMap = new HashMap<SegmentInfo,SegmentReader>();
+    private final Map<ReaderKey,SegmentReader> readerMap = new HashMap<ReaderKey,SegmentReader>();
 
     /** Forcefully clear changes for the specified segments,
      *  and remove from the pool.   This is called on successful merge. */
     synchronized void clear(SegmentInfos infos) throws IOException {
       if (infos == null) {
-        for (Map.Entry<SegmentInfo,SegmentReader> ent: readerMap.entrySet()) {
+        for (Map.Entry<ReaderKey,SegmentReader> ent: readerMap.entrySet()) {
           ent.getValue().hasChanges = false;
         }
       } else {
@@ -425,9 +425,9 @@
      */
     public synchronized boolean release(SegmentReader sr, boolean drop) throws IOException {
 
-      final boolean pooled = readerMap.containsKey(sr.getSegmentInfo());
+      final boolean pooled = readerMap.containsKey(sr.getReaderKey());
 
-      assert !pooled || readerMap.get(sr.getSegmentInfo()) == sr;
+      assert !pooled || readerMap.get(sr.getReaderKey()) == sr;
 
       // Drop caller's ref; for an external reader (not
       // pooled), this decRef will close it
@@ -452,7 +452,7 @@
 
         // We are the last ref to this reader; since we're
         // not pooling readers, we release it:
-        readerMap.remove(sr.getSegmentInfo());
+        readerMap.remove(sr.getReaderKey());
 
         return hasChanges;
       }
@@ -467,10 +467,10 @@
       // sync'd on IW:
       assert Thread.holdsLock(IndexWriter.this);
 
-      Iterator<Map.Entry<SegmentInfo,SegmentReader>> iter = readerMap.entrySet().iterator();
+      Iterator<Map.Entry<ReaderKey,SegmentReader>> iter = readerMap.entrySet().iterator();
       while (iter.hasNext()) {
         
-        Map.Entry<SegmentInfo,SegmentReader> ent = iter.next();
+        Map.Entry<ReaderKey,SegmentReader> ent = iter.next();
 
         SegmentReader sr = ent.getValue();
         if (sr.hasChanges) {
@@ -503,7 +503,7 @@
       // sync'd on IW:
       assert Thread.holdsLock(IndexWriter.this);
 
-      for (Map.Entry<SegmentInfo,SegmentReader> ent : readerMap.entrySet()) {
+      for (Map.Entry<ReaderKey,SegmentReader> ent : readerMap.entrySet()) {
 
         SegmentReader sr = ent.getValue();
         if (sr.hasChanges) {
@@ -524,7 +524,8 @@
      * it when you're done (ie, do not call release()).
      */
     public synchronized SegmentReader getReadOnlyClone(SegmentInfo info, boolean doOpenStores, int termInfosIndexDivisor) throws IOException {
-      SegmentReader sr = get(info, doOpenStores, BufferedIndexInput.BUFFER_SIZE, termInfosIndexDivisor);
+      IOContext ioContext = new IOContext(BufferedIndexInput.BUFFER_SIZE, info.dir.getIOFactory());
+      SegmentReader sr = get(info, ioContext, doOpenStores, termInfosIndexDivisor);
       try {
         return (SegmentReader) sr.clone(true);
       } finally {
@@ -541,7 +542,8 @@
      * @throws IOException
      */
     public synchronized SegmentReader get(SegmentInfo info, boolean doOpenStores) throws IOException {
-      return get(info, doOpenStores, BufferedIndexInput.BUFFER_SIZE, config.getReaderTermsIndexDivisor());
+      IOContext ioContext = new IOContext(BufferedIndexInput.BUFFER_SIZE, info.dir.getIOFactory());
+      return get(info, ioContext, doOpenStores, config.getReaderTermsIndexDivisor());
     }
 
     /**
@@ -555,22 +557,17 @@
      * @param termsIndexDivisor
      * @throws IOException
      */
-    public synchronized SegmentReader get(SegmentInfo info, boolean doOpenStores, int readBufferSize, int termsIndexDivisor) throws IOException {
-
-      if (poolReaders) {
-        readBufferSize = BufferedIndexInput.BUFFER_SIZE;
-      }
-
+    public synchronized SegmentReader get(SegmentInfo info, IOContext ioContext, boolean doOpenStores, int termsIndexDivisor) throws IOException {
       SegmentReader sr = readerMap.get(info);
       if (sr == null) {
         // TODO: we may want to avoid doing this while
         // synchronized
         // Returns a ref, which we xfer to readerMap:
-        sr = SegmentReader.get(false, info.dir, info, readBufferSize, doOpenStores, termsIndexDivisor);
+        sr = SegmentReader.get(false, info.dir, info, ioContext, doOpenStores, termsIndexDivisor);
 
         if (info.dir == directory) {
           // Only pool if reader is not external
-          readerMap.put(info, sr);
+          readerMap.put(sr.getReaderKey(), sr);
         }
       } else {
         if (doOpenStores) {
@@ -605,8 +602,42 @@
     }
   }
   
+  public static class ReaderKey {
+    final SegmentInfo segmentInfo;
+    final IOContext ioContext;
+    
+    public ReaderKey(SegmentInfo segmentInfo, IOContext ioContext) {
+      this.segmentInfo = segmentInfo;
+      this.ioContext = ioContext;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result
+          + ((ioContext == null) ? 0 : ioContext.hashCode());
+      result = prime * result
+          + ((segmentInfo == null) ? 0 : segmentInfo.hashCode());
+      return result;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      ReaderKey other = (ReaderKey) obj;
+      if (ioContext == null) {
+        if (other.ioContext != null) return false;
+      } else if (!ioContext.equals(other.ioContext)) return false;
+      if (segmentInfo == null) {
+        if (other.segmentInfo != null) return false;
+      } else if (!segmentInfo.equals(other.segmentInfo)) return false;
+      return true;
+    }
+  }
   
-  
   /**
    * Obtain the number of deleted docs for a pooled reader.
    * If the reader isn't being pooled, the segmentInfo's 
@@ -3048,6 +3079,15 @@
     }
   }
 
+  protected IOContext getMergeIOContext() {
+    IOContext mergeIOContext = config.getMergeIOContext();
+    if (mergeIOContext == null) {
+      return new IOContext(BufferedIndexInput.BUFFER_SIZE, directory.getIOFactory());
+    } else {
+      return mergeIOContext;
+    }
+  }
+  
   /** Does the actual (time-consuming) work of the merge,
    *  but without holding synchronized lock on IndexWriter
    *  instance */
@@ -3080,14 +3120,15 @@
     boolean success = false;
     try {
       int totDocCount = 0;
-
       for (int i = 0; i < numSegments; i++) {
         final SegmentInfo info = sourceSegments.info(i);
 
         // Hold onto the "live" reader; we will use this to
         // commit merged deletes
-        SegmentReader reader = merge.readers[i] = readerPool.get(info, true,
-                                                                 MERGE_READ_BUFFER_SIZE,
+        IOContext ioContext = getMergeIOContext();
+        SegmentReader reader = merge.readers[i] = readerPool.get(info, 
+                                                                 ioContext, 
+                                                                 true,
                                                                  -config.getReaderTermsIndexDivisor());
 
         // We clone the segment readers because other
@@ -3201,8 +3242,7 @@
       // TODO: in the non-realtime case, we may want to only
       // keep deletes (it's costly to open entire reader
       // when we just need deletes)
-
-      final SegmentReader mergedReader = readerPool.get(merge.info, loadDocStores, BufferedIndexInput.BUFFER_SIZE, termsIndexDivisor);
+      final SegmentReader mergedReader = readerPool.get(merge.info, getDefaultIOContext(), loadDocStores, termsIndexDivisor);
       try {
         if (poolReaders && mergedSegmentWarmer != null) {
           mergedSegmentWarmer.warm(mergedReader);
@@ -3236,6 +3276,10 @@
     return mergedDocCount;
   }
 
+  IOContext getDefaultIOContext() {
+    return new IOContext(BufferedIndexInput.BUFFER_SIZE, directory.getIOFactory());
+  }
+  
   synchronized void addMergeException(MergePolicy.OneMerge merge) {
     assert merge.getException() != null;
     if (!mergeExceptions.contains(merge) && mergeGen == merge.mergeGen)
Index: src/java/org/apache/lucene/index/IndexWriterConfig.java
===================================================================
--- src/java/org/apache/lucene/index/IndexWriterConfig.java	(revision 1057360)
+++ src/java/org/apache/lucene/index/IndexWriterConfig.java	(working copy)
@@ -22,6 +22,7 @@
 import org.apache.lucene.index.IndexWriter.IndexReaderWarmer;
 import org.apache.lucene.index.codecs.CodecProvider;
 import org.apache.lucene.search.Similarity;
+import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.Version;
 
 /**
@@ -128,6 +129,7 @@
   private int maxThreadStates;
   private boolean readerPooling;
   private int readerTermsIndexDivisor;
+  private IOContext mergeIOContext;
   
   // required for clone
   private Version matchVersion;
@@ -160,6 +162,7 @@
     maxThreadStates = DEFAULT_MAX_THREAD_STATES;
     readerPooling = DEFAULT_READER_POOLING;
     readerTermsIndexDivisor = DEFAULT_READER_TERMS_INDEX_DIVISOR;
+    mergeIOContext = null;
   }
   
   @Override
@@ -603,6 +606,14 @@
     return readerTermsIndexDivisor;
   }
   
+  public void setMergeIOContext(IOContext mergeIOContext) {
+    this.mergeIOContext = mergeIOContext;
+  }
+  
+  public IOContext getMergeIOContext() {
+    return mergeIOContext;
+  }
+  
   @Override
   public String toString() {
     StringBuilder sb = new StringBuilder();
@@ -626,6 +637,7 @@
     sb.append("maxThreadStates=").append(maxThreadStates).append("\n");
     sb.append("readerPooling=").append(readerPooling).append("\n");
     sb.append("readerTermsIndexDivisor=").append(readerTermsIndexDivisor).append("\n");
+    sb.append("mergeIOContext=").append(mergeIOContext == null ? "null" : mergeIOContext).append("\n");
     return sb.toString();
   }
 }
Index: src/java/org/apache/lucene/store/IOFactory.java
===================================================================
--- src/java/org/apache/lucene/store/IOFactory.java	(revision 0)
+++ src/java/org/apache/lucene/store/IOFactory.java	(revision 0)
@@ -0,0 +1,28 @@
+package org.apache.lucene.store;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IOContext;
+
+public abstract class IOFactory {
+  public abstract IndexOutput createOutput(String name, IOContext ioContext, Directory directory) throws IOException;
+  
+  public abstract IndexInput openInput(String name, IOContext ioContext, Directory directory) throws IOException;
+}
Index: src/java/org/apache/lucene/store/NIOFSDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/NIOFSDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/NIOFSDirectory.java	(working copy)
@@ -24,6 +24,8 @@
 import java.nio.channels.FileChannel;
 import java.util.concurrent.Future; // javadoc
 
+import org.apache.lucene.index.IOContext;
+
 /**
  * An {@link FSDirectory} implementation that uses java.nio's FileChannel's
  * positional read, which allows multiple threads to read from the same file
@@ -178,4 +180,16 @@
       }
     }
   }
+  
+  public IOFactory getIOFactory() {
+    return new NIOFSDirectoryFactory();
+  }
+  
+  public static class NIOFSDirectoryFactory extends FSIOFactory {
+    public IndexInput openInput(String name, IOContext ioContext, Directory directory) throws IOException {
+      FSDirectory fsdir = (FSDirectory)directory;
+      int bufferSize = ioContext.getBufferSize();
+      return new NIOFSIndexInput(new File(fsdir.getDirectory(), name), bufferSize, fsdir.getReadChunkSize());
+    }
+  }
 }
Index: src/java/org/apache/lucene/store/MMapDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/MMapDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/MMapDirectory.java	(working copy)
@@ -31,6 +31,7 @@
 import java.security.PrivilegedActionException;
 import java.lang.reflect.Method;
 
+import org.apache.lucene.index.IOContext;
 import org.apache.lucene.util.Constants;
 
 /** File-based {@link Directory} implementation that uses
@@ -80,7 +81,7 @@
 public class MMapDirectory extends FSDirectory {
   private boolean useUnmapHack = UNMAP_SUPPORTED;
   public static final int DEFAULT_MAX_BUFF = Constants.JRE_IS_64BIT ? Integer.MAX_VALUE : (256 * 1024 * 1024);
-  private int maxBBuf = DEFAULT_MAX_BUFF;
+  private static int maxBBuf = DEFAULT_MAX_BUFF;
 
   /** Create a new MMapDirectory for the named location.
    *
@@ -151,6 +152,10 @@
    * that mmapped files cannot be modified or deleted.
    */
   final void cleanMapping(final ByteBuffer buffer) throws IOException {
+    cleanMapping(buffer, useUnmapHack);
+  }
+    
+  static final void cleanMapping(final ByteBuffer buffer, boolean useUnmapHack) throws IOException {
     if (useUnmapHack) {
       try {
         AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@@ -214,7 +219,7 @@
     }
   }
 
-  private class MMapIndexInput extends IndexInput {
+  private static class MMapIndexInput extends IndexInput {
 
     private ByteBuffer buffer;
     private final long length;
@@ -300,7 +305,7 @@
       // unmap the buffer (if enabled) and at least unset it for GC
       try {
         if (isClone || buffer == null) return;
-        cleanMapping(buffer);
+        cleanMapping(buffer, UNMAP_SUPPORTED);
       } finally {
         buffer = null;
       }
@@ -310,7 +315,7 @@
   // Because Java's ByteBuffer uses an int to address the
   // values, it's necessary to access a file >
   // Integer.MAX_VALUE in size using multiple byte buffers.
-  private class MultiMMapIndexInput extends IndexInput {
+  private static class MultiMMapIndexInput extends IndexInput {
   
     private ByteBuffer[] buffers;
     private int[] bufSizes; // keep here, ByteBuffer.size() method is optional
@@ -467,7 +472,7 @@
         for (int bufNr = 0; bufNr < buffers.length; bufNr++) {
           // unmap the buffer (if enabled) and at least unset it for GC
           try {
-            cleanMapping(buffers[bufNr]);
+            cleanMapping(buffers[bufNr], UNMAP_SUPPORTED);
           } finally {
             buffers[bufNr] = null;
           }
@@ -477,4 +482,26 @@
       }
     }
   }
+  
+  public IOFactory getIOFactory() {
+    return new MMapIOFactory();
+  }
+  
+  public static class MMapIOFactory extends FSIOFactory {
+    @Override
+    public IndexInput openInput(String name, IOContext ioContext,
+        Directory directory) throws IOException {
+      FSDirectory fsdir = (FSDirectory)directory;
+      fsdir.ensureOpen();
+      File f = new File(fsdir.getDirectory(), name);
+      RandomAccessFile raf = new RandomAccessFile(f, "r");
+      try {
+        return (raf.length() <= maxBBuf)
+               ? (IndexInput) new MMapIndexInput(raf)
+               : (IndexInput) new MultiMMapIndexInput(raf, maxBBuf);
+      } finally {
+        raf.close();
+      }
+    }
+  }
 }
Index: src/java/org/apache/lucene/store/Directory.java
===================================================================
--- src/java/org/apache/lucene/store/Directory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/Directory.java	(working copy)
@@ -22,6 +22,7 @@
 import java.io.Closeable;
 import java.util.Collection; // for javadocs
 
+import org.apache.lucene.index.IOContext;
 import org.apache.lucene.util.IOUtils;
 
 /** A Directory is a flat list of files.  Files may be written once, when they
@@ -94,6 +95,11 @@
   public abstract IndexOutput createOutput(String name)
        throws IOException;
 
+  public IndexOutput createOutput(String name, IOContext ioContext) 
+       throws IOException {
+    return createOutput(name);
+  }
+  
   /**
    * Ensure that any writes to these files are moved to
    * stable storage.  Lucene uses this to properly commit
@@ -121,7 +127,11 @@
   public IndexInput openInput(String name, int bufferSize) throws IOException {
     return openInput(name);
   }
-
+  
+  public IndexInput openInput(String name, IOContext ioContext) throws IOException {
+    return openInput(name);
+  }
+  
   /** Construct a {@link Lock}.
    * @param name the name of the lock file
    */
@@ -223,4 +233,6 @@
     if (!isOpen)
       throw new AlreadyClosedException("this Directory is closed");
   }
+  
+  public abstract IOFactory getIOFactory();
 }
Index: src/java/org/apache/lucene/store/RAMDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/RAMDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/RAMDirectory.java	(working copy)
@@ -28,6 +28,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.lucene.index.IOContext;
 import org.apache.lucene.util.ThreadInterruptedException;
 
 /**
@@ -221,4 +222,20 @@
     isOpen = false;
     fileMap.clear();
   }
+  
+  public IOFactory getIOFactory() {
+    return new RAMIOFactory();
+  }
+  
+  public static class RAMIOFactory extends IOFactory {
+    public IndexOutput createOutput(String name, IOContext ioContext, Directory directory) throws IOException {
+      RAMDirectory ramDir = (RAMDirectory)directory;
+      return ramDir.createOutput(name);
+    }
+    
+    public IndexInput openInput(String name, IOContext ioContext, Directory directory) throws IOException {
+      RAMDirectory ramDir = (RAMDirectory)directory;
+      return ramDir.openInput(name);
+    }
+  }
 }
Index: src/java/org/apache/lucene/store/SimpleFSDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/SimpleFSDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/SimpleFSDirectory.java	(working copy)
@@ -21,6 +21,8 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 
+import org.apache.lucene.index.IOContext;
+
 /** A straightforward implementation of {@link FSDirectory}
  *  using java.io.RandomAccessFile.  However, this class has
  *  poor concurrent performance (multiple threads will
@@ -168,4 +170,18 @@
       out.copyBytes(this, numBytes);
     }
   }
+  
+  public IOFactory getIOFactory() {
+    return new SimpleFSIOFactory();
+  }
+  
+  public static class SimpleFSIOFactory extends FSIOFactory {
+    @Override
+    public IndexInput openInput(String name, IOContext ioContext, Directory directory)
+        throws IOException {
+      SimpleFSDirectory fsDir = (SimpleFSDirectory)directory;
+      int bufferSize = ioContext.getBufferSize();
+      return new SimpleFSIndexInput(new File(fsDir.getDirectory(), name), bufferSize, fsDir.getReadChunkSize());
+    }
+  }
 }
Index: src/java/org/apache/lucene/store/FileSwitchDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/FileSwitchDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/FileSwitchDirectory.java	(working copy)
@@ -153,4 +153,8 @@
   public IndexInput openInput(String name) throws IOException {
     return getDirectory(name).openInput(name);
   }
+  
+  public IOFactory getIOFactory() {
+    throw new UnsupportedOperationException();
+  }
 }
Index: src/java/org/apache/lucene/store/FSDirectory.java
===================================================================
--- src/java/org/apache/lucene/store/FSDirectory.java	(revision 1057360)
+++ src/java/org/apache/lucene/store/FSDirectory.java	(working copy)
@@ -33,6 +33,7 @@
 
 import org.apache.lucene.util.ThreadInterruptedException;
 import org.apache.lucene.util.Constants;
+import org.apache.lucene.index.IOContext;
 
 /**
  * <a name="subclasses"/>
@@ -132,6 +133,7 @@
   protected final File directory; // The underlying filesystem directory
   protected final Set<String> staleFiles = synchronizedSet(new HashSet<String>()); // Files written, but not yet sync'ed
   private int chunkSize = DEFAULT_READ_CHUNK_SIZE; // LUCENE-1566
+  private IOFactory defaultDirectoryFactory;
 
   // returns the canonical version of the directory, creating it if it doesn't exist.
   private static File getCanonicalPath(File file) throws IOException {
@@ -311,7 +313,35 @@
     ensureCanWrite(name);
     return new FSIndexOutput(this, name);
   }
+  
+  /** Creates an IndexOutput for the file with the given name. */
+  @Override
+  public IndexOutput createOutput(String name, IOContext ioContext) throws IOException {
+    ensureOpen();
 
+    ensureCanWrite(name);
+    
+    IOFactory ioFactory = getIOFactory(ioContext);
+    return ioFactory.createOutput(name, ioContext, this);
+  }
+
+  protected IOFactory getIOFactory(IOContext ioContext) {
+    if (ioContext.getDirectoryFactory() == null) {
+      return defaultDirectoryFactory;
+    } else {
+      return null;
+    }
+  }
+  
+  public abstract static class FSIOFactory extends IOFactory {
+    @Override
+    public IndexOutput createOutput(String name, IOContext ioContext, Directory directory)
+        throws IOException {
+      FSDirectory fsDirectory = (FSDirectory)directory;
+      return new FSIndexOutput(fsDirectory, name);
+    }
+  }
+  
   protected void ensureCanWrite(String name) throws IOException {
     if (!directory.exists())
       if (!directory.mkdirs())
@@ -344,6 +374,12 @@
     ensureOpen();
     return openInput(name, BufferedIndexInput.BUFFER_SIZE);
   }
+  
+  @Override
+  public IndexInput openInput(String name, IOContext ioContext) throws IOException {
+    ensureOpen();
+    return getIOFactory(ioContext).openInput(name, ioContext, this);
+  }
 
   /**
    * So we can do some byte-to-hexchar conversion below
Index: contrib/misc/src/java/org/apache/lucene/store/DirectIOLinuxDirectory.java
===================================================================
--- contrib/misc/src/java/org/apache/lucene/store/DirectIOLinuxDirectory.java	(revision 1057360)
+++ contrib/misc/src/java/org/apache/lucene/store/DirectIOLinuxDirectory.java	(working copy)
@@ -25,8 +25,10 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 
+import org.apache.lucene.index.IOContext;
 import org.apache.lucene.store.Directory; // javadoc
 import org.apache.lucene.store.NativeFSLockFactory; // javadoc
+import org.apache.lucene.store.NIOFSDirectory.NIOFSDirectoryFactory;
 
 /**
  * An {@link Directory} implementation that uses the
@@ -362,4 +364,27 @@
       }
     }
   }
+  
+  public IOFactory getIOFactory() {
+    return new NIOFSDirectoryFactory();
+  }
+  
+  public static class DirectIOLinuxIOFactory extends IOFactory {
+    @Override
+    public IndexOutput createOutput(String name, IOContext ioContext,
+        Directory directory) throws IOException {
+      FSDirectory fsdir = (FSDirectory)directory;
+      fsdir.ensureCanWrite(name);
+      int forcedBufferSize = ioContext.getBufferSize();
+      return new DirectIOLinuxIndexOutput(new File(fsdir.getDirectory(), name), forcedBufferSize == 0 ? BufferedIndexOutput.BUFFER_SIZE : forcedBufferSize);
+    }
+
+    @Override
+    public IndexInput openInput(String name, IOContext ioContext,
+        Directory directory) throws IOException {
+      FSDirectory fsdir = (FSDirectory)directory;
+      int bufferSize = ioContext.getBufferSize();
+      return new DirectIOLinuxIndexInput(new File(fsdir.getDirectory(), name), bufferSize);
+    }
+  }
 }
