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