1 /* 2 * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de> 3 * 4 * This library is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU Lesser General Public License as published 6 * by the Free Software Foundation; either version 2.1 of the License, or 7 * (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 12 * License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this library; If not, write to the Free Software Foundation, Inc., 16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 */ 18 19 package de.waldheinz.fs.util; 20 21 import de.waldheinz.fs.BlockDevice; 22 import de.waldheinz.fs.ReadOnlyException; 23 import java.io.File; 24 import java.io.FileNotFoundException; 25 import java.io.IOException; 26 import java.io.RandomAccessFile; 27 import java.nio.ByteBuffer; 28 import java.nio.channels.FileChannel; 29 30 /** 31 * This is a {@code BlockDevice} that uses a {@link File} as it's backing store. 32 * 33 * @author Matthias Treydte <matthias.treydte at meetwise.com> 34 */ 35 public final class FileDisk implements BlockDevice { 36 37 /** 38 * The number of bytes per sector for all {@code FileDisk} instances. 39 */ 40 public final static int BYTES_PER_SECTOR = 512; 41 42 private final RandomAccessFile raf; 43 private final FileChannel fc; 44 private final boolean readOnly; 45 private boolean closed; 46 47 /** 48 * Creates a new instance of {@code FileDisk} for the specified 49 * {@code File}. 50 * 51 * @param file the file that holds the disk contents 52 * @param readOnly if the file should be opened in read-only mode, which 53 * will result in a read-only {@code FileDisk} instance 54 * @throws FileNotFoundException if the specified file does not exist 55 * @see #isReadOnly() 56 */ FileDisk(File file, boolean readOnly)57 public FileDisk(File file, boolean readOnly) throws FileNotFoundException { 58 if (!file.exists()) throw new FileNotFoundException(); 59 60 this.readOnly = readOnly; 61 this.closed = false; 62 final String modeString = readOnly ? "r" : "rw"; //NOI18N 63 this.raf = new RandomAccessFile(file, modeString); 64 this.fc = raf.getChannel(); 65 } 66 FileDisk(RandomAccessFile raf, FileChannel fc, boolean readOnly)67 public FileDisk(RandomAccessFile raf, FileChannel fc, boolean readOnly) { 68 this.closed = false; 69 this.raf = raf; 70 this.fc = fc; 71 this.readOnly = readOnly; 72 } 73 FileDisk(RandomAccessFile raf, boolean readOnly)74 private FileDisk(RandomAccessFile raf, boolean readOnly) { 75 this.closed = false; 76 this.raf = raf; 77 this.fc = raf.getChannel(); 78 this.readOnly = readOnly; 79 } 80 81 /** 82 * Creates a new {@code FileDisk} of the specified size. The 83 * {@code FileDisk} returned by this method will be writable. 84 * 85 * @param file the file to hold the {@code FileDisk} contents 86 * @param size the size of the new {@code FileDisk} 87 * @return the created {@code FileDisk} instance 88 * @throws IOException on error creating the {@code FileDisk} 89 */ create(File file, long size)90 public static FileDisk create(File file, long size) throws IOException { 91 try { 92 final RandomAccessFile raf = 93 new RandomAccessFile(file, "rw"); //NOI18N 94 raf.setLength(size); 95 96 return new FileDisk(raf, false); 97 } catch (FileNotFoundException ex) { 98 throw new IOException(ex); 99 } 100 } 101 102 @Override getSize()103 public long getSize() throws IOException { 104 checkClosed(); 105 106 return raf.length(); 107 } 108 109 @Override read(long devOffset, ByteBuffer dest)110 public void read(long devOffset, ByteBuffer dest) throws IOException { 111 checkClosed(); 112 113 int toRead = dest.remaining(); 114 if ((devOffset + toRead) > getSize()) throw new IOException( 115 "reading past end of device"); 116 117 while (toRead > 0) { 118 final int read = fc.read(dest, devOffset); 119 if (read < 0) throw new IOException(); 120 toRead -= read; 121 devOffset += read; 122 } 123 } 124 125 @Override write(long devOffset, ByteBuffer src)126 public void write(long devOffset, ByteBuffer src) throws IOException { 127 checkClosed(); 128 129 if (this.readOnly) throw new ReadOnlyException(); 130 131 int toWrite = src.remaining(); 132 133 if ((devOffset + toWrite) > getSize()) throw new IOException( 134 "writing past end of file"); 135 136 while (toWrite > 0) { 137 final int written = fc.write(src, devOffset); 138 if (written < 0) throw new IOException(); 139 toWrite -= written; 140 devOffset += written; 141 } 142 } 143 144 @Override flush()145 public void flush() throws IOException { 146 checkClosed(); 147 } 148 149 @Override getSectorSize()150 public int getSectorSize() { 151 checkClosed(); 152 153 return BYTES_PER_SECTOR; 154 } 155 156 @Override close()157 public void close() throws IOException { 158 if (isClosed()) return; 159 160 this.closed = true; 161 this.fc.close(); 162 this.raf.close(); 163 } 164 165 @Override isClosed()166 public boolean isClosed() { 167 return this.closed; 168 } 169 checkClosed()170 private void checkClosed() { 171 if (closed) throw new IllegalStateException("device already closed"); 172 } 173 174 @Override isReadOnly()175 public boolean isReadOnly() { 176 checkClosed(); 177 178 return this.readOnly; 179 } 180 181 } 182