/* * Copyright 2000-2015 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.openapi.Forceable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; import java.io.*; import java.nio.ByteBuffer; public class RandomAccessDataFile implements Forceable, Closeable { protected static final Logger LOG = Logger.getInstance("#com.intellij.util.io.RandomAccessDataFile"); private static final OpenChannelsCache ourCache = new OpenChannelsCache(150, "rw"); private static int ourFilesCount; private final int myCount = ourFilesCount++; private final File myFile; private final PagePool myPool; private long lastSeek = -1L; private final byte[] myTypedIOBuffer = new byte[8]; private final FileWriter log; private volatile long mySize; private volatile boolean myIsDirty; private volatile boolean myIsDisposed; private static final boolean DEBUG = false; public RandomAccessDataFile(@NotNull File file) throws IOException { this(file, PagePool.SHARED); } public RandomAccessDataFile(@NotNull File file, @NotNull PagePool pool) throws IOException { myPool = pool; myFile = file; if (!file.exists()) { throw new FileNotFoundException(file.getPath() + " does not exist"); } mySize = file.length(); if (DEBUG) { log = new FileWriter(file.getPath() + ".log"); } else { log = null; } } @NotNull public File getFile() { return myFile; } public void put(long addr, byte[] bytes, int off, int len) { assertNotDisposed(); myIsDirty = true; mySize = Math.max(mySize, addr + len); while (len > 0) { final Page page = myPool.alloc(this, addr); int written = page.put(addr, bytes, off, len); len -= written; addr += written; off += written; } } public void get(long addr, byte[] bytes, int off, int len) { assertNotDisposed(); while (len > 0) { final Page page = myPool.alloc(this, addr); int read = page.get(addr, bytes, off, len); len -= read; addr += read; off += read; } } private void releaseFile() { ourCache.releaseChannel(myFile); } private RandomAccessFile getRandomAccessFile() throws FileNotFoundException { return ourCache.getChannel(myFile); } public void putInt(long addr, int value) { Bits.putInt(myTypedIOBuffer, 0, value); put(addr, myTypedIOBuffer, 0, 4); } public int getInt(long addr) { get(addr, myTypedIOBuffer, 0, 4); return Bits.getInt(myTypedIOBuffer, 0); } public void putLong(long addr, long value) { Bits.putLong(myTypedIOBuffer, 0, value); put(addr, myTypedIOBuffer, 0, 8); } public void putByte(final long addr, final byte b) { myTypedIOBuffer[0] = b; put(addr, myTypedIOBuffer, 0, 1); } public byte getByte(long addr) { get(addr, myTypedIOBuffer, 0, 1); return myTypedIOBuffer[0]; } public long getLong(long addr) { get(addr, myTypedIOBuffer, 0, 8); return Bits.getLong(myTypedIOBuffer, 0); } public String getUTF(long addr) { int len = getInt(addr); byte[] bytes = new byte[len]; get(addr + 4, bytes, 0, len); return new String(bytes, CharsetToolkit.UTF8_CHARSET); } public void putUTF(long addr, String value) { final byte[] bytes = value.getBytes(CharsetToolkit.UTF8_CHARSET); putInt(addr, bytes.length); put(addr + 4, bytes, 0, bytes.length); } public long length() { assertNotDisposed(); return mySize; } public long physicalLength() { assertNotDisposed(); long res; try { RandomAccessFile file = getRandomAccessFile(); try { synchronized (file) { res = file.length(); } } finally { releaseFile(); } } catch (IOException e) { return 0; } return res; } public void dispose() { if (myIsDisposed) return; myPool.flushPages(this); ourCache.closeChannel(myFile); myIsDisposed = true; } @Override public void close() { dispose(); } /** * Flushes dirty pages to underlying buffers */ @Override public void force() { assertNotDisposed(); if (isDirty()) { myPool.flushPages(this); myIsDirty = false; } } /** * Flushes dirty pages to buffers and saves them to disk */ public void sync() { force(); try { RandomAccessFile file = getRandomAccessFile(); file.getChannel().force(true); } catch (IOException ignored) { } finally { releaseFile(); } } public void flushSomePages(int maxPagesToFlush) { assertNotDisposed(); if (isDirty()) { myIsDirty = !myPool.flushPages(this, maxPagesToFlush); } } @Override public boolean isDirty() { assertNotDisposed(); return myIsDirty; } public boolean isDisposed() { return myIsDisposed; } private void assertNotDisposed() { if (myIsDisposed) { LOG.error("storage file is disposed: " + myFile); } } public static int totalReads; public static long totalReadBytes; public static int seekcount; public static int totalWrites; public static long totalWriteBytes; void loadPage(final Page page) { assertNotDisposed(); try { final RandomAccessFile file = getRandomAccessFile(); try { synchronized (file) { seek(file, page.getOffset()); final ByteBuffer buf = page.getBuf(); totalReads++; totalReadBytes += Page.PAGE_SIZE; if (DEBUG) { log.write("Read at: \t" + page.getOffset() + "\t len: " + Page.PAGE_SIZE + ", size: " + mySize + "\n"); } file.read(buf.array(), 0, Page.PAGE_SIZE); lastSeek += Page.PAGE_SIZE; } } finally { releaseFile(); } } catch (IOException e) { throw new RuntimeException(e); } } void flushPage(final Page page, int start, int end) { assertNotDisposed(); try { flush(page.getBuf(), page.getOffset() + start, start, end - start); } catch (IOException e) { throw new RuntimeException(e); } } private void flush(final ByteBuffer buf, final long fileOffset, final int bufOffset, int length) throws IOException { if (fileOffset + length > mySize) { length = (int)(mySize - fileOffset); } final RandomAccessFile file = getRandomAccessFile(); try { synchronized (file) { seek(file, fileOffset); totalWrites++; totalWriteBytes += length; if (DEBUG) { log.write("Write at: \t" + fileOffset + "\t len: " + length + ", size: " + mySize + ", filesize: " + file.length() + "\n"); } file.write(buf.array(), bufOffset, length); lastSeek += length; } } finally { releaseFile(); } } private void seek(final RandomAccessFile file, final long fileOffset) throws IOException { if (DEBUG) { if (lastSeek != -1L && fileOffset != lastSeek) { long delta = fileOffset - lastSeek; seekcount++; log.write("Seeking: " + delta + "\n"); } lastSeek = fileOffset; } file.seek(fileOffset); } @Override public int hashCode() { return myCount; } @Override public synchronized String toString() { return "RandomAccessFile[" + myFile + ", dirty=" + myIsDirty + "]"; } }