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 &lt;matthias.treydte at meetwise.com&gt;
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