summaryrefslogtreecommitdiff
path: root/src/main/java/de/waldheinz/fs/util/RamDisk.java
blob: 51a1be15ad85a1e20af13df176e84394f28b605c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
 * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
 *
 * 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.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.zip.GZIPInputStream;

/**
 * A {@link BlockDevice} that lives entirely in heap memory. This is basically
 * a RAM disk. A {@code RamDisk} is always writable.
 *
 * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
 */
public final class RamDisk implements BlockDevice {
    
    /**
     * The default sector size for {@code RamDisk}s.
     */
    public final static int DEFAULT_SECTOR_SIZE = 512;
    
    private final int sectorSize;
    private final ByteBuffer data;
    private final int size;
    private boolean closed;

    /**
     * Reads a GZIP compressed disk image from the specified input stream and
     * returns a {@code RamDisk} holding the decompressed image.
     *
     * @param in the stream to read the disk image from
     * @return the decompressed {@code RamDisk}
     * @throws IOException on read or decompression error
     */
    public static RamDisk readGzipped(InputStream in) throws IOException {
        final GZIPInputStream zis = new GZIPInputStream(in);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        final byte[] buffer = new byte[4096];
        
        int read = zis.read(buffer);
        int total = 0;
        
        while (read >= 0) {
            total += read;
            bos.write(buffer, 0, read);
            read = zis.read(buffer);
        }

        if (total < DEFAULT_SECTOR_SIZE) throw new IOException(
                "read only " + total + " bytes"); //NOI18N
                
        final ByteBuffer bb = ByteBuffer.wrap(bos.toByteArray(), 0, total);
        return new RamDisk(bb, DEFAULT_SECTOR_SIZE);
    }
    
    private RamDisk(ByteBuffer buffer, int sectorSize) {
        this.size = buffer.limit();
        this.sectorSize = sectorSize;
        this.data = buffer;
        this.closed = false;
    }

    /**
     * Creates a new instance of {@code RamDisk} of this specified
     * size and using the {@link #DEFAULT_SECTOR_SIZE}.
     *
     * @param size the size of the new block device
     */
    public RamDisk(int size) {
        this(size, DEFAULT_SECTOR_SIZE);
    }

    /**
     * Creates a new instance of {@code RamDisk} of this specified
     * size and sector size
     *
     * @param size the size of the new block device
     * @param sectorSize the sector size of the new block device
     */
    public RamDisk(int size, int sectorSize) {
        if (sectorSize < 1) throw new IllegalArgumentException(
                "invalid sector size"); //NOI18N
        
        this.sectorSize = sectorSize;
        this.size = size;
        this.data = ByteBuffer.allocate(size);
    }
    
    @Override
    public long getSize() {
        checkClosed();
        return this.size;
    }

    @Override
    public void read(long devOffset, ByteBuffer dest) throws IOException {
        checkClosed();
        
        if (devOffset > getSize()){
            final StringBuilder sb = new StringBuilder();
            sb.append("read at ").append(devOffset);
            sb.append(" is off size (").append(getSize()).append(")");
            
            throw new IllegalArgumentException(sb.toString());
        }
        
        data.limit((int) (devOffset + dest.remaining()));
        data.position((int) devOffset);
        
        dest.put(data);
    }

    @Override
    public void write(long devOffset, ByteBuffer src) throws IOException {
        checkClosed();
        
        if (devOffset + src.remaining() > getSize()) throw new
                IllegalArgumentException(
                "offset=" + devOffset +
                ", length=" + src.remaining() +
                ", size=" + getSize());
                
        data.limit((int) (devOffset + src.remaining()));
        data.position((int) devOffset);
        
        
        data.put(src);
    }
    
    /**
     * Returns a slice of the {@code ByteBuffer} that is used by this
     * {@code RamDisk} as it's backing store. The returned buffer will be
     * live (reflecting any changes made through the
     * {@link #write(long, java.nio.ByteBuffer) method}, but read-only.
     *
     * @return a buffer holding the contents of this {@code RamDisk}
     */
    public ByteBuffer getBuffer() {
        return this.data.asReadOnlyBuffer();
    }
    
    @Override
    public void flush() throws IOException {
        checkClosed();
    }
    
    @Override
    public int getSectorSize() {
        checkClosed();
        return this.sectorSize;
    }

    @Override
    public void close() throws IOException {
        this.closed = true;
    }

    @Override
    public boolean isClosed() {
        return this.closed;
    }

    private void checkClosed() {
        if (closed) throw new IllegalStateException("device already closed");
    }

    /**
     * Returns always {@code false}, as a {@code RamDisk} is always writable.
     *
     * @return always {@code false}
     */
    @Override
    public boolean isReadOnly() {
        checkClosed();
        
        return false;
    }
    
}