1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.Flushable;
20 import java.io.IOException;
21 import java.nio.ByteBuffer;
22 import java.nio.ByteOrder;
23 import java.nio.channels.Channel;
24 import java.nio.channels.FileChannel;
25
26
27
28
29
30
31 public class PageChannel implements Channel, Flushable {
32
33 static final int INVALID_PAGE_NUMBER = -1;
34
35
36 public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
37
38
39
40
41 private static final byte[] INVALID_PAGE_BYTE_HEADER =
42 new byte[]{PageTypes.INVALID, (byte)0, (byte)0, (byte)0};
43
44
45 static final int PAGE_GLOBAL_USAGE_MAP = 1;
46
47 static final int ROW_GLOBAL_USAGE_MAP = 0;
48
49
50
51
52 private static final int FIRST_REUSABLE_PAGE_NUMBER =
53 PAGE_GLOBAL_USAGE_MAP + 1;
54
55
56 private final FileChannel _channel;
57
58 private final boolean _closeChannel;
59
60 private final JetFormat _format;
61
62 private final boolean _autoSync;
63
64
65 private final ByteBuffer _invalidPageBytes =
66 ByteBuffer.wrap(INVALID_PAGE_BYTE_HEADER);
67
68 private final ByteBuffer _forceBytes = ByteBuffer.allocate(1);
69
70 private UsageMap _globalUsageMap;
71
72 private boolean _reuseFreePages;
73
74
75 private int _reuseSearchStart = FIRST_REUSABLE_PAGE_NUMBER;
76
77 private CodecHandler _codecHandler = DefaultCodecProvider.DUMMY_HANDLER;
78
79 private TempPageHolder _fullPageEncodeBufferH;
80 private TempBufferHolder _tempDecodeBufferH;
81 private int _writeCount;
82
83
84
85
86 protected PageChannel(boolean testing) {
87 if(!testing) {
88 throw new IllegalArgumentException();
89 }
90 _channel = null;
91 _closeChannel = false;
92 _format = JetFormat.VERSION_4;
93 _autoSync = false;
94 }
95
96
97
98
99
100 public PageChannel(FileChannel channel, boolean closeChannel,
101 JetFormat format, boolean autoSync)
102 {
103 _channel = channel;
104 _closeChannel = closeChannel;
105 _format = format;
106 _autoSync = autoSync;
107 }
108
109
110
111
112 public void initialize(DatabaseImpl database, CodecProvider codecProvider)
113 throws IOException
114 {
115
116 _codecHandler = codecProvider.createHandler(this, database.getCharset());
117 if(!_codecHandler.canEncodePartialPage()) {
118 _fullPageEncodeBufferH =
119 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
120 }
121 if(!_codecHandler.canDecodeInline()) {
122 _tempDecodeBufferH = TempBufferHolder.newHolder(
123 TempBufferHolder.Type.SOFT, true);
124 }
125
126
127
128 _globalUsageMap = UsageMap.read(database, PAGE_GLOBAL_USAGE_MAP,
129 ROW_GLOBAL_USAGE_MAP, true);
130 }
131
132 public JetFormat getFormat() {
133 return _format;
134 }
135
136 public boolean isAutoSync() {
137 return _autoSync;
138 }
139
140 public boolean isReuseFreePages() {
141 return _reuseFreePages;
142 }
143
144 public void setReuseFreePages(boolean reuseFreePages) {
145 _reuseFreePages = reuseFreePages;
146 }
147
148
149
150
151
152 public void startWrite() {
153 ++_writeCount;
154 }
155
156
157
158
159
160
161 public void startExclusiveWrite() {
162 if(_writeCount != 0) {
163 throw new IllegalArgumentException(
164 "Another write operation is currently in progress");
165 }
166 startWrite();
167 }
168
169
170
171
172
173
174
175
176 public void finishWrite() throws IOException {
177 assertWriting();
178 if((--_writeCount == 0) && _autoSync) {
179 flush();
180 }
181 }
182
183
184
185
186
187 public boolean isWriting() {
188 return(_writeCount > 0);
189 }
190
191
192
193
194 private void assertWriting() {
195 if(!isWriting()) {
196 throw new IllegalStateException("No write operation in progress");
197 }
198 }
199
200
201
202
203 private int getNextPageNumber(long size) {
204 return (int)(size / getFormat().PAGE_SIZE);
205 }
206
207
208
209
210
211
212
213
214 private long getWholePageFileSize() throws IOException {
215 long size = _channel.size();
216 if((size % getFormat().PAGE_SIZE) != 0L) {
217 throw new IOException("Database corrupted, file size " + size +
218 " is not multiple of page size " +
219 getFormat().PAGE_SIZE);
220 }
221 return size;
222 }
223
224
225
226
227 private long getPageOffset(int pageNumber) {
228 return((long) pageNumber * (long) getFormat().PAGE_SIZE);
229 }
230
231
232
233
234
235
236 private void validatePageNumber(int pageNumber)
237 throws IOException
238 {
239 int nextPageNumber = getNextPageNumber(_channel.size());
240 if((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
241 throw new IOException("invalid page number " + pageNumber);
242 }
243 }
244
245
246
247
248
249
250 private void readFully(ByteBuffer buffer, long offset, int pageNumber)
251 throws IOException
252 {
253 int pageSize = getFormat().PAGE_SIZE;
254 int bytesRead = 0;
255 while(bytesRead < pageSize) {
256 int read = _channel.read(buffer, offset + bytesRead);
257 if(read <= 0) {
258
259
260 throw new IOException("Failed attempting to read " + pageSize +
261 " bytes from page " + pageNumber +
262 ", only read " + bytesRead);
263 }
264 bytesRead += read;
265 }
266 }
267
268
269
270
271
272
273 private void writeFully(ByteBuffer buffer, long offset, int pageNumber)
274 throws IOException
275 {
276 int writeLen = buffer.remaining();
277 int bytesWritten = 0;
278 while(bytesWritten < writeLen) {
279 int written = _channel.write(buffer, offset + bytesWritten);
280 if(written <= 0) {
281
282
283 throw new IOException("Failed attempting to write " + writeLen +
284 " bytes to page " + pageNumber +
285 ", only wrote " + bytesWritten);
286 }
287 bytesWritten += written;
288 }
289 }
290
291
292
293
294
295 public void readPage(ByteBuffer buffer, int pageNumber)
296 throws IOException
297 {
298 if(pageNumber == 0) {
299 readRootPage(buffer);
300 return;
301 }
302
303 validatePageNumber(pageNumber);
304
305 ByteBuffer inPage = buffer;
306 ByteBuffer outPage = buffer;
307 if(!_codecHandler.canDecodeInline()) {
308 inPage = _tempDecodeBufferH.getPageBuffer(this);
309 outPage.clear();
310 }
311
312 inPage.clear();
313 readFully(inPage, (long) pageNumber * (long) getFormat().PAGE_SIZE,
314 pageNumber);
315 inPage.flip();
316
317 _codecHandler.decodePage(inPage, outPage, pageNumber);
318 }
319
320
321
322
323 public void readRootPage(ByteBuffer buffer)
324 throws IOException
325 {
326
327
328 buffer.clear();
329 readFully(buffer, 0L, 0);
330 buffer.flip();
331
332
333 applyHeaderMask(buffer);
334 }
335
336
337
338
339
340
341 public void writePage(ByteBuffer page, int pageNumber) throws IOException {
342 writePage(page, pageNumber, 0);
343 }
344
345
346
347
348
349
350
351
352 public void writePage(ByteBuffer page, int pageNumber, int pageOffset)
353 throws IOException
354 {
355 assertWriting();
356 validatePageNumber(pageNumber);
357
358 page.rewind().position(pageOffset);
359
360 int writeLen = page.remaining();
361 if((writeLen + pageOffset) > getFormat().PAGE_SIZE) {
362 throw new IllegalArgumentException(
363 "Page buffer is too large, size " + (writeLen + pageOffset));
364 }
365
366 ByteBuffer encodedPage = page;
367 if(pageNumber == 0) {
368
369 applyHeaderMask(page);
370 } else {
371
372 if(!_codecHandler.canEncodePartialPage()) {
373 if((pageOffset > 0) && (writeLen < getFormat().PAGE_SIZE)) {
374
375
376
377
378 ByteBuffer fullPage = _fullPageEncodeBufferH.setPage(
379 this, pageNumber);
380
381
382 fullPage.position(pageOffset);
383 fullPage.put(page);
384 fullPage.rewind();
385
386
387 page = fullPage;
388 pageOffset = 0;
389 writeLen = getFormat().PAGE_SIZE;
390
391 } else {
392
393 _fullPageEncodeBufferH.possiblyInvalidate(pageNumber, null);
394 }
395 }
396
397
398 encodedPage = _codecHandler.encodePage(page, pageNumber, pageOffset);
399
400
401 encodedPage.position(pageOffset).limit(pageOffset + writeLen);
402 }
403
404 try {
405 writeFully(encodedPage, getPageOffset(pageNumber) + pageOffset,
406 pageNumber);
407 } finally {
408 if(pageNumber == 0) {
409
410 applyHeaderMask(page);
411 }
412 }
413 }
414
415
416
417
418
419 public int allocateNewPage() throws IOException {
420 assertWriting();
421
422 long size = getWholePageFileSize();
423
424 int pageNumber = getNextPageNumber(size);
425
426 if(_reuseFreePages) {
427 int freePageNumber = findFreePage(pageNumber);
428 if(freePageNumber != INVALID_PAGE_NUMBER) {
429
430
431
432 _globalUsageMap.removePageNumber(freePageNumber);
433 return freePageNumber;
434 }
435 }
436
437
438
439
440 if(size >= getFormat().MAX_DATABASE_SIZE) {
441 throw new IOException("Database is at maximum size " +
442 getFormat().MAX_DATABASE_SIZE);
443 }
444
445 _forceBytes.rewind();
446
447
448
449
450 int pageOffset = (getFormat().PAGE_SIZE - _forceBytes.remaining());
451 long offset = size + pageOffset;
452
453
454
455 writeFully(_forceBytes, offset, pageNumber);
456
457 _globalUsageMap.removePageNumber(pageNumber);
458 return pageNumber;
459 }
460
461
462
463
464
465
466
467
468
469
470
471
472
473 private int findFreePage(int nextPageNumber) {
474
475
476 int pageNumber = _globalUsageMap.getNextPageNumberFrom(_reuseSearchStart);
477 if((pageNumber < FIRST_REUSABLE_PAGE_NUMBER) ||
478 (pageNumber >= nextPageNumber)) {
479
480
481
482
483
484 _reuseSearchStart = nextPageNumber + 1;
485 return INVALID_PAGE_NUMBER;
486 }
487 _reuseSearchStart = pageNumber + 1;
488 return pageNumber;
489 }
490
491
492
493
494
495
496
497 public int truncateFreeSpace() throws IOException {
498 assertWriting();
499
500 int nextPageNumber = getNextPageNumber(getWholePageFileSize());
501 int firstFreePageNumber = nextPageNumber;
502 while((firstFreePageNumber > FIRST_REUSABLE_PAGE_NUMBER) &&
503 _globalUsageMap.holdsPageNumber(firstFreePageNumber - 1)) {
504 --firstFreePageNumber;
505 }
506
507 int pageCount = nextPageNumber - firstFreePageNumber;
508 if(pageCount == 0) {
509 return 0;
510 }
511
512
513
514 if(_fullPageEncodeBufferH != null) {
515 _fullPageEncodeBufferH.invalidate();
516 }
517
518
519
520
521
522 _channel.truncate(getPageOffset(firstFreePageNumber));
523
524 return pageCount;
525 }
526
527
528
529
530 public void deallocatePage(int pageNumber) throws IOException {
531 assertWriting();
532
533 validatePageNumber(pageNumber);
534
535
536
537 _invalidPageBytes.rewind();
538 _channel.write(_invalidPageBytes, getPageOffset(pageNumber));
539
540 _globalUsageMap.addPageNumber(pageNumber);
541
542
543
544
545 if(_fullPageEncodeBufferH != null) {
546 _fullPageEncodeBufferH.possiblyInvalidate(pageNumber, null);
547 }
548
549 if((pageNumber < _reuseSearchStart) &&
550 (pageNumber >= FIRST_REUSABLE_PAGE_NUMBER)) {
551
552
553 _reuseSearchStart = pageNumber;
554 }
555 }
556
557
558
559
560 public ByteBuffer createPageBuffer() {
561 return createBuffer(getFormat().PAGE_SIZE);
562 }
563
564
565
566
567
568 public static ByteBuffer createBuffer(int size) {
569 return createBuffer(size, DEFAULT_BYTE_ORDER);
570 }
571
572
573
574
575 public static ByteBuffer createBuffer(int size, ByteOrder order) {
576 return ByteBuffer.allocate(size).order(order);
577 }
578
579 @Override
580 public void flush() throws IOException {
581 _channel.force(true);
582 }
583
584 @Override
585 public void close() throws IOException {
586 flush();
587 if(_closeChannel) {
588 _channel.close();
589 }
590 }
591
592 @Override
593 public boolean isOpen() {
594 return _channel.isOpen();
595 }
596
597
598
599
600 private void applyHeaderMask(ByteBuffer buffer) {
601
602 byte[] headerMask = _format.HEADER_MASK;
603 for(int idx = 0; idx < headerMask.length; ++idx) {
604 int pos = idx + _format.OFFSET_MASKED_HEADER;
605 byte b = (byte)(buffer.get(pos) ^ headerMask[idx]);
606 buffer.put(pos, b);
607 }
608 }
609
610
611
612
613
614 public static ByteBuffer narrowBuffer(ByteBuffer buffer, int position,
615 int limit)
616 {
617 return buffer.duplicate()
618 .order(buffer.order())
619 .clear()
620 .limit(limit)
621 .position(position)
622 .mark();
623 }
624
625
626
627
628
629 public static ByteBuffer wrap(byte[] bytes) {
630 return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER);
631 }
632 }