/*
 * 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.
 */
package org.apache.lucene.store;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;

import org.apache.lucene.util.SuppressForbidden;

A straightforward implementation of FSDirectory using java.io.RandomAccessFile. However, this class has poor concurrent performance (multiple threads will bottleneck) as it synchronizes when multiple threads read from the same file. It's usually better to use NIOFSDirectory or MMapDirectory instead.

NOTE: Because this uses RandomAccessFile, it will generally not work with non-default filesystem providers. It is only provided for applications that relied on the fact that RandomAccessFile's IO was not interruptible.

/** A straightforward implementation of {@link FSDirectory} * using java.io.RandomAccessFile. However, this class has * poor concurrent performance (multiple threads will * bottleneck) as it synchronizes when multiple threads * read from the same file. It's usually better to use * {@link NIOFSDirectory} or {@link MMapDirectory} instead. * <p> * NOTE: Because this uses RandomAccessFile, it will generally * not work with non-default filesystem providers. It is only * provided for applications that relied on the fact that * RandomAccessFile's IO was not interruptible. */
@SuppressForbidden(reason = "java.io.File: RAFDirectory is legacy API") public class RAFDirectory extends FSDirectory {
Create a new RAFDirectory for the named location. The directory is created at the named location if it does not yet exist.
Params:
  • path – the path of the directory
  • lockFactory – the lock factory to use
Throws:
/** Create a new RAFDirectory for the named location. * The directory is created at the named location if it does not yet exist. * * @param path the path of the directory * @param lockFactory the lock factory to use * @throws IOException if there is a low-level I/O error */
public RAFDirectory(Path path, LockFactory lockFactory) throws IOException { super(path, lockFactory); path.toFile(); // throw exception if we can't get a File }
Create a new SimpleFSDirectory for the named location and FSLockFactory.getDefault(). The directory is created at the named location if it does not yet exist.
Params:
  • path – the path of the directory
Throws:
/** Create a new SimpleFSDirectory for the named location and {@link FSLockFactory#getDefault()}. * The directory is created at the named location if it does not yet exist. * * @param path the path of the directory * @throws IOException if there is a low-level I/O error */
public RAFDirectory(Path path) throws IOException { this(path, FSLockFactory.getDefault()); }
Creates an IndexInput for the file with the given name.
/** Creates an IndexInput for the file with the given name. */
@Override public IndexInput openInput(String name, IOContext context) throws IOException { ensureOpen(); ensureCanRead(name); final File path = directory.resolve(name).toFile(); RandomAccessFile raf = new RandomAccessFile(path, "r"); return new RAFIndexInput("SimpleFSIndexInput(path=\"" + path.getPath() + "\")", raf, context); } /** * Reads bytes with {@link RandomAccessFile#seek(long)} followed by * {@link RandomAccessFile#read(byte[], int, int)}. */ @SuppressForbidden(reason = "java.io.File: RAFDirectory is legacy API") static final class RAFIndexInput extends BufferedIndexInput {
The maximum chunk size is 8192 bytes, because RandomAccessFile mallocs a native buffer outside of stack if the read buffer size is larger.
/** * The maximum chunk size is 8192 bytes, because {@link RandomAccessFile} mallocs * a native buffer outside of stack if the read buffer size is larger. */
private static final int CHUNK_SIZE = 8192;
the file channel we will read from
/** the file channel we will read from */
protected final RandomAccessFile file;
is this instance a clone and hence does not own the file to close it
/** is this instance a clone and hence does not own the file to close it */
boolean isClone = false;
start offset: non-zero in the slice case
/** start offset: non-zero in the slice case */
protected final long off;
end offset (start+length)
/** end offset (start+length) */
protected final long end; public RAFIndexInput(String resourceDesc, RandomAccessFile file, IOContext context) throws IOException { super(resourceDesc, context); this.file = file; this.off = 0L; this.end = file.length(); } public RAFIndexInput(String resourceDesc, RandomAccessFile file, long off, long length, int bufferSize) { super(resourceDesc, bufferSize); this.file = file; this.off = off; this.end = off + length; this.isClone = true; } @Override public void close() throws IOException { if (!isClone) { file.close(); } } @Override public RAFIndexInput clone() { RAFIndexInput clone = (RAFIndexInput)super.clone(); clone.isClone = true; return clone; } @Override public IndexInput slice(String sliceDescription, long offset, long length) throws IOException { if (offset < 0 || length < 0 || offset + length > this.length()) { throw new IllegalArgumentException("slice() " + sliceDescription + " out of bounds: " + this); } return new RAFIndexInput(sliceDescription, file, off + offset, length, getBufferSize()); } @Override public final long length() { return end - off; }
IndexInput methods
/** IndexInput methods */
@Override protected void readInternal(byte[] b, int offset, int len) throws IOException { synchronized (file) { long position = off + getFilePointer(); file.seek(position); int total = 0; if (position + len > end) { throw new EOFException("read past EOF: " + this); } try { while (total < len) { final int toRead = Math.min(CHUNK_SIZE, len - total); final int i = file.read(b, offset + total, toRead); if (i < 0) { // be defensive here, even though we checked before hand, something could have changed throw new EOFException("read past EOF: " + this + " off: " + offset + " len: " + len + " total: " + total + " chunkLen: " + toRead + " end: " + end); } assert i > 0 : "RandomAccessFile.read with non zero-length toRead must always read at least one byte"; total += i; } assert total == len; } catch (IOException ioe) { throw new IOException(ioe.getMessage() + ": " + this, ioe); } } } @Override protected void seekInternal(long pos) throws IOException { if (pos > length()) { throw new EOFException("read past EOF: pos=" + pos + " vs length=" + length() + ": " + this); } } boolean isFDValid() throws IOException { return file.getFD().valid(); } } }