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.IOException;
20 import java.lang.System.Logger;
21 import java.nio.ByteBuffer;
22 import java.nio.ByteOrder;
23 import java.util.Collection;
24
25 import com.healthmarketscience.jackcess.InvalidValueException;
26
27
28
29
30
31
32
33 class LongValueColumnImpl extends ColumnImpl
34 {
35
36
37
38
39 private static final byte LONG_VALUE_TYPE_THIS_PAGE = (byte) 0x80;
40
41
42
43
44 private static final byte LONG_VALUE_TYPE_OTHER_PAGE = (byte) 0x40;
45
46
47
48
49 private static final byte LONG_VALUE_TYPE_OTHER_PAGES = (byte) 0x00;
50
51
52
53
54 private static final int LONG_VALUE_TYPE_MASK = 0xC0000000;
55
56
57
58 private LongValueBufferHolder _lvalBufferH;
59 private int _maxLenInUnits = INVALID_LENGTH;
60
61 LongValueColumnImpl(InitArgs args)
62 {
63 super(args);
64 }
65
66 @Override
67 public int getOwnedPageCount() {
68 return ((_lvalBufferH == null) ? 0 : _lvalBufferH.getOwnedPageCount());
69 }
70
71 @Override
72 void setUsageMaps(UsageMap./../../com/healthmarketscience/jackcess/impl/UsageMap.html#UsageMap">UsageMap ownedPages, UsageMap freeSpacePages) {
73 if(ownedPages == null) {
74
75
76 return;
77 }
78 _lvalBufferH = new UmapLongValueBufferHolder(ownedPages, freeSpacePages);
79 }
80
81 @Override
82 void collectUsageMapPages(Collection<Integer> pages) {
83 _lvalBufferH.collectUsageMapPages(pages);
84 }
85
86 @Override
87 void postTableLoadInit() throws IOException {
88 if(_lvalBufferH == null) {
89 _lvalBufferH = new LegacyLongValueBufferHolder();
90 }
91 super.postTableLoadInit();
92 }
93
94 protected final int getMaxLengthInUnits() {
95 if(_maxLenInUnits == INVALID_LENGTH) {
96 _maxLenInUnits = calcMaxLengthInUnits();
97 }
98 return _maxLenInUnits;
99 }
100
101 protected int calcMaxLengthInUnits() {
102 return getType().toUnitSize(getType().getMaxSize(), getFormat());
103 }
104
105 @Override
106 public Object read(byte[] data, ByteOrder order) throws IOException {
107 switch(getType()) {
108 case OLE:
109 if (data.length > 0) {
110 return readLongValue(data);
111 }
112 return null;
113 case MEMO:
114 if (data.length > 0) {
115 return readLongStringValue(data);
116 }
117 return null;
118 default:
119 throw new RuntimeException(withErrorContext(
120 "unexpected var length, long value type: " + getType()));
121 }
122 }
123
124 @Override
125 protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
126 ByteOrder order)
127 throws IOException
128 {
129 switch(getType()) {
130 case OLE:
131
132 break;
133 case MEMO:
134 obj = encodeTextValue(obj, 0, getMaxLengthInUnits(), false).array();
135 break;
136 default:
137 throw new RuntimeException(withErrorContext(
138 "unexpected var length, long value type: " + getType()));
139 }
140
141
142 return writeLongValue(toByteArray(obj), remainingRowLength);
143 }
144
145
146
147
148
149 protected byte[] readLongValue(byte[] lvalDefinition)
150 throws IOException
151 {
152 ByteBuffer def = PageChannel.wrap(lvalDefinition);
153 int lengthWithFlags = def.getInt();
154 int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
155
156 byte[] rtn = new byte[length];
157 byte type = (byte)((lengthWithFlags & LONG_VALUE_TYPE_MASK) >>> 24);
158
159 if(type == LONG_VALUE_TYPE_THIS_PAGE) {
160
161
162 def.getInt();
163 def.getInt();
164
165 int rowLen = def.remaining();
166 if(rowLen < length) {
167
168 LOG.log(Logger.Level.WARNING, withErrorContext(
169 "Value may be truncated: expected length " +
170 length + " found " + rowLen));
171 rtn = new byte[rowLen];
172 }
173
174 def.get(rtn);
175
176 } else {
177
178
179 if (lvalDefinition.length != getFormat().SIZE_LONG_VALUE_DEF) {
180 throw new IOException(withErrorContext(
181 "Expected " + getFormat().SIZE_LONG_VALUE_DEF +
182 " bytes in long value definition, but found " +
183 lvalDefinition.length));
184 }
185
186 int rowNum = ByteUtil.getUnsignedByte(def);
187 int pageNum = ByteUtil.get3ByteInt(def, def.position());
188 ByteBuffer lvalPage = getPageChannel().createPageBuffer();
189
190 switch (type) {
191 case LONG_VALUE_TYPE_OTHER_PAGE:
192 {
193 getPageChannel().readPage(lvalPage, pageNum);
194
195 short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
196 short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
197
198 int rowLen = rowEnd - rowStart;
199 if(rowLen < length) {
200
201 LOG.log(Logger.Level.WARNING, withErrorContext(
202 "Value may be truncated: expected length " +
203 length + " found " + rowLen));
204 rtn = new byte[rowLen];
205 }
206
207 lvalPage.position(rowStart);
208 lvalPage.get(rtn);
209 }
210 break;
211
212 case LONG_VALUE_TYPE_OTHER_PAGES:
213
214 ByteBuffer rtnBuf = ByteBuffer.wrap(rtn);
215 int remainingLen = length;
216 while(remainingLen > 0) {
217 lvalPage.clear();
218 getPageChannel().readPage(lvalPage, pageNum);
219
220 short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
221 short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
222
223
224 lvalPage.position(rowStart);
225 rowNum = ByteUtil.getUnsignedByte(lvalPage);
226 pageNum = ByteUtil.get3ByteInt(lvalPage);
227
228
229 int chunkLength = (rowEnd - rowStart) - 4;
230 if(chunkLength > remainingLen) {
231 rowEnd = (short)(rowEnd - (chunkLength - remainingLen));
232 chunkLength = remainingLen;
233 }
234 remainingLen -= chunkLength;
235
236 lvalPage.limit(rowEnd);
237 rtnBuf.put(lvalPage);
238 }
239
240 break;
241
242 default:
243 throw new IOException(withErrorContext(
244 "Unrecognized long value type: " + type));
245 }
246 }
247
248 return rtn;
249 }
250
251
252
253
254
255 private String readLongStringValue(byte[] lvalDefinition)
256 throws IOException
257 {
258 byte[] binData = readLongValue(lvalDefinition);
259 if(binData == null) {
260 return null;
261 }
262 if(binData.length == 0) {
263 return "";
264 }
265 return decodeTextValue(binData);
266 }
267
268
269
270
271
272
273
274
275
276 protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength)
277 throws IOException
278 {
279 if(value.length > getType().getMaxSize()) {
280 throw new InvalidValueException(withErrorContext(
281 "value too big for column, max " +
282 getType().getMaxSize() + ", got " + value.length));
283 }
284
285
286 byte type = 0;
287 int lvalDefLen = getFormat().SIZE_LONG_VALUE_DEF;
288 if(((getFormat().SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength)
289 && (value.length <= getFormat().MAX_INLINE_LONG_VALUE_SIZE)) {
290 type = LONG_VALUE_TYPE_THIS_PAGE;
291 lvalDefLen += value.length;
292 } else if(value.length <= getFormat().MAX_LONG_VALUE_ROW_SIZE) {
293 type = LONG_VALUE_TYPE_OTHER_PAGE;
294 } else {
295 type = LONG_VALUE_TYPE_OTHER_PAGES;
296 }
297
298 ByteBuffer def = PageChannel.createBuffer(lvalDefLen);
299
300 int lengthWithFlags = value.length | (type << 24);
301 def.putInt(lengthWithFlags);
302
303 if(type == LONG_VALUE_TYPE_THIS_PAGE) {
304
305
306 def.putInt(0);
307 def.putInt(0);
308 def.put(value);
309 } else {
310
311 ByteBuffer lvalPage = null;
312 int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
313 byte firstLvalRow = 0;
314
315
316 switch(type) {
317 case LONG_VALUE_TYPE_OTHER_PAGE:
318 lvalPage = _lvalBufferH.getLongValuePage(value.length);
319 firstLvalPageNum = _lvalBufferH.getPageNumber();
320 firstLvalRow = (byte)TableImpl.addDataPageRow(lvalPage, value.length,
321 getFormat(), 0);
322 lvalPage.put(value);
323 getPageChannel().writePage(lvalPage, firstLvalPageNum);
324 break;
325
326 case LONG_VALUE_TYPE_OTHER_PAGES:
327
328 ByteBuffer buffer = ByteBuffer.wrap(value);
329 int remainingLen = buffer.remaining();
330 buffer.limit(0);
331 lvalPage = _lvalBufferH.getLongValuePage(remainingLen);
332 firstLvalPageNum = _lvalBufferH.getPageNumber();
333 firstLvalRow = (byte)TableImpl.getRowsOnDataPage(lvalPage, getFormat());
334 int lvalPageNum = firstLvalPageNum;
335 ByteBuffer nextLvalPage = null;
336 int nextLvalPageNum = 0;
337 int nextLvalRowNum = 0;
338 while(remainingLen > 0) {
339 lvalPage.clear();
340
341
342
343 int chunkLength = Math.min(getFormat().MAX_LONG_VALUE_ROW_SIZE - 4,
344 remainingLen);
345
346
347 if(chunkLength < remainingLen) {
348
349 _lvalBufferH.clear();
350 nextLvalPage = _lvalBufferH.getLongValuePage(
351 (remainingLen - chunkLength) + 4);
352 nextLvalPageNum = _lvalBufferH.getPageNumber();
353 nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage,
354 getFormat());
355 } else {
356 nextLvalPage = null;
357 nextLvalPageNum = 0;
358 nextLvalRowNum = 0;
359 }
360
361
362 TableImpl.addDataPageRow(lvalPage, chunkLength + 4, getFormat(), 0);
363
364
365 lvalPage.put((byte)nextLvalRowNum);
366 ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum);
367
368
369 buffer.limit(buffer.limit() + chunkLength);
370 lvalPage.put(buffer);
371 remainingLen -= chunkLength;
372
373
374 getPageChannel().writePage(lvalPage, lvalPageNum);
375
376
377 lvalPage = nextLvalPage;
378 lvalPageNum = nextLvalPageNum;
379 }
380 break;
381
382 default:
383 throw new IOException(withErrorContext(
384 "Unrecognized long value type: " + type));
385 }
386
387
388 def.put(firstLvalRow);
389 ByteUtil.put3ByteInt(def, firstLvalPageNum);
390 def.putInt(0);
391
392 }
393
394 def.flip();
395 return def;
396 }
397
398
399
400
401 private void writeLongValueHeader(ByteBuffer lvalPage)
402 {
403 lvalPage.put(PageTypes.DATA);
404 lvalPage.put((byte) 1);
405 lvalPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE);
406 lvalPage.put((byte) 'L');
407 lvalPage.put((byte) 'V');
408 lvalPage.put((byte) 'A');
409 lvalPage.put((byte) 'L');
410 lvalPage.putInt(0);
411 lvalPage.putShort((short)0);
412 }
413
414
415
416
417
418 private abstract class LongValueBufferHolder
419 {
420
421
422
423 public ByteBuffer getLongValuePage(int dataLength) throws IOException {
424
425 TempPageHolder lvalBufferH = getBufferHolder();
426 dataLength = Math.min(dataLength, getFormat().MAX_LONG_VALUE_ROW_SIZE);
427
428 ByteBuffer lvalPage = null;
429 if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
430 lvalPage = lvalBufferH.getPage(getPageChannel());
431 if(TableImpl.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
432
433 return lvalPage;
434 }
435 }
436
437
438 return findNewPage(dataLength);
439 }
440
441 protected ByteBuffer findNewPage(int dataLength) throws IOException {
442 ByteBuffer lvalPage = getBufferHolder().setNewPage(getPageChannel());
443 writeLongValueHeader(lvalPage);
444 return lvalPage;
445 }
446
447 public int getOwnedPageCount() {
448 return 0;
449 }
450
451
452
453
454 public int getPageNumber() {
455 return getBufferHolder().getPageNumber();
456 }
457
458
459
460
461 public void clear() throws IOException {
462 getBufferHolder().clear();
463 }
464
465 public void collectUsageMapPages(Collection<Integer> pages) {
466
467 }
468
469 protected abstract TempPageHolder getBufferHolder();
470 }
471
472
473
474
475
476
477 private final class LegacyLongValueBufferHolder extends LongValueBufferHolder
478 {
479 @Override
480 protected TempPageHolder getBufferHolder() {
481 return getTable().getLongValueBuffer();
482 }
483 }
484
485
486
487
488 private final class UmapLongValueBufferHolder extends LongValueBufferHolder
489 {
490
491 private final UsageMap _ownedPages;
492
493 private final UsageMap _freeSpacePages;
494
495 private final TempPageHolder _longValueBufferH =
496 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
497
498 private UmapLongValueBufferHolder(UsageMap ownedPages,
499 UsageMap freeSpacePages) {
500 _ownedPages = ownedPages;
501 _freeSpacePages = freeSpacePages;
502 }
503
504 @Override
505 protected TempPageHolder getBufferHolder() {
506 return _longValueBufferH;
507 }
508
509 @Override
510 public int getOwnedPageCount() {
511 return _ownedPages.getPageCount();
512 }
513
514 @Override
515 protected ByteBuffer findNewPage(int dataLength) throws IOException {
516
517
518 ByteBuffer newPage = TableImpl.findFreeRowSpace(
519 _ownedPages, _freeSpacePages, _longValueBufferH);
520
521 if(newPage != null) {
522 if(TableImpl.rowFitsOnDataPage(dataLength, newPage, getFormat())) {
523 return newPage;
524 }
525
526 clear();
527 }
528
529
530 newPage = super.findNewPage(dataLength);
531 int pageNumber = getPageNumber();
532 _ownedPages.addPageNumber(pageNumber);
533 _freeSpacePages.addPageNumber(pageNumber);
534 return newPage;
535 }
536
537 @Override
538 public void clear() throws IOException {
539 int pageNumber = getPageNumber();
540 if(pageNumber != PageChannel.INVALID_PAGE_NUMBER) {
541 _freeSpacePages.removePageNumber(pageNumber);
542 }
543 super.clear();
544 }
545
546 @Override
547 public void collectUsageMapPages(Collection<Integer> pages) {
548 pages.add(_ownedPages.getTablePageNumber());
549 pages.add(_freeSpacePages.getTablePageNumber());
550 }
551 }
552 }