/* * Copyright (C) 2009,2010 Matthias Treydte * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package de.waldheinz.fs.util; import de.waldheinz.fs.BlockDevice; import de.waldheinz.fs.ReadOnlyException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * This is a {@code BlockDevice} that uses a {@link File} as it's backing store. * * @author Matthias Treydte <matthias.treydte at meetwise.com> */ public final class FileDisk implements BlockDevice { /** * The number of bytes per sector for all {@code FileDisk} instances. */ public final static int BYTES_PER_SECTOR = 512; private final RandomAccessFile raf; private final FileChannel fc; private final boolean readOnly; private boolean closed; /** * Creates a new instance of {@code FileDisk} for the specified * {@code File}. * * @param file the file that holds the disk contents * @param readOnly if the file should be opened in read-only mode, which * will result in a read-only {@code FileDisk} instance * @throws FileNotFoundException if the specified file does not exist * @see #isReadOnly() */ public FileDisk(File file, boolean readOnly) throws FileNotFoundException { if (!file.exists()) throw new FileNotFoundException(); this.readOnly = readOnly; this.closed = false; final String modeString = readOnly ? "r" : "rw"; //NOI18N this.raf = new RandomAccessFile(file, modeString); this.fc = raf.getChannel(); } public FileDisk(RandomAccessFile raf, FileChannel fc, boolean readOnly) { this.closed = false; this.raf = raf; this.fc = fc; this.readOnly = readOnly; } private FileDisk(RandomAccessFile raf, boolean readOnly) { this.closed = false; this.raf = raf; this.fc = raf.getChannel(); this.readOnly = readOnly; } /** * Creates a new {@code FileDisk} of the specified size. The * {@code FileDisk} returned by this method will be writable. * * @param file the file to hold the {@code FileDisk} contents * @param size the size of the new {@code FileDisk} * @return the created {@code FileDisk} instance * @throws IOException on error creating the {@code FileDisk} */ public static FileDisk create(File file, long size) throws IOException { try { final RandomAccessFile raf = new RandomAccessFile(file, "rw"); //NOI18N raf.setLength(size); return new FileDisk(raf, false); } catch (FileNotFoundException ex) { throw new IOException(ex); } } @Override public long getSize() throws IOException { checkClosed(); return raf.length(); } @Override public void read(long devOffset, ByteBuffer dest) throws IOException { checkClosed(); int toRead = dest.remaining(); if ((devOffset + toRead) > getSize()) throw new IOException( "reading past end of device"); while (toRead > 0) { final int read = fc.read(dest, devOffset); if (read < 0) throw new IOException(); toRead -= read; devOffset += read; } } @Override public void write(long devOffset, ByteBuffer src) throws IOException { checkClosed(); if (this.readOnly) throw new ReadOnlyException(); int toWrite = src.remaining(); if ((devOffset + toWrite) > getSize()) throw new IOException( "writing past end of file"); while (toWrite > 0) { final int written = fc.write(src, devOffset); if (written < 0) throw new IOException(); toWrite -= written; devOffset += written; } } @Override public void flush() throws IOException { checkClosed(); } @Override public int getSectorSize() { checkClosed(); return BYTES_PER_SECTOR; } @Override public void close() throws IOException { if (isClosed()) return; this.closed = true; this.fc.close(); this.raf.close(); } @Override public boolean isClosed() { return this.closed; } private void checkClosed() { if (closed) throw new IllegalStateException("device already closed"); } @Override public boolean isReadOnly() { checkClosed(); return this.readOnly; } }