Index: lucene/misc/src/java/org/apache/lucene/store/CachingRAMDirectory.java
===================================================================
--- lucene/misc/src/java/org/apache/lucene/store/CachingRAMDirectory.java	(revision 0)
+++ lucene/misc/src/java/org/apache/lucene/store/CachingRAMDirectory.java	(working copy)
@@ -0,0 +1,252 @@
+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 java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+
+// nocommit off heap alloc?  native (unsafe) alloc?
+
+// TODO
+//   - we could segment the byte[] so we can handle > 2.1 GB
+//     files
+
+/** Wraps another directory but on opening an input fully
+ *  loads the file's contents into a single byte[] and reads
+ *  from that.  This cannot handle files over ~2.1 GB! */
+public class CachingRAMDirectory extends Directory implements Cloneable {
+  private final Directory delegate;
+
+  private final ConcurrentHashMap<String,CachedIndexInput> files = new ConcurrentHashMap<String,CachedIndexInput>();
+
+  public CachingRAMDirectory(Directory delegate) {
+    this.delegate = delegate;
+    this.lockFactory = delegate.getLockFactory();
+  }
+
+  @Override
+  public synchronized Lock makeLock(String name) {
+    return delegate.makeLock(name);
+  }
+
+  @Override
+  public synchronized void clearLock(String name) throws IOException {
+    delegate.clearLock(name);
+  }
+
+  @Override
+  public synchronized void setLockFactory(LockFactory lockFactory) throws IOException {
+    delegate.setLockFactory(lockFactory);
+  }
+
+  @Override
+  public synchronized LockFactory getLockFactory() {
+    return delegate.getLockFactory();
+  }
+
+  @Override
+  public synchronized String getLockID() {
+    return delegate.getLockID();
+  }
+
+  @Override
+  public final String[] listAll() throws IOException {
+    return delegate.listAll();
+  }
+
+  @Override
+  public final boolean fileExists(String name) throws IOException {
+    return delegate.fileExists(name);
+  }
+
+  @Override
+  public final long fileLength(String name) throws IOException {
+    return delegate.fileLength(name);
+  }
+
+  @Override
+  public void deleteFile(String name) throws IOException {
+    files.remove(name);
+    delegate.deleteFile(name);
+  }
+
+  @Override
+  public synchronized IndexOutput createOutput(String name, IOContext context) throws IOException {
+    // NOTE: Lucene doesn't normally double-write, but some
+    // tests do:
+    if (files.containsKey(name)) {
+      files.remove(name);
+    }
+    return delegate.createOutput(name, context);
+  }
+
+  @Override
+  public void sync(Collection<String> names) throws IOException {
+    delegate.sync(names);
+  }
+
+  // TODO: createSlicer?
+
+  @Override
+  public synchronized IndexInput openInput(String name, IOContext context) throws IOException {
+    if (name.startsWith("segments")) {
+      // Always open segments files from real directory,
+      // because Lucene relies on getting IOE if the
+      // segments file is not fully written yet:
+      return delegate.openInput(name, context);
+    } else if (!files.containsKey(name)) {
+      IndexInput in = delegate.openInput(name, context);
+      try {
+        files.put(name, new CachedIndexInput(name, delegate.fileLength(name), in));
+      } finally {
+        in.close();
+      }
+    }
+    return (IndexInput) files.get(name).clone();
+  }
+
+  @Override
+  public void close() throws IOException {
+    delegate.close();
+    files.clear();
+  }
+
+  // nocommit can we somehow merge w/ ByteArrayDataInput?
+  // just extend it...?
+  private static final class CachedIndexInput extends IndexInput {
+    // NOTE: 2.1 GB limit!!!
+    private final byte[] bytes;
+    private int pos;
+
+    public CachedIndexInput(String name, long length, IndexInput in) throws IOException {
+      super("CachedIndexInput(" + name + ")");
+      if (length > Integer.MAX_VALUE) {
+        throw new IllegalStateException("cannot read file " + name + ": size (" + length + ") is over Integer.MAX_VALUE");
+      }
+      bytes = new byte[(int) length];
+      in.readBytes(bytes, 0, (int) length);
+    }
+
+    @Override
+    public void close() {
+      // nothing to do here
+    }
+
+    @Override
+    public long length() {
+      return bytes.length;
+    }
+
+    @Override
+    public byte readByte() throws IOException {
+      // NOTE: throws AIOOBE not EOFE
+      return bytes[pos++];
+    }
+
+    @Override
+    public void readBytes(byte[] b, int offset, int len) throws IOException {
+      // NOTE: throws AIOOBE not EOFE
+      System.arraycopy(bytes, pos, b, offset, len);
+      pos += len;
+    }
+
+    @Override
+    public long getFilePointer() {
+      return pos;
+    }
+
+    @Override
+    public void seek(long pos) throws IOException {
+      this.pos = (int) pos;
+    }
+
+    @Override
+    public short readShort() {
+      return (short) (((bytes[pos++] & 0xFF) <<  8) |  (bytes[pos++] & 0xFF));
+    }
+ 
+    @Override
+    public int readInt() {
+      return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16)
+        | ((bytes[pos++] & 0xFF) <<  8) |  (bytes[pos++] & 0xFF);
+    }
+ 
+    @Override
+    public long readLong() {
+      final int i1 = ((bytes[pos++] & 0xff) << 24) | ((bytes[pos++] & 0xff) << 16) |
+        ((bytes[pos++] & 0xff) << 8) | (bytes[pos++] & 0xff);
+      final int i2 = ((bytes[pos++] & 0xff) << 24) | ((bytes[pos++] & 0xff) << 16) |
+        ((bytes[pos++] & 0xff) << 8) | (bytes[pos++] & 0xff);
+      return (((long)i1) << 32) | (i2 & 0xFFFFFFFFL);
+    }
+
+    @Override
+    public int readVInt() {
+      byte b = bytes[pos++];
+      if (b >= 0) return b;
+      int i = b & 0x7F;
+      b = bytes[pos++];
+      i |= (b & 0x7F) << 7;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7F) << 14;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7F) << 21;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      // Warning: the next ands use 0x0F / 0xF0 - beware copy/paste errors:
+      i |= (b & 0x0F) << 28;
+      if ((b & 0xF0) == 0) return i;
+      throw new RuntimeException("Invalid vInt detected (too many bits)");
+    }
+ 
+    @Override
+    public long readVLong() {
+      byte b = bytes[pos++];
+      if (b >= 0) return b;
+      long i = b & 0x7FL;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 7;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 14;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 21;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 28;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 35;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 42;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 49;
+      if (b >= 0) return i;
+      b = bytes[pos++];
+      i |= (b & 0x7FL) << 56;
+      if (b >= 0) return i;
+      throw new RuntimeException("Invalid vLong detected (negative values disallowed)");
+    }
+  }
+}

Property changes on: lucene/misc/src/java/org/apache/lucene/store/CachingRAMDirectory.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
