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