/*
* Copyright 2000-2007 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.
*/
package com.intellij.util.io;
import com.intellij.util.containers.IntObjectCache;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.Arrays;
public abstract class CachedFile implements DataOutput, DataInput, Flushable, Closeable {
@NonNls private static final String UTF_8_CHARSET_NAME = "UTF-8";
private byte[] myTypedIOBuffer = new byte[8];
protected long myLength;
protected long myPosition;
protected CachingStrategy myStrategy;
abstract void loadPage(long offset, byte[] page, int size) throws IOException;
abstract void savePage(long offset, byte[] page, int size) throws IOException;
final void markPageDirty(CachedFilePage page, boolean dirty) {
myStrategy.markPageDirty(page, dirty);
}
public final long getFilePointer() {
return myPosition;
}
public final long length() {
return myLength;
}
public final void seek(long pos) throws IOException {
if (pos < 0 || pos > myLength) {
throw new IOException("Invalid position: " + pos + ", length = " + myLength);
}
myPosition = pos;
}
public final int usedMemory() {
return myStrategy.usedMemory();
}
public final double cacheHitRate() {
return myStrategy.cacheHitRate();
}
public void write(int b) throws IOException {
myStrategy.getPage(this, myPosition).setByte((int)(myPosition % myStrategy.getPageSize()), (byte)b);
if (++myPosition > myLength) {
myLength = myPosition;
}
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
CachedFilePage page = myStrategy.getPage(this, myPosition);
int pageIndex = (int)(myPosition % myStrategy.getPageSize());
int pageBytes = myStrategy.getPageSize() - pageIndex;
if (pageBytes > len) {
pageBytes = len;
}
page.write(b, off, pageIndex, pageBytes);
len -= pageBytes;
off += pageBytes;
myPosition += pageBytes;
if (myPosition > myLength) {
myLength = myPosition;
}
}
}
public final void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
public final void writeByte(int v) throws IOException {
write(v);
}
public final void writeShort(int v) throws IOException {
byte[] buffer = myTypedIOBuffer;
buffer[0] = (byte)((v >>> 8) & 0xFF);
buffer[1] = (byte)(v & 0xFF);
write(buffer, 0, 2);
}
public final void writeChar(int v) throws IOException {
writeShort(v);
}
public final void writeInt(int v) throws IOException {
byte[] buffer = myTypedIOBuffer;
buffer[0] = (byte)((v >>> 24) & 0xFF);
buffer[1] = (byte)((v >>> 16) & 0xFF);
buffer[2] = (byte)((v >>> 8) & 0xFF);
buffer[3] = (byte)(v & 0xFF);
write(buffer, 0, 4);
}
public final void writeLong(long v) throws IOException {
byte[] buffer = myTypedIOBuffer;
buffer[0] = (byte)((v >>> 56) & 0xFF);
buffer[1] = (byte)((v >>> 48) & 0xFF);
buffer[2] = (byte)((v >>> 40) & 0xFF);
buffer[3] = (byte)((v >>> 32) & 0xFF);
buffer[4] = (byte)((v >>> 24) & 0xFF);
buffer[5] = (byte)((v >>> 16) & 0xFF);
buffer[6] = (byte)((v >>> 8) & 0xFF);
buffer[7] = (byte)(v & 0xFF);
write(buffer, 0, 8);
}
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
public final void writeBytes(String s) throws IOException {
write(s.getBytes());
}
public final void writeChars(String s) throws IOException {
int clen = s.length();
int blen = 2 * clen;
byte[] b = new byte[blen];
char[] c = new char[clen];
s.getChars(0, clen, c, 0);
for (int i = 0, j = 0; i < clen; i++) {
b[j++] = (byte)(c[i] >>> 8);
b[j++] = (byte)c[i];
}
write(b);
}
public final void writeUTF(String str) throws IOException {
final byte[] bytes = str.getBytes(UTF_8_CHARSET_NAME);
writeInt(bytes.length);
write(bytes);
}
public int read() throws IOException, EOFException {
if (myPosition >= myLength) {
throw new EOFException();
}
final byte result = myStrategy.getPage(this, myPosition).getByte((int)(myPosition % myStrategy.getPageSize()));
++myPosition;
return result & 0xff;
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
int savedOffset = off;
while (len > 0 && myPosition < myLength) {
CachedFilePage page = myStrategy.getPage(this, myPosition);
int pageIndex = (int)(myPosition % myStrategy.getPageSize());
int readBytes = page.read(pageIndex, b, off, len);
myPosition += readBytes;
off += readBytes;
len -= readBytes;
}
return off - savedOffset;
}
public final void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public final void readFully(byte[] b, int off, int len) throws IOException {
int n = 0;
while (len > n) {
if (myPosition >= myLength) {
throw new EOFException("Position " + myPosition + " is out of file length " + myLength + ". Bytes to read: " + (len - n));
}
n += read(b, off + n, len - n);
}
}
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = getFilePointer();
len = length();
newpos = pos + n;
if (newpos > len) {
newpos = len;
}
seek(newpos);
return (int)(newpos - pos);
}
public final boolean readBoolean() throws IOException {
int ch = read();
return (ch != 0);
}
public final byte readByte() throws IOException {
int ch = read();
return (byte)ch;
}
public final int readUnsignedByte() throws IOException {
return read();
}
public final short readShort() throws IOException {
byte[] buffer = myTypedIOBuffer;
if (read(buffer, 0, 2) < 2) {
throw new EOFException();
}
int ch1 = buffer[0] & 0xff;
int ch2 = buffer[1] & 0xff;
return (short)((ch1 << 8) + ch2);
}
public final int readUnsignedShort() throws IOException {
byte[] buffer = myTypedIOBuffer;
if (read(buffer, 0, 2) < 2) {
throw new EOFException();
}
int ch1 = buffer[0] & 0xff;
int ch2 = buffer[1] & 0xff;
return (ch1 << 8) + ch2;
}
public final char readChar() throws IOException {
return (char)readUnsignedShort();
}
public final int readInt() throws IOException {
byte[] buffer = myTypedIOBuffer;
if (read(buffer, 0, 4) < 4) {
throw new EOFException();
}
int ch1 = buffer[0] & 0xff;
int ch2 = buffer[1] & 0xff;
int ch3 = buffer[2] & 0xff;
int ch4 = buffer[3] & 0xff;
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
public final long readLong() throws IOException {
byte[] buffer = myTypedIOBuffer;
if (read(buffer, 0, 8) < 8) {
throw new EOFException();
}
long ch1 = buffer[0] & 0xff;
long ch2 = buffer[1] & 0xff;
long ch3 = buffer[2] & 0xff;
long ch4 = buffer[3] & 0xff;
long ch5 = buffer[4] & 0xff;
long ch6 = buffer[5] & 0xff;
long ch7 = buffer[6] & 0xff;
long ch8 = buffer[7] & 0xff;
return ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + ch8);
}
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
@Nullable
public final String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c = -1;
boolean eol = false;
while (!eol) {
switch (c = read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
long cur = getFilePointer();
if ((read()) != '\n') {
seek(cur);
}
break;
default:
input.append((char)c);
break;
}
}
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
public final String readUTF() throws IOException {
int len = readInt();
byte[] bytes = new byte[ len ];
readFully(bytes);
return new String(bytes, UTF_8_CHARSET_NAME);
}
public void flush() throws IOException {
myStrategy.flush(this);
}
public void close() throws IOException {
myStrategy.close(this);
}
public void dispose() throws IOException {
close();
}
protected void init(final CachingStrategy strategy, final long length, final long position) throws IOException {
myLength = length;
myPosition = position;
myStrategy = strategy;
}
protected static class SingleFileCachingStrategy implements CachingStrategy {
final private int myPageSize;
final private IntObjectCache<CachedFilePage> myCache;
final private TIntHashSet myDirtyPages;
private CachedFilePage myLastAccessedPage;
private CachedFilePage myFreePage;
SingleFileCachingStrategy(int pageSize, int pagesCount) {
myPageSize = pageSize;
myCache = new IntObjectCache<CachedFilePage>(pagesCount);
myDirtyPages = new TIntHashSet();
myCache.addDeletedPairsListener(new IntObjectCache.DeletedPairsListener() {
public void objectRemoved(final int key, final Object value) {
myFreePage = (CachedFilePage)value;
}
});
}
public CachedFilePage getPage(CachedFile owner, long offset) throws IOException {
offset -= (offset % myPageSize);
if (myLastAccessedPage != null && offset == myLastAccessedPage.getOffset()) {
return myLastAccessedPage;
}
int pageOffset = (int)(offset / (long)myPageSize);
CachedFilePage result = myCache.tryKey(pageOffset);
if (result == null) {
if (myFreePage == null) {
result = new CachedFilePage(owner, offset, myPageSize);
}
else {
result = myFreePage;
result.setOffset(offset);
myFreePage = null;
}
long bytes2End = owner.length() - offset;
if (bytes2End < 0) {
bytes2End = 0;
}
result.setSize((bytes2End >= myPageSize) ? myPageSize : (int)bytes2End);
result.load();
myCache.cacheObject(pageOffset, result);
if (myFreePage != null) {
myFreePage.save();
}
}
return (myLastAccessedPage = result);
}
public void markPageDirty(CachedFilePage page, boolean dirty) {
int offset = (int)(page.getOffset() / myPageSize);
if (dirty) {
myDirtyPages.add(offset);
}
else {
myDirtyPages.remove(offset);
}
}
public void flush(CachedFile owner) throws IOException {
flush();
}
public void flush() throws IOException {
if (myDirtyPages.size() > 0) {
final int[] ints = myDirtyPages.toArray();
if (ints.length > 1) {
Arrays.sort(ints);
}
for (int offset : ints) {
final CachedFilePage page = myCache.tryKey(offset);
if (page != null) {
page.save();
}
}
}
}
public void close(CachedFile owner) throws IOException {
flush();
}
public int getCacheSize() {
return myCache.size();
}
public int getPageSize() {
return myPageSize;
}
public int usedMemory() {
return myCache.count() * myPageSize;
}
public double cacheHitRate() {
return myCache.hitRate();
}
}
}