| 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.EOFException; |
| import java.io.IOException; |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.util.Iterator; |
| |
| import org.apache.lucene.util.WeakIdentityMap; |
| |
| /** |
| * Base IndexInput implementation that uses an array |
| * of ByteBuffers to represent a file. |
| * <p> |
| * Because Java's ByteBuffer uses an int to address the |
| * values, it's necessary to access a file greater |
| * Integer.MAX_VALUE in size using multiple byte buffers. |
| * <p> |
| * For efficiency, this class requires that the buffers |
| * are a power-of-two (<code>chunkSizePower</code>). |
| */ |
| abstract class ByteBufferIndexInput extends IndexInput { |
| private ByteBuffer[] buffers; |
| |
| private final long chunkSizeMask; |
| private final int chunkSizePower; |
| |
| private int offset; |
| private long length; |
| private String sliceDescription; |
| |
| private int curBufIndex; |
| |
| private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex] |
| |
| private boolean isClone = false; |
| private final WeakIdentityMap<ByteBufferIndexInput,Boolean> clones; |
| |
| ByteBufferIndexInput(String resourceDescription, ByteBuffer[] buffers, long length, int chunkSizePower, boolean trackClones) throws IOException { |
| super(resourceDescription); |
| this.buffers = buffers; |
| this.length = length; |
| this.chunkSizePower = chunkSizePower; |
| this.chunkSizeMask = (1L << chunkSizePower) - 1L; |
| this.clones = trackClones ? WeakIdentityMap.<ByteBufferIndexInput,Boolean>newConcurrentHashMap() : null; |
| |
| assert chunkSizePower >= 0 && chunkSizePower <= 30; |
| assert (length >>> chunkSizePower) < Integer.MAX_VALUE; |
| |
| seek(0L); |
| } |
| |
| @Override |
| public final byte readByte() throws IOException { |
| try { |
| return curBuf.get(); |
| } catch (BufferUnderflowException e) { |
| do { |
| curBufIndex++; |
| if (curBufIndex >= buffers.length) { |
| throw new EOFException("read past EOF: " + this); |
| } |
| curBuf = buffers[curBufIndex]; |
| curBuf.position(0); |
| } while (!curBuf.hasRemaining()); |
| return curBuf.get(); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final 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 EOFException("read past EOF: " + this); |
| } |
| curBuf = buffers[curBufIndex]; |
| curBuf.position(0); |
| curAvail = curBuf.remaining(); |
| } |
| curBuf.get(b, offset, len); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final short readShort() throws IOException { |
| try { |
| return curBuf.getShort(); |
| } catch (BufferUnderflowException e) { |
| return super.readShort(); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final int readInt() throws IOException { |
| try { |
| return curBuf.getInt(); |
| } catch (BufferUnderflowException e) { |
| return super.readInt(); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final long readLong() throws IOException { |
| try { |
| return curBuf.getLong(); |
| } catch (BufferUnderflowException e) { |
| return super.readLong(); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final long getFilePointer() { |
| try { |
| return (((long) curBufIndex) << chunkSizePower) + curBuf.position() - offset; |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final void seek(long pos) throws IOException { |
| // necessary in case offset != 0 and pos < 0, but pos >= -offset |
| if (pos < 0L) { |
| throw new IllegalArgumentException("Seeking to negative position: " + this); |
| } |
| pos += offset; |
| // we use >> here to preserve negative, so we will catch AIOOBE, |
| // in case pos + offset overflows. |
| 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) { |
| throw new EOFException("seek past EOF: " + this); |
| } catch (IllegalArgumentException iae) { |
| throw new EOFException("seek past EOF: " + this); |
| } catch (NullPointerException npe) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| } |
| |
| @Override |
| public final long length() { |
| return length; |
| } |
| |
| @Override |
| public final ByteBufferIndexInput clone() { |
| final ByteBufferIndexInput clone = buildSlice(0L, this.length); |
| try { |
| clone.seek(getFilePointer()); |
| } catch(IOException ioe) { |
| throw new RuntimeException("Should never happen: " + this, ioe); |
| } |
| |
| return clone; |
| } |
| |
| /** |
| * Creates a slice of this index input, with the given description, offset, and length. The slice is seeked to the beginning. |
| */ |
| public final ByteBufferIndexInput slice(String sliceDescription, long offset, long length) { |
| if (isClone) { // well we could, but this is stupid |
| throw new IllegalStateException("cannot slice() " + sliceDescription + " from a cloned IndexInput: " + this); |
| } |
| final ByteBufferIndexInput clone = buildSlice(offset, length); |
| clone.sliceDescription = sliceDescription; |
| try { |
| clone.seek(0L); |
| } catch(IOException ioe) { |
| throw new RuntimeException("Should never happen: " + this, ioe); |
| } |
| |
| return clone; |
| } |
| |
| private ByteBufferIndexInput buildSlice(long offset, long length) { |
| if (buffers == null) { |
| throw new AlreadyClosedException("Already closed: " + this); |
| } |
| if (offset < 0 || length < 0 || offset+length > this.length) { |
| throw new IllegalArgumentException("slice() " + sliceDescription + " out of bounds: offset=" + offset + ",length=" + length + ",fileLength=" + this.length + ": " + this); |
| } |
| |
| // include our own offset into the final offset: |
| offset += this.offset; |
| |
| final ByteBufferIndexInput clone = (ByteBufferIndexInput)super.clone(); |
| clone.isClone = true; |
| // we keep clone.clones, so it shares the same map with original and we have no additional cost on clones |
| assert clone.clones == this.clones; |
| clone.buffers = buildSlice(buffers, offset, length); |
| clone.offset = (int) (offset & chunkSizeMask); |
| clone.length = length; |
| |
| // register the new clone in our clone list to clean it up on closing: |
| if (clones != null) { |
| this.clones.put(clone, Boolean.TRUE); |
| } |
| |
| return clone; |
| } |
| |
| /** Returns a sliced view from a set of already-existing buffers: |
| * the last buffer's limit() will be correct, but |
| * you must deal with offset separately (the first buffer will not be adjusted) */ |
| private ByteBuffer[] buildSlice(ByteBuffer[] buffers, long offset, long length) { |
| final long sliceEnd = offset + length; |
| |
| final int startIndex = (int) (offset >>> chunkSizePower); |
| final int endIndex = (int) (sliceEnd >>> chunkSizePower); |
| |
| // we always allocate one more slice, the last one may be a 0 byte one |
| final ByteBuffer slices[] = new ByteBuffer[endIndex - startIndex + 1]; |
| |
| for (int i = 0; i < slices.length; i++) { |
| slices[i] = buffers[startIndex + i].duplicate(); |
| } |
| |
| // set the last buffer's limit for the sliced view. |
| slices[slices.length - 1].limit((int) (sliceEnd & chunkSizeMask)); |
| |
| return slices; |
| } |
| |
| private void unsetBuffers() { |
| buffers = null; |
| curBuf = null; |
| curBufIndex = 0; |
| } |
| |
| @Override |
| public final void close() throws IOException { |
| try { |
| if (buffers == null) return; |
| |
| // make local copy, then un-set early |
| final ByteBuffer[] bufs = buffers; |
| unsetBuffers(); |
| if (clones != null) { |
| clones.remove(this); |
| } |
| |
| if (isClone) return; |
| |
| // for extra safety unset also all clones' buffers: |
| if (clones != null) { |
| for (Iterator<ByteBufferIndexInput> it = this.clones.keyIterator(); it.hasNext();) { |
| final ByteBufferIndexInput clone = it.next(); |
| assert clone.isClone; |
| clone.unsetBuffers(); |
| } |
| this.clones.clear(); |
| } |
| |
| for (final ByteBuffer b : bufs) { |
| freeBuffer(b); |
| } |
| } finally { |
| unsetBuffers(); |
| } |
| } |
| |
| /** |
| * Called when the contents of a buffer will be no longer needed. |
| */ |
| protected abstract void freeBuffer(ByteBuffer b) throws IOException; |
| |
| @Override |
| public final String toString() { |
| if (sliceDescription != null) { |
| return super.toString() + " [slice=" + sliceDescription + "]"; |
| } else { |
| return super.toString(); |
| } |
| } |
| } |