1// viewx.cpp -- 2// $Id: viewx.cpp 1230 2007-03-09 15:58:53Z jcw $ 3// This is part of Metakit, see http://www.equi4.com/metakit.html 4 5/** @file 6 * Implements c4_Sequence, c4_Reference, and c4_...Ref 7 */ 8 9#include "header.h" 10#include "handler.h" 11#include "store.h" 12#include "column.h" 13 14///////////////////////////////////////////////////////////////////////////// 15 16c4_Sequence::c4_Sequence(): _refCount(0), _dependencies(0), _propertyLimit(0), 17 _tempBuf(0){} 18 19c4_Sequence::~c4_Sequence() { 20 d4_assert(_refCount == 0); 21 22 d4_assert(!_dependencies); // there can be no dependencies left 23 24 ClearCache(); 25 26 delete _tempBuf; 27} 28 29c4_Persist *c4_Sequence::Persist()const { 30 return 0; 31} 32 33/// Increment the reference count of this sequence 34void c4_Sequence::IncRef() { 35 ++_refCount; 36 37 d4_assert(_refCount != 0); 38} 39 40/// Decrement the reference count, delete objects when last 41void c4_Sequence::DecRef() { 42 d4_assert(_refCount != 0); 43 44 if (--_refCount == 0) 45 delete this; 46} 47 48/// Return the current reference count 49int c4_Sequence::NumRefs()const { 50 return _refCount; 51} 52 53/// Compare the specified row with another one 54int c4_Sequence::Compare(int index_, c4_Cursor cursor_)const { 55 d4_assert(cursor_._seq != 0); 56 57 c4_Bytes data; 58 59 for (int colNum = 0; colNum < NumHandlers(); ++colNum) { 60 c4_Handler &h = NthHandler(colNum); 61 62 const c4_Sequence *hc = HandlerContext(colNum); 63 int i = RemapIndex(index_, hc); 64 65 if (!cursor_._seq->Get(cursor_._index, h.PropId(), data)) 66 h.ClearBytes(data); 67 68 int f = h.Compare(i, data); 69 if (f != 0) 70 return f; 71 } 72 73 return 0; 74} 75 76/// Restrict the search range for rows 77bool c4_Sequence::RestrictSearch(c4_Cursor, int &, int &) { 78 return true; 79} 80 81/// Replace the contents of a specified row 82void c4_Sequence::SetAt(int index_, c4_Cursor newElem_) { 83 d4_assert(newElem_._seq != 0); 84 85 c4_Bytes data; 86 87 c4_Notifier change(this); 88 if (GetDependencies()) 89 change.StartSetAt(index_, newElem_); 90 91 for (int i = 0; i < newElem_._seq->NumHandlers(); ++i) { 92 c4_Handler &h = newElem_._seq->NthHandler(i); 93 94 // added 06-12-1999 to do index remapping for derived seq's 95 const c4_Sequence *hc = newElem_._seq->HandlerContext(i); 96 int ri = newElem_._seq->RemapIndex(newElem_._index, hc); 97 98 h.GetBytes(ri, data); 99 100 // Set(index_, cursor._seq->NthProperty(i), data); 101 int colNum = PropIndex(h.Property()); 102 d4_assert(colNum >= 0); 103 104 NthHandler(colNum).Set(index_, data); 105 } 106 107 // if number of props in dest is larger after adding, clear the rest 108 // this way, new props get copied and undefined props get cleared 109 if (newElem_._seq->NumHandlers() < NumHandlers()) { 110 for (int j = 0; j < NumHandlers(); ++j) { 111 c4_Handler &h = NthHandler(j); 112 113 // if the property does not appear in the source 114 if (newElem_._seq->PropIndex(h.PropId()) < 0) { 115 h.ClearBytes(data); 116 h.Set(index_, data); 117 } 118 } 119 } 120} 121 122/// Remap the index to an underlying view 123int c4_Sequence::RemapIndex(int index_, const c4_Sequence *seq_)const { 124 return seq_ == this ? index_ : - 1; 125} 126 127/// Gives access to a general purpose temporary buffer 128c4_Bytes &c4_Sequence::Buffer() { 129 if (_tempBuf == 0) 130 _tempBuf = d4_new c4_Bytes; 131 return *_tempBuf; 132} 133 134// 1.8.5: extra buffer to hold returned description strings 135const char *c4_Sequence::UseTempBuffer(const char *str_) { 136 return strcpy((char*)Buffer().SetBuffer(strlen(str_) + 1), str_); 137} 138 139/// Change number of rows, either by inserting or removing them 140void c4_Sequence::Resize(int newSize_, int) { 141 if (NumHandlers() > 0) { 142 int diff = newSize_ - NumRows(); 143 144 if (diff > 0) { 145 c4_Row empty; // make sure this doesn't recurse, see below 146 InsertAt(NumRows(), &empty, diff); 147 } else if (diff < 0) 148 RemoveAt(newSize_, - diff); 149 } else 150 // need special case to avoid recursion for c4_Row allocations 151 SetNumRows(newSize_); 152} 153 154/// Insert one or more rows into this sequence 155void c4_Sequence::InsertAt(int index_, c4_Cursor newElem_, int count_) { 156 d4_assert(newElem_._seq != 0); 157 158 c4_Notifier change(this); 159 if (GetDependencies()) 160 change.StartInsertAt(index_, newElem_, count_); 161 162 SetNumRows(NumRows() + count_); 163 164 c4_Bytes data; 165 166 for (int i = 0; i < newElem_._seq->NumHandlers(); ++i) { 167 c4_Handler &h = newElem_._seq->NthHandler(i); 168 169 // added 06-12-1999 to do index remapping for derived seq's 170 const c4_Sequence *hc = newElem_._seq->HandlerContext(i); 171 int ri = newElem_._seq->RemapIndex(newElem_._index, hc); 172 173 int colNum = PropIndex(h.Property()); 174 d4_assert(colNum >= 0); 175 176 if (h.Property().Type() == 'V') { 177 // If inserting from self: Make sure we get a copy of the bytes, 178 // so we don't get an invalid pointer if the memory get realloc'ed 179 h.GetBytes(ri, data, newElem_._seq == this); 180 181 // special treatment for subviews, insert empty, then overwrite 182 // changed 19990904 - probably fixes a long-standing limitation 183 c4_Bytes temp; 184 h.ClearBytes(temp); 185 186 c4_Handler &h2 = NthHandler(colNum); 187 h2.Insert(index_, temp, count_); 188 189 for (int j = 0; j < count_; ++j) 190 h2.Set(index_ + j, data); 191 } else { 192 h.GetBytes(ri, data); 193 NthHandler(colNum).Insert(index_, data, count_); 194 } 195 } 196 197 // if number of props in dest is larger after adding, clear the rest 198 // this way, new props get copied and undefined props get cleared 199 if (newElem_._seq->NumHandlers() < NumHandlers()) { 200 for (int j = 0; j < NumHandlers(); ++j) { 201 c4_Handler &h = NthHandler(j); 202 203 // if the property does not appear in the source 204 if (newElem_._seq->PropIndex(h.PropId()) < 0) { 205 h.ClearBytes(data); 206 h.Insert(index_, data, count_); 207 } 208 } 209 } 210} 211 212/// Remove one or more rows from this sequence 213void c4_Sequence::RemoveAt(int index_, int count_) { 214 c4_Notifier change(this); 215 if (GetDependencies()) 216 change.StartRemoveAt(index_, count_); 217 218 SetNumRows(NumRows() - count_); 219 220 //! careful, this does no index remapping, wrong for derived seq's 221 for (int i = 0; i < NumHandlers(); ++i) 222 NthHandler(i).Remove(index_, count_); 223} 224 225/// Move a row to another position 226void c4_Sequence::Move(int from_, int to_) { 227 c4_Notifier change(this); 228 if (GetDependencies()) 229 change.StartMove(from_, to_); 230 231 //! careful, this does no index remapping, wrong for derived seq's 232 for (int i = 0; i < NumHandlers(); ++i) 233 NthHandler(i).Move(from_, to_); 234} 235 236/// Return the id of the N-th property 237int c4_Sequence::NthPropId(int index_)const { 238 return NthHandler(index_).PropId(); 239} 240 241void c4_Sequence::ClearCache() { 242 if (_propertyLimit > 0) { 243 delete [] _propertyMap; // property indexes may change 244 _propertyLimit = 0; 245 } 246} 247 248/// Find the index of a property by its id 249int c4_Sequence::PropIndex(int propId_) { 250 //! CACHING NOTE: derived views will fail if underlying view is restructured 251 // still, this cache is kept, since sort will fail anyway... 252 // The only safe change in these cases is adding new properties at the end. 253 254 // use the map for the fastest result once known 255 if (propId_ < _propertyLimit && _propertyMap[propId_] >= 0) 256 return _propertyMap[propId_]; 257 258 // locate the property using a linear search, return if not present 259 int n = NumHandlers(); 260 do { 261 if (--n < 0) 262 return - 1; 263 } while (NthPropId(n) != propId_); 264 265 // if the map is too small, resize it (with a little slack) 266 if (propId_ >= _propertyLimit) { 267 int round = (propId_ + 8) &~0x07; 268 short *vec = d4_new short[round]; 269 270 for (int i = 0; i < round; ++i) 271 vec[i] = i < _propertyLimit ? _propertyMap[i]: - 1; 272 273 if (_propertyLimit > 0) 274 delete [] _propertyMap; 275 276 _propertyMap = vec; 277 _propertyLimit = round; 278 } 279 280 // we have a map, adjust the entry and return 281 return _propertyMap[propId_] = n; 282} 283 284/// Find the index of a property, or create a new entry 285int c4_Sequence::PropIndex(const c4_Property &prop_) { 286 int pos = PropIndex(prop_.GetId()); 287 if (pos >= 0) { 288 d4_assert(NthHandler(pos).Property().Type() == prop_.Type()); 289 return pos; 290 } 291 292 c4_Handler *h = CreateHandler(prop_); 293 d4_assert(h != 0); 294 295 int i = AddHandler(h); 296 if (i >= 0 && NumRows() > 0) { 297 c4_Bytes data; 298 h->ClearBytes(data); 299 h->Insert(0, data, NumRows()); 300 } 301 302 return i; 303} 304 305const char *c4_Sequence::Description() { 306 return 0; 307} 308 309int c4_Sequence::ItemSize(int index_, int propId_) { 310 int colNum = PropIndex(propId_); 311 return colNum >= 0 ? NthHandler(colNum).ItemSize(index_): - 1; 312} 313 314bool c4_Sequence::Get(int index_, int propId_, c4_Bytes &buf_) { 315 int colNum = PropIndex(propId_); 316 if (colNum < 0) 317 return false; 318 319 NthHandler(colNum).GetBytes(index_, buf_); 320 return true; 321} 322 323void c4_Sequence::Set(int index_, const c4_Property &prop_, const c4_Bytes 324 &buf_) { 325 int colNum = PropIndex(prop_); 326 d4_assert(colNum >= 0); 327 328 c4_Handler &h = NthHandler(colNum); 329 330 c4_Notifier change(this); 331 if (GetDependencies()) 332 change.StartSet(index_, prop_.GetId(), buf_); 333 334 if (buf_.Size()) 335 h.Set(index_, buf_); 336 else { 337 c4_Bytes empty; 338 h.ClearBytes(empty); 339 h.Set(index_, empty); 340 } 341} 342 343/// Register a sequence to receive change notifications 344void c4_Sequence::Attach(c4_Sequence *child_) { 345 IncRef(); 346 347 if (!_dependencies) 348 _dependencies = d4_new c4_Dependencies; 349 350 _dependencies->Add(child_); 351} 352 353/// Unregister a sequence which received change notifications 354void c4_Sequence::Detach(c4_Sequence *child_) { 355 d4_assert(_dependencies != 0); 356 357 if (!_dependencies->Remove(child_)) { 358 delete _dependencies; 359 _dependencies = 0; 360 } 361 362 DecRef(); 363} 364 365/// Called just before a change is made to the sequence 366c4_Notifier *c4_Sequence::PreChange(c4_Notifier &) { 367 d4_assert(0); // should not be called, because it should not attach 368 return 0; 369} 370 371/// Called after changes have been made to the sequence 372void c4_Sequence::PostChange(c4_Notifier &){} 373 374///////////////////////////////////////////////////////////////////////////// 375 376c4_Reference &c4_Reference::operator = (const c4_Reference &value_) { 377 c4_Bytes result; 378 value_.GetData(result); 379 SetData(result); 380 381 return *this; 382} 383 384bool operator == (const c4_Reference &a_, const c4_Reference &b_) { 385 c4_Bytes buf1; 386 bool f1 = a_.GetData(buf1); 387 388 c4_Bytes buf2; 389 bool f2 = b_.GetData(buf2); 390 391 // if absent, fill either with zero bytes to match length 392 if (!f1) 393 buf1.SetBufferClear(buf2.Size()); 394 if (!f2) 395 buf2.SetBufferClear(buf1.Size()); 396 397 return buf1 == buf2; 398} 399 400///////////////////////////////////////////////////////////////////////////// 401 402c4_IntRef::operator t4_i32()const { 403 c4_Bytes result; 404 if (!GetData(result)) 405 return 0; 406 407 d4_assert(result.Size() == sizeof(t4_i32)); 408 return *(const t4_i32*)result.Contents(); 409} 410 411c4_IntRef &c4_IntRef::operator = (t4_i32 value_) { 412 SetData(c4_Bytes(&value_, sizeof value_)); 413 return *this; 414} 415 416///////////////////////////////////////////////////////////////////////////// 417#if !q4_TINY 418///////////////////////////////////////////////////////////////////////////// 419 420c4_LongRef::operator t4_i64()const { 421 c4_Bytes result; 422 if (!GetData(result)) { 423 static t4_i64 zero; 424 return zero; 425 } 426 427 d4_assert(result.Size() == sizeof(t4_i64)); 428 return *(const t4_i64*)result.Contents(); 429} 430 431c4_LongRef &c4_LongRef::operator = (t4_i64 value_) { 432 SetData(c4_Bytes(&value_, sizeof value_)); 433 return *this; 434} 435 436///////////////////////////////////////////////////////////////////////////// 437 438c4_FloatRef::operator double()const { 439 c4_Bytes result; 440 if (!GetData(result)) 441 return 0; 442 443 d4_assert(result.Size() == sizeof(float)); 444 return *(const float*)result.Contents(); 445} 446 447c4_FloatRef &c4_FloatRef::operator = (double value_) { 448 float v = (float)value_; // loses precision 449 SetData(c4_Bytes(&v, sizeof v)); 450 return *this; 451} 452 453///////////////////////////////////////////////////////////////////////////// 454 455c4_DoubleRef::operator double()const { 456 c4_Bytes result; 457 if (!GetData(result)) 458 return 0; 459 460 d4_assert(result.Size() == sizeof(double)); 461 return *(const double*)result.Contents(); 462} 463 464c4_DoubleRef &c4_DoubleRef::operator = (double value_) { 465 SetData(c4_Bytes(&value_, sizeof value_)); 466 return *this; 467} 468 469///////////////////////////////////////////////////////////////////////////// 470#endif // !q4_TINY 471///////////////////////////////////////////////////////////////////////////// 472 473c4_BytesRef::operator c4_Bytes()const { 474 c4_Bytes result; 475 GetData(result); 476 477 // the result must immediately be used, its lifetime may be limited 478 return result; 479} 480 481c4_BytesRef &c4_BytesRef::operator = (const c4_Bytes &value_) { 482 SetData(value_); 483 return *this; 484} 485 486c4_Bytes c4_BytesRef::Access(t4_i32 off_, int len_, bool noCopy_)const { 487 c4_Bytes &buffer = _cursor._seq->Buffer(); 488 489 int colNum = _cursor._seq->PropIndex(_property.GetId()); 490 if (colNum >= 0) { 491 c4_Handler &h = _cursor._seq->NthHandler(colNum); 492 int sz = h.ItemSize(_cursor._index); 493 if (len_ == 0 || off_ + len_ > sz) 494 len_ = sz - off_; 495 496 if (len_ > 0) { 497 c4_Column *col = h.GetNthMemoCol(_cursor._index, true); 498 if (col != 0) { 499 if (noCopy_) { 500 // 21-11-2005 optimization by A. Stigsen 501 // return just the first segment (even if it is smaller than 502 // len). this avoids any expensive memcopies, but you have to 503 // remember to check length of the returned bytes. 504 c4_ColIter iter(*col, off_, off_ + len_); 505 iter.Next(); 506 return c4_Bytes(iter.BufLoad(), iter.BufLen() < len_ ? iter.BufLen(): 507 len_); 508 } else { 509 const t4_byte *bytes = col->FetchBytes(off_, len_, buffer, false); 510 if (bytes == buffer.Contents()) 511 return buffer; 512 return c4_Bytes(bytes, len_); 513 } 514 } else 515 { // do it the hard way for custom/mapped views (2002-03-13) 516 c4_Bytes result; 517 GetData(result); 518 d4_assert(off_ + len_ <= result.Size()); 519 return c4_Bytes(result.Contents() + off_, len_, true); 520 } 521 } 522 } 523 524 return c4_Bytes(); 525} 526 527bool c4_BytesRef::Modify(const c4_Bytes &buf_, t4_i32 off_, int diff_)const { 528 int colNum = _cursor._seq->PropIndex(_property.GetId()); 529 if (colNum >= 0) { 530 c4_Handler &h = _cursor._seq->NthHandler(colNum); 531 const int n = buf_.Size(); 532 const t4_i32 limit = off_ + n; // past changed bytes 533 const t4_i32 overshoot = limit - h.ItemSize(_cursor._index); 534 535 if (diff_ < overshoot) 536 diff_ = overshoot; 537 538 c4_Column *col = h.GetNthMemoCol(_cursor._index, true); 539 if (col != 0) { 540 if (diff_ < 0) 541 col->Shrink(limit, - diff_); 542 else if (diff_ > 0) 543 // insert bytes in the highest possible spot 544 // if a gap is created, it will contain garbage 545 col->Grow(overshoot > 0 ? col->ColSize(): diff_ > n ? off_ : limit - 546 diff_, diff_); 547 548 col->StoreBytes(off_, buf_); 549 } else 550 { // do it the hard way for custom/mapped views (2002-03-13) 551 c4_Bytes orig; 552 GetData(orig); 553 554 c4_Bytes result; 555 t4_byte *ptr = result.SetBuffer(orig.Size() + diff_); 556 557 memcpy(ptr, orig.Contents(), off_); 558 memcpy(ptr + off_, buf_.Contents(), n); 559 memcpy(ptr + off_ + n, orig.Contents() + off_, orig.Size() - off_); 560 561 SetData(result); 562 } 563 return true; 564 } 565 566 return false; 567} 568 569///////////////////////////////////////////////////////////////////////////// 570 571c4_StringRef::operator const char *()const { 572 c4_Bytes result; 573 GetData(result); 574 575 return result.Size() > 0 ? (const char*)result.Contents(): ""; 576} 577 578c4_StringRef &c4_StringRef::operator = (const char *value_) { 579 SetData(c4_Bytes(value_, strlen(value_) + 1)); 580 return *this; 581} 582 583///////////////////////////////////////////////////////////////////////////// 584 585c4_ViewRef::operator c4_View()const { 586 c4_Bytes result; 587 if (!GetData(result)) 588 return (c4_Sequence*)0; 589 // resolve ambiguity 590 591 d4_assert(result.Size() == sizeof(c4_Sequence*)); 592 return *(c4_Sequence *const*)result.Contents(); 593} 594 595c4_ViewRef &c4_ViewRef::operator = (const c4_View &value_) { 596 SetData(c4_Bytes(&value_._seq, sizeof value_._seq)); 597 return *this; 598} 599 600///////////////////////////////////////////////////////////////////////////// 601 602c4_Stream::~c4_Stream(){} 603 604///////////////////////////////////////////////////////////////////////////// 605 606c4_Strategy::c4_Strategy(): _bytesFlipped(false), _failure(0), _mapStart(0), 607 _dataSize(0), _baseOffset(0), _rootPos( - 1), _rootLen( - 1){} 608 609c4_Strategy::~c4_Strategy() { 610 d4_assert(_mapStart == 0); 611} 612 613/// Read a number of bytes 614int c4_Strategy::DataRead(t4_i32, void *, int) { 615 /* 616 if (_mapStart != 0 && pos_ + length_ <= _dataSize) 617 { 618 memcpy(buffer_, _mapStart + pos_, length_); 619 return length_; 620 } 621 */ 622 ++_failure; 623 return - 1; 624} 625 626/// Write a number of bytes, return true if successful 627void c4_Strategy::DataWrite(t4_i32, const void *, int) { 628 ++_failure; 629} 630 631/// Flush and truncate file 632void c4_Strategy::DataCommit(t4_i32){} 633 634/// Override to support memory-mapped files 635void c4_Strategy::ResetFileMapping(){} 636 637/// Report total size of the datafile 638t4_i32 c4_Strategy::FileSize() { 639 return _dataSize; 640} 641 642/// Return a value to use as fresh generation counter 643t4_i32 c4_Strategy::FreshGeneration() { 644 return 1; 645} 646 647/// Define the base offset where data is stored 648void c4_Strategy::SetBase(t4_i32 base_) { 649 t4_i32 off = base_ - _baseOffset; 650 _baseOffset = base_; 651 _dataSize -= off; 652 if (_mapStart != 0) 653 _mapStart += off; 654} 655 656/* 657end_ is file position to start from (0 defaults to FileSize()) 658 659result is the logical end of the datafile (or -1 if no data) 660 661This code uses a tiny state machine so all the code to read and decode 662file marks is in one place within the loop. 663 */ 664 665/// Scan datafile head/tail markers, return logical end of data 666t4_i32 c4_Strategy::EndOfData(t4_i32 end_) { 667 enum { 668 kStateAtEnd, kStateCommit, kStateHead, kStateOld, kStateDone 669 }; 670 671 t4_i32 pos = (end_ >= 0 ? end_ : FileSize()) - _baseOffset; 672 t4_i32 last = pos; 673 t4_i32 rootPos = 0; 674 t4_i32 rootLen = - 1; // impossible value, flags old-style header 675 t4_byte mark[8]; 676 677 for (int state = kStateAtEnd; state != kStateDone;) { 678 pos -= 8; 679 if (pos + _baseOffset < 0 && state != kStateOld) { 680 // bad offset, try old-style header from start of file 681 pos = - _baseOffset; 682 state = kStateOld; 683 } 684 685 if (DataRead(pos, &mark, sizeof mark) != sizeof mark) 686 return - 1; 687 688 t4_i32 count = 0; 689 for (int i = 1; i < 4; ++i) 690 count = (count << 8) + mark[i]; 691 692 t4_i32 offset = 0; 693 for (int j = 4; j < 8; ++j) 694 offset = (offset << 8) + mark[j]; 695 696 const bool isSkipTail = ((mark[0] & 0xF0) == 0x90 /* 2006-11-11 */ || 697 mark[0] == 0x80 && count == 0) && offset > 0; 698 const bool isCommitTail = mark[0] == 0x80 && count > 0 && offset > 0; 699 const bool isHeader = (mark[0] == 'J' || mark[0] == 'L') && (mark[0] ^ 700 mark[1]) == ('J' ^ 'L') && mark[2] == 0x1A && (mark[3] & 0x40) == 0; 701 702 switch (state) { 703 case kStateAtEnd: 704 // no commit tail found yet 705 706 if (isSkipTail) { 707 pos -= offset; 708 last = pos; 709 } 710 else if (isCommitTail) { 711 rootPos = offset; 712 rootLen = count; 713 state = kStateCommit; 714 } 715 else { 716 pos = 8; 717 state = kStateOld; 718 } 719 break; 720 721 case kStateCommit: 722 // commit tail must be preceded by skip tail 723 724 if (!isSkipTail) 725 return - 1; 726 pos -= offset - 8; 727 state = kStateHead; 728 break; 729 730 case kStateHead: 731 // fetch the header 732 733 if (!isHeader) { 734 pos = 8; 735 state = kStateOld; 736 } 737 else { 738 state = kStateDone; 739 } 740 break; 741 742 case kStateOld: 743 // old format, look for header in first 4 Kb 744 745 if (isHeader && mark[3] == 0x80) { 746 d4_assert(rootPos == 0); 747 for (int k = 8; --k >= 4;) 748 // old header is little-endian 749 rootPos = (rootPos << 8) + mark[k]; 750 state = kStateDone; 751 } 752 else { 753 pos += 16; 754 if (pos > 4096) 755 return - 1; 756 } 757 break; 758 } 759 } 760 761 last += _baseOffset; // all seeks were relative to current offset 762 763 if (end_ >= 0) 764 { // if end was specified, then adjust this strategy object 765 _baseOffset += pos; 766 d4_assert(_baseOffset >= 0); 767 if (_mapStart != 0) { 768 _mapStart += pos; 769 _dataSize -= pos; 770 } 771 772 _rootPos = rootPos; 773 _rootLen = rootLen; 774 } 775 776 d4_assert(mark[0] == 'J' || mark[1] == 'J'); 777 _bytesFlipped = (char)*(const short*)mark != 'J'; 778 779 return last; 780} 781 782///////////////////////////////////////////////////////////////////////////// 783