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