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 <waldheinz at gmail.com> 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