blob: a9885110de304e03e3252e266ac5773fdf516d0f [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed 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.
*/
/*
* @author max
*/
package com.intellij.util.io;
import com.intellij.util.containers.LimitedPool;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.util.BitSet;
public class Page {
public static final int PAGE_SIZE = 4 * 1024;
private static final LimitedPool<ByteBuffer> ourBufferPool = new LimitedPool<ByteBuffer>(10, new LimitedPool.ObjectFactory<ByteBuffer>() {
@Override
public ByteBuffer create() {
return ByteBuffer.allocate(PAGE_SIZE);
}
@Override
public void cleanup(final ByteBuffer byteBuffer) {
}
});
private final long offset;
private final RandomAccessDataFile owner;
private final PoolPageKey myKey;
private ByteBuffer buf;
private boolean read = false;
private boolean dirty = false;
private int myFinalizationId;
private BitSet myWriteMask;
private static class PageLock {}
private final PageLock lock = new PageLock();
public Page(RandomAccessDataFile owner, long offset) {
this.owner = owner;
this.offset = offset;
myKey = new PoolPageKey(owner, offset);
read = false;
dirty = false;
myWriteMask = null;
assert offset >= 0;
}
private void ensureRead() {
if (!read) {
if (myWriteMask != null) {
byte[] content = new byte[PAGE_SIZE];
final ByteBuffer b = getBuf();
b.position(0);
b.get(content, 0, PAGE_SIZE);
owner.loadPage(this);
for(int i=myWriteMask.nextSetBit(0); i>=0; i=myWriteMask.nextSetBit(i+1)) {
b.put(i, content[i]);
}
myWriteMask = null;
}
else {
owner.loadPage(this);
}
read = true;
}
}
private void ensureReadOrWriteMaskExists() {
dirty = true;
if (read || myWriteMask != null) return;
myWriteMask = new BitSet(PAGE_SIZE);
}
private static class Range {
int start;
int end;
}
private final Range myContinuousRange = new Range();
@Nullable
private Range calcContinousRange(final BitSet mask) {
int lowestByte = mask.nextSetBit(0);
int highestByte;
if (lowestByte >= 0) {
highestByte = mask.nextClearBit(lowestByte);
if (highestByte > 0) {
int nextChunk = mask.nextSetBit(highestByte);
if (nextChunk < 0) {
myContinuousRange.start = lowestByte;
myContinuousRange.end = highestByte;
return myContinuousRange;
}
else {
return null;
}
}
else {
myContinuousRange.start = lowestByte;
myContinuousRange.end = PAGE_SIZE;
return myContinuousRange;
}
}
else {
return null;
}
}
public void flush() {
synchronized (lock) {
if (dirty) {
int start = 0;
int end = PAGE_SIZE;
if (myWriteMask != null) {
Range range = calcContinousRange(myWriteMask);
if (range == null) {
// System.out.println("Discountinous write of: " + myWriteMask.cardinality() + " bytes. Performing ensure read before flush.");
ensureRead();
}
else {
start = range.start;
end = range.end;
}
myWriteMask = null;
}
if (end - start > 0) {
owner.flushPage(this, start, end);
}
dirty = false;
}
}
}
public ByteBuffer getBuf() {
synchronized (lock) {
if (buf == null) {
synchronized (ourBufferPool) {
buf = ourBufferPool.alloc();
}
}
return buf;
}
}
private void recycle() {
if (buf != null) {
synchronized (ourBufferPool) {
ourBufferPool.recycle(buf);
}
}
buf = null;
read = false;
dirty = false;
myWriteMask = null;
}
public long getOffset() {
return offset;
}
public int put(long index, byte[] bytes, int off, int length) {
synchronized (lock) {
myFinalizationId = 0;
ensureReadOrWriteMaskExists();
final int start = (int)(index - offset);
final ByteBuffer b = getBuf();
b.position(start);
int count = Math.min(length, PAGE_SIZE - start);
b.put(bytes, off, count);
if (myWriteMask != null) {
myWriteMask.set(start, start + count);
}
return count;
}
}
public int get(long index, byte[] bytes, int off, int length) {
synchronized (lock) {
myFinalizationId = 0;
ensureRead();
final int start = (int)(index - offset);
final ByteBuffer b = getBuf();
b.position(start);
int count = Math.min(length, PAGE_SIZE - start);
b.get(bytes, off, count);
return count;
}
}
@Nullable
public FinalizationRequest prepareForFinalization(int finalizationId) {
synchronized (lock) {
if (dirty) {
myFinalizationId = finalizationId;
return new FinalizationRequest(this, finalizationId);
}
else {
recycle();
return null;
}
}
}
public RandomAccessDataFile getOwner() {
return owner;
}
public PoolPageKey getKey() {
return myKey;
}
public boolean flushIfFinalizationIdIsEqualTo(final long finalizationId) {
synchronized (lock) {
if (myFinalizationId == finalizationId) {
flush();
return true;
}
return false;
}
}
public boolean recycleIfFinalizationIdIsEqualTo(final long finalizationId) {
synchronized (lock) {
if (myFinalizationId == finalizationId) {
recycle();
return true;
}
return false;
}
}
@Override
public String toString() {
synchronized (lock) {
return "Page[" + owner + ", dirty: " + dirty + ", offset=" + offset + "]";
}
}
}