001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store.fs;
007:
008: import java.io.EOFException;
009: import java.io.IOException;
010: import java.util.LinkedHashMap;
011: import java.util.Map;
012:
013: import org.h2.compress.CompressLZF;
014: import org.h2.util.MathUtils;
015:
016: /**
017: * This class is an abstraction of an in-memory random access file.
018: * Data compression using the LZF algorithm is supported as well.
019: */
020: public class FileObjectMemory implements FileObject {
021: private static final int CACHE_SIZE = 8;
022: private static final int BLOCK_SIZE_SHIFT = 16;
023: private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT;
024: private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;
025: private String name;
026: private final boolean compress;
027: private long length;
028: private long pos;
029: private byte[][] data;
030: private long lastModified;
031:
032: private static final CompressLZF LZF = new CompressLZF();
033: private static final byte[] BUFFER = new byte[BLOCK_SIZE * 2];
034: private static final byte[] COMPRESSED_BLOCK;
035:
036: //#ifdef JDK14
037: static class Cache extends LinkedHashMap {
038: private static final long serialVersionUID = 5549197956072850355L;
039: private int size;
040:
041: public Cache(int size) {
042: this .size = size;
043: }
044:
045: protected boolean removeEldestEntry(Map.Entry eldest) {
046: if (size() < size) {
047: return false;
048: }
049: CompressItem c = (CompressItem) eldest.getKey();
050: compress(c.data, c.l);
051: return true;
052: }
053: }
054:
055: static class CompressItem {
056: byte[][] data;
057: int l;
058:
059: public int hashCode() {
060: return data.hashCode() ^ l;
061: }
062:
063: public boolean equals(Object o) {
064: if (o instanceof CompressItem) {
065: CompressItem c = (CompressItem) o;
066: return c.data == data && c.l == l;
067: }
068: return false;
069: }
070: }
071:
072: private static final Cache COMPRESS_LATER = new Cache(CACHE_SIZE);
073:
074: //#endif
075:
076: private static void compressLater(byte[][] data, int l) {
077: //#ifdef JDK14
078: CompressItem c = new CompressItem();
079: c.data = data;
080: c.l = l;
081: synchronized (LZF) {
082: COMPRESS_LATER.put(c, c);
083: }
084: //#endif
085: }
086:
087: private static void expand(byte[][] data, int i) {
088: byte[] d = data[i];
089: if (d.length == BLOCK_SIZE) {
090: return;
091: }
092: byte[] out = new byte[BLOCK_SIZE];
093: synchronized (LZF) {
094: LZF.expand(d, 0, d.length, out, 0, BLOCK_SIZE);
095: }
096: data[i] = out;
097: }
098:
099: private static void compress(byte[][] data, int i) {
100: byte[] d = data[i];
101: synchronized (LZF) {
102: int len = LZF.compress(d, BLOCK_SIZE, BUFFER, 0);
103: if (len <= BLOCK_SIZE) {
104: d = new byte[len];
105: System.arraycopy(BUFFER, 0, d, 0, len);
106: data[i] = d;
107: }
108: }
109: }
110:
111: static {
112: byte[] n = new byte[BLOCK_SIZE];
113: int len = LZF.compress(n, BLOCK_SIZE, BUFFER, 0);
114: COMPRESSED_BLOCK = new byte[len];
115: System.arraycopy(BUFFER, 0, COMPRESSED_BLOCK, 0, len);
116: }
117:
118: public FileObjectMemory(String name, boolean compress) {
119: this .name = name;
120: this .compress = compress;
121: data = new byte[0][];
122: touch();
123: }
124:
125: private void touch() {
126: lastModified = System.currentTimeMillis();
127: }
128:
129: public long length() {
130: return length;
131: }
132:
133: public void setFileLength(long l) {
134: touch();
135: if (l < length) {
136: pos = Math.min(pos, l);
137: changeLength(l);
138: long end = MathUtils.roundUpLong(l, BLOCK_SIZE);
139: if (end != l) {
140: int lastBlock = (int) (l >>> BLOCK_SIZE_SHIFT);
141: expand(data, lastBlock);
142: byte[] d = data[lastBlock];
143: for (int i = (int) (l & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
144: d[i] = 0;
145: }
146: if (compress) {
147: compressLater(data, lastBlock);
148: }
149: }
150: } else {
151: changeLength(l);
152: }
153: }
154:
155: public void seek(long pos) {
156: this .pos = (int) pos;
157: }
158:
159: private void changeLength(long len) {
160: length = len;
161: len = MathUtils.roundUpLong(len, BLOCK_SIZE);
162: int blocks = (int) (len >>> BLOCK_SIZE_SHIFT);
163: if (blocks != data.length) {
164: byte[][] n = new byte[blocks][];
165: System.arraycopy(data, 0, n, 0, Math.min(data.length,
166: n.length));
167: for (int i = data.length; i < blocks; i++) {
168: n[i] = COMPRESSED_BLOCK;
169: }
170: data = n;
171: }
172:
173: }
174:
175: private void readWrite(byte[] b, int off, int len, boolean write)
176: throws IOException {
177: long end = pos + len;
178: if (end > length) {
179: if (write) {
180: changeLength(end);
181: } else {
182: if (len == 0) {
183: return;
184: }
185: throw new EOFException("File: " + name);
186: }
187: }
188: while (len > 0) {
189: int l = (int) Math.min(len, BLOCK_SIZE
190: - (pos & BLOCK_SIZE_MASK));
191: int id = (int) (pos >>> BLOCK_SIZE_SHIFT);
192: expand(data, id);
193: byte[] block = data[id];
194: int blockOffset = (int) (pos & BLOCK_SIZE_MASK);
195: if (write) {
196: System.arraycopy(b, off, block, blockOffset, l);
197: } else {
198: System.arraycopy(block, blockOffset, b, off, l);
199: }
200: if (compress) {
201: compressLater(data, id);
202: }
203: off += l;
204: pos += l;
205: len -= l;
206: }
207: }
208:
209: public void write(byte[] b, int off, int len) throws IOException {
210: touch();
211: readWrite(b, off, len, true);
212: }
213:
214: public void readFully(byte[] b, int off, int len)
215: throws IOException {
216: readWrite(b, off, len, false);
217: }
218:
219: public long getFilePointer() {
220: return pos;
221: }
222:
223: public void close() {
224: pos = 0;
225: }
226:
227: public void sync() throws IOException {
228: }
229:
230: public void setName(String name) {
231: this .name = name;
232: }
233:
234: public String getName() {
235: return name;
236: }
237:
238: public long getLastModified() {
239: return lastModified;
240: }
241: }
|