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 package com.healthmarketscience.jackcess;
29
30 import java.io.Flushable;
31 import java.io.IOException;
32 import java.nio.ByteBuffer;
33 import java.nio.ByteOrder;
34 import java.nio.channels.Channel;
35 import java.nio.channels.FileChannel;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40
41
42
43
44 public class PageChannel implements Channel, Flushable {
45
46 private static final Log LOG = LogFactory.getLog(PageChannel.class);
47
48 static final int INVALID_PAGE_NUMBER = -1;
49
50 static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
51
52
53
54
55 private static final byte[] INVALID_PAGE_BYTE_HEADER =
56 new byte[]{PageTypes.INVALID, (byte)0, (byte)0, (byte)0};
57
58
59 static final int PAGE_GLOBAL_USAGE_MAP = 1;
60
61 static final int ROW_GLOBAL_USAGE_MAP = 0;
62
63
64 private final FileChannel _channel;
65
66 private final boolean _closeChannel;
67
68 private final JetFormat _format;
69
70 private final boolean _autoSync;
71
72
73 private final ByteBuffer _invalidPageBytes =
74 ByteBuffer.wrap(INVALID_PAGE_BYTE_HEADER);
75
76 private final ByteBuffer _forceBytes = ByteBuffer.allocate(1);
77
78 private UsageMap _globalUsageMap;
79
80 private CodecHandler _codecHandler = DefaultCodecProvider.DUMMY_HANDLER;
81
82 private final TempPageHolder _fullPageEncodeBufferH =
83 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
84
85
86
87
88
89 public PageChannel(FileChannel channel, boolean closeChannel,
90 JetFormat format, boolean autoSync)
91 throws IOException
92 {
93 _channel = channel;
94 _closeChannel = closeChannel;
95 _format = format;
96 _autoSync = autoSync;
97 }
98
99
100
101
102 public void initialize(Database database, CodecProvider codecProvider)
103 throws IOException
104 {
105
106 _codecHandler = codecProvider.createHandler(this, database.getCharset());
107
108
109
110 _globalUsageMap = UsageMap.read(database, PAGE_GLOBAL_USAGE_MAP,
111 ROW_GLOBAL_USAGE_MAP, true);
112 }
113
114
115
116
117 PageChannel(boolean testing) {
118 if(!testing) {
119 throw new IllegalArgumentException();
120 }
121 _channel = null;
122 _closeChannel = false;
123 _format = JetFormat.VERSION_4;
124 _autoSync = false;
125 }
126
127 public JetFormat getFormat() {
128 return _format;
129 }
130
131 public boolean isAutoSync() {
132 return _autoSync;
133 }
134
135
136
137
138 private int getNextPageNumber(long size) {
139 return (int)(size / getFormat().PAGE_SIZE);
140 }
141
142
143
144
145 private long getPageOffset(int pageNumber) {
146 return((long) pageNumber * (long) getFormat().PAGE_SIZE);
147 }
148
149
150
151
152 private void validatePageNumber(int pageNumber)
153 throws IOException
154 {
155 int nextPageNumber = getNextPageNumber(_channel.size());
156 if((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
157 throw new IllegalStateException("invalid page number " + pageNumber);
158 }
159 }
160
161
162
163
164
165 public void readPage(ByteBuffer buffer, int pageNumber)
166 throws IOException
167 {
168 validatePageNumber(pageNumber);
169 if (LOG.isDebugEnabled()) {
170 LOG.debug("Reading in page " + Integer.toHexString(pageNumber));
171 }
172 buffer.clear();
173 int bytesRead = _channel.read(
174 buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE);
175 buffer.flip();
176 if(bytesRead != getFormat().PAGE_SIZE) {
177 throw new IOException("Failed attempting to read " +
178 getFormat().PAGE_SIZE + " bytes from page " +
179 pageNumber + ", only read " + bytesRead);
180 }
181
182 if(pageNumber == 0) {
183
184 applyHeaderMask(buffer);
185 } else {
186 _codecHandler.decodePage(buffer, pageNumber);
187 }
188 }
189
190
191
192
193
194
195 public void writePage(ByteBuffer page, int pageNumber) throws IOException {
196 writePage(page, pageNumber, 0);
197 }
198
199
200
201
202
203
204
205
206 public void writePage(ByteBuffer page, int pageNumber, int pageOffset)
207 throws IOException
208 {
209 validatePageNumber(pageNumber);
210
211 page.rewind().position(pageOffset);
212
213 int writeLen = page.remaining();
214 if((writeLen + pageOffset) > getFormat().PAGE_SIZE) {
215 throw new IllegalArgumentException(
216 "Page buffer is too large, size " + (writeLen + pageOffset));
217 }
218
219 ByteBuffer encodedPage = page;
220 if(pageNumber == 0) {
221
222 applyHeaderMask(page);
223 } else {
224
225 if(!_codecHandler.canEncodePartialPage()) {
226 if((pageOffset > 0) && (writeLen < getFormat().PAGE_SIZE)) {
227
228
229
230
231 ByteBuffer fullPage = _fullPageEncodeBufferH.setPage(
232 this, pageNumber);
233
234
235 fullPage.position(pageOffset);
236 fullPage.put(page);
237 fullPage.rewind();
238
239
240 page = fullPage;
241 pageOffset = 0;
242 writeLen = getFormat().PAGE_SIZE;
243
244 } else {
245
246 _fullPageEncodeBufferH.possiblyInvalidate(pageNumber, null);
247 }
248 }
249
250
251 encodedPage = _codecHandler.encodePage(page, pageNumber, pageOffset);
252
253
254 encodedPage.position(pageOffset).limit(pageOffset + writeLen);
255 }
256
257 try {
258 _channel.write(encodedPage, (getPageOffset(pageNumber) + pageOffset));
259 if(_autoSync) {
260 flush();
261 }
262 } finally {
263 if(pageNumber == 0) {
264
265 applyHeaderMask(page);
266 }
267 }
268 }
269
270
271
272
273
274 public int allocateNewPage() throws IOException {
275
276 long size = _channel.size();
277 if(size >= getFormat().MAX_DATABASE_SIZE) {
278 throw new IOException("Database is at maximum size " +
279 getFormat().MAX_DATABASE_SIZE);
280 }
281 if((size % getFormat().PAGE_SIZE) != 0L) {
282 throw new IOException("Database corrupted, file size " + size +
283 " is not multiple of page size " +
284 getFormat().PAGE_SIZE);
285 }
286
287 _forceBytes.rewind();
288
289
290
291 int pageOffset = (getFormat().PAGE_SIZE - _forceBytes.remaining());
292 long offset = size + pageOffset;
293 int pageNumber = getNextPageNumber(size);
294
295
296
297 _channel.write(_forceBytes, offset);
298
299
300
301 _globalUsageMap.removePageNumber(pageNumber, true);
302 return pageNumber;
303 }
304
305
306
307
308 public void deallocatePage(int pageNumber) throws IOException {
309 validatePageNumber(pageNumber);
310
311
312
313 _invalidPageBytes.rewind();
314 _channel.write(_invalidPageBytes, getPageOffset(pageNumber));
315
316 _globalUsageMap.addPageNumber(pageNumber);
317 }
318
319
320
321
322 public ByteBuffer createPageBuffer() {
323 return createBuffer(getFormat().PAGE_SIZE);
324 }
325
326
327
328
329
330 public ByteBuffer createBuffer(int size) {
331 return createBuffer(size, DEFAULT_BYTE_ORDER);
332 }
333
334
335
336
337 public ByteBuffer createBuffer(int size, ByteOrder order) {
338 return ByteBuffer.allocate(size).order(order);
339 }
340
341 public void flush() throws IOException {
342 _channel.force(true);
343 }
344
345 public void close() throws IOException {
346 flush();
347 if(_closeChannel) {
348 _channel.close();
349 }
350 }
351
352 public boolean isOpen() {
353 return _channel.isOpen();
354 }
355
356
357
358
359 private void applyHeaderMask(ByteBuffer buffer) {
360
361 byte[] headerMask = _format.HEADER_MASK;
362 for(int idx = 0; idx < headerMask.length; ++idx) {
363 int pos = idx + _format.OFFSET_MASKED_HEADER;
364 byte b = (byte)(buffer.get(pos) ^ headerMask[idx]);
365 buffer.put(pos, b);
366 }
367 }
368
369
370
371
372
373 public static ByteBuffer narrowBuffer(ByteBuffer buffer, int position,
374 int limit)
375 {
376 return (ByteBuffer)buffer.duplicate()
377 .order(buffer.order())
378 .clear()
379 .limit(limit)
380 .position(position)
381 .mark();
382 }
383
384
385
386
387
388 public static ByteBuffer wrap(byte[] bytes) {
389 return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER);
390 }
391 }