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.*;
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.nio.ByteBuffer;
26 import java.util.zip.GZIPInputStream;
27 
28 /**
29  * A {@link BlockDevice} that lives entirely in heap memory. This is basically
30  * a RAM disk. A {@code RamDisk} is always writable.
31  *
32  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
33  */
34 public final class RamDisk implements BlockDevice {
35 
36     /**
37      * The default sector size for {@code RamDisk}s.
38      */
39     public final static int DEFAULT_SECTOR_SIZE = 512;
40 
41     private final int sectorSize;
42     private final ByteBuffer data;
43     private final int size;
44     private boolean closed;
45 
46     /**
47      * Reads a GZIP compressed disk image from the specified input stream and
48      * returns a {@code RamDisk} holding the decompressed image.
49      *
50      * @param in the stream to read the disk image from
51      * @return the decompressed {@code RamDisk}
52      * @throws IOException on read or decompression error
53      */
readGzipped(InputStream in)54     public static RamDisk readGzipped(InputStream in) throws IOException {
55         final GZIPInputStream zis = new GZIPInputStream(in);
56         ByteArrayOutputStream bos = new ByteArrayOutputStream();
57 
58         final byte[] buffer = new byte[4096];
59 
60         int read = zis.read(buffer);
61         int total = 0;
62 
63         while (read >= 0) {
64             total += read;
65             bos.write(buffer, 0, read);
66             read = zis.read(buffer);
67         }
68 
69         if (total < DEFAULT_SECTOR_SIZE) throw new IOException(
70                 "read only " + total + " bytes"); //NOI18N
71 
72         final ByteBuffer bb = ByteBuffer.wrap(bos.toByteArray(), 0, total);
73         return new RamDisk(bb, DEFAULT_SECTOR_SIZE);
74     }
75 
RamDisk(ByteBuffer buffer, int sectorSize)76     private RamDisk(ByteBuffer buffer, int sectorSize) {
77         this.size = buffer.limit();
78         this.sectorSize = sectorSize;
79         this.data = buffer;
80         this.closed = false;
81     }
82 
83     /**
84      * Creates a new instance of {@code RamDisk} of this specified
85      * size and using the {@link #DEFAULT_SECTOR_SIZE}.
86      *
87      * @param size the size of the new block device
88      */
RamDisk(int size)89     public RamDisk(int size) {
90         this(size, DEFAULT_SECTOR_SIZE);
91     }
92 
93     /**
94      * Creates a new instance of {@code RamDisk} of this specified
95      * size and sector size
96      *
97      * @param size the size of the new block device
98      * @param sectorSize the sector size of the new block device
99      */
RamDisk(int size, int sectorSize)100     public RamDisk(int size, int sectorSize) {
101         if (sectorSize < 1) throw new IllegalArgumentException(
102                 "invalid sector size"); //NOI18N
103 
104         this.sectorSize = sectorSize;
105         this.size = size;
106         this.data = ByteBuffer.allocate(size);
107     }
108 
109     @Override
getSize()110     public long getSize() {
111         checkClosed();
112         return this.size;
113     }
114 
115     @Override
read(long devOffset, ByteBuffer dest)116     public void read(long devOffset, ByteBuffer dest) throws IOException {
117         checkClosed();
118 
119         if (devOffset > getSize()){
120             final StringBuilder sb = new StringBuilder();
121             sb.append("read at ").append(devOffset);
122             sb.append(" is off size (").append(getSize()).append(")");
123 
124             throw new IllegalArgumentException(sb.toString());
125         }
126 
127         data.limit((int) (devOffset + dest.remaining()));
128         data.position((int) devOffset);
129 
130         dest.put(data);
131     }
132 
133     @Override
write(long devOffset, ByteBuffer src)134     public void write(long devOffset, ByteBuffer src) throws IOException {
135         checkClosed();
136 
137         if (devOffset + src.remaining() > getSize()) throw new
138                 IllegalArgumentException(
139                 "offset=" + devOffset +
140                 ", length=" + src.remaining() +
141                 ", size=" + getSize());
142 
143         data.limit((int) (devOffset + src.remaining()));
144         data.position((int) devOffset);
145 
146 
147         data.put(src);
148     }
149 
150     /**
151      * Returns a slice of the {@code ByteBuffer} that is used by this
152      * {@code RamDisk} as it's backing store. The returned buffer will be
153      * live (reflecting any changes made through the
154      * {@link #write(long, java.nio.ByteBuffer) method}, but read-only.
155      *
156      * @return a buffer holding the contents of this {@code RamDisk}
157      */
getBuffer()158     public ByteBuffer getBuffer() {
159         return this.data.asReadOnlyBuffer();
160     }
161 
162     @Override
flush()163     public void flush() throws IOException {
164         checkClosed();
165     }
166 
167     @Override
getSectorSize()168     public int getSectorSize() {
169         checkClosed();
170         return this.sectorSize;
171     }
172 
173     @Override
close()174     public void close() throws IOException {
175         this.closed = true;
176     }
177 
178     @Override
isClosed()179     public boolean isClosed() {
180         return this.closed;
181     }
182 
checkClosed()183     private void checkClosed() {
184         if (closed) throw new IllegalStateException("device already closed");
185     }
186 
187     /**
188      * Returns always {@code false}, as a {@code RamDisk} is always writable.
189      *
190      * @return always {@code false}
191      */
192     @Override
isReadOnly()193     public boolean isReadOnly() {
194         checkClosed();
195 
196         return false;
197     }
198 
199 }
200