diff options
Diffstat (limited to 'akregator/src/mk4storage/metakit/src')
40 files changed, 13577 insertions, 0 deletions
diff --git a/akregator/src/mk4storage/metakit/src/Makefile.am b/akregator/src/mk4storage/metakit/src/Makefile.am new file mode 100644 index 00000000..e428fe93 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/Makefile.am @@ -0,0 +1,10 @@ +INCLUDES = -I$(srcdir)/../include + +noinst_LTLIBRARIES = libmetakitlocal.la + +noinst_HEADERS = borc.h column.h column.inl custom.h derived.h field.h field.inl format.h gnuc.h handler.h handler.inl header.h mfc.h \ + msvc.h mwcw.h persist.h remap.h std.h store.h store.inl univ.h univ.inl win.h + +libmetakitlocal_la_SOURCES = column.cpp custom.cpp derived.cpp field.cpp fileio.cpp format.cpp handler.cpp persist.cpp remap.cpp std.cpp store.cpp \ + string.cpp table.cpp univ.cpp view.cpp viewx.cpp + diff --git a/akregator/src/mk4storage/metakit/src/borc.h b/akregator/src/mk4storage/metakit/src/borc.h new file mode 100644 index 00000000..2b881fc9 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/borc.h @@ -0,0 +1,33 @@ +// borc.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Configuration header for Borland C++ + */ + +#define q4_BORC 1 + + // get rid of several common warning messages +#if !q4_STRICT +#pragma warn -aus // 'identifier' is assigned a value that is never used +#pragma warn -par // Parameter 'parameter' is never used. +#pragma warn -sig // Conversion may lose significant digits +#pragma warn -use // 'identifier' declared but never used +#endif + +#if __BORLANDC__ >= 0x500 +#define q4_BOOL 1 // supports the bool datatype + // undo previous defaults, because q4_BOOL is not set early enough +#undef false +#undef true +#undef bool +#endif + +#if !defined (q4_EXPORT) +#define q4_EXPORT 1 // requires export/import specifiers +#endif + +#if defined (__MT__) +#define q4_MULTI 1 // uses multi-threading +#endif diff --git a/akregator/src/mk4storage/metakit/src/column.cpp b/akregator/src/mk4storage/metakit/src/column.cpp new file mode 100644 index 00000000..2d191c64 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/column.cpp @@ -0,0 +1,1533 @@ +// column.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Implements c4_Column, c4_ColOfInts, and c4_ColIter + */ + +#include "header.h" +#include "column.h" +#include "persist.h" + +#if !q4_INLINE +#include "column.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#if !HAVE_MEMMOVE && !HAVE_BCOPY + // in case we have no library memmove, or one that can't handle overlap + + void f4_memmove (void* to_, const void* from_, int n_) + { + char* to = (char*) to_; + const char* from = (const char*) from_; + + if (to + n_ <= from || from + n_ <= to) + memcpy(to, from, n_); + else if (to < from) + while (--n_ >= 0) + *to++ = *from++; + else if (to > from) + while (--n_ >= 0) + to[n_] = from[n_]; + } +#endif + +///////////////////////////////////////////////////////////////////////////// +// c4_Column + +c4_Column::c4_Column (c4_Persist* persist_) + : _position (0), _size (0), _persist (persist_), _gap (0), + _slack (0), _dirty (false) +{ +} + +#if q4_CHECK + + // debugging version to verify that the internal data is consistent + void c4_Column::Validate() const + { + d4_assert(0 <= _slack && _slack < kSegMax); + + if (_segments.GetSize() == 0) + return; // ok, not initialized + + d4_assert(_gap <= _size); + + int n = fSegIndex(_size + _slack); + d4_assert(n == _segments.GetSize() - 1); + + t4_byte* p = (t4_byte*) _segments.GetAt(n); + + if (fSegRest(_size + _slack) == 0) + d4_assert(p == 0); + else + d4_assert(p != 0); + + while (--n >= 0) { + t4_byte* p = (t4_byte*) _segments.GetAt(n); + d4_assert(p != 0); + } + } + +#else + + // nothing, so inline this thing to avoid even the calling overhead + d4_inline void c4_Column::Validate() const + { + } + +#endif + +c4_Column::~c4_Column () +{ + Validate(); + ReleaseAllSegments(); + + // this is needed to remove this column from the cache + d4_assert(_slack == 0); + FinishSlack(); + + _slack = -1; // bad value in case we try to set up again (!) +} + +c4_Strategy& c4_Column::Strategy() const +{ + d4_assert(_persist != 0); + + return _persist->Strategy(); +} + +bool c4_Column::IsMapped() const +{ + return _position > 1 && _persist != 0 && Strategy()._mapStart != 0; +} + +bool c4_Column::UsesMap(const t4_byte* ptr_) const +{ + // the most common falsifying case is checked first + return _persist != 0 && ptr_ >= Strategy()._mapStart && + Strategy()._dataSize != 0 && // added 2003-05-08, thx V DeMarco + ptr_ - Strategy()._mapStart < Strategy()._dataSize; +} + +bool c4_Column::RequiresMap() const +{ + if (_persist != 0 && Strategy()._mapStart != 0) + for (int i = _segments.GetSize(); --i >= 0; ) + if (UsesMap((t4_byte*) _segments.GetAt(i))) + return true; + return false; +} + +void c4_Column::ReleaseSegment(int index_) +{ + t4_byte* p = (t4_byte*) _segments.GetAt(index_); + if (!UsesMap(p)) + delete [] p; +} + +void c4_Column::ReleaseAllSegments() +{ + //for (int i = 0; i < _segments.GetSize(); ++i) + for (int i = _segments.GetSize(); --i >= 0; ) + ReleaseSegment(i); // last one might be a null pointer + + _segments.SetSize(0); + + _gap = 0; + _slack = 0; + + if (_size == 0) + _position = 0; + + _dirty = false; +} + + //@func Define where data is on file, or setup buffers (opt cleared). +void c4_Column::SetLocation(t4_i32 pos_, t4_i32 size_) +{ + d4_assert(size_ > 0 || pos_ == 0); + + ReleaseAllSegments(); + + _position = pos_; + _size = size_; + + // There are two position settings: + // + // 0 = raw buffer, no file access + // >1 = file position from where data can be loaded on demand + + _dirty = pos_ == 0; +} + +void c4_Column::PullLocation(const t4_byte*& ptr_) +{ + d4_assert(_segments.GetSize() == 0); + + _size = PullValue(ptr_); + _position = 0; + if (_size > 0) { + _position = PullValue(ptr_); + if (_position > 0) { + d4_assert(_persist != 0); + _persist->OccupySpace(_position, _size); + } + } + + _dirty = false; +} + + //@func How many contiguous bytes are there at a specified position. +int c4_Column::AvailAt(t4_i32 offset_) const +{ + d4_assert(offset_ <= _size); + d4_assert(_gap <= _size); + + t4_i32 limit = _gap; + + if (offset_ >= _gap) { + offset_ += _slack; + limit = _size + _slack; + } + + int count = kSegMax - fSegRest(offset_); + if (offset_ + count > limit) + count = (int) (limit - offset_); + + // either some real data or it must be at the very end of all data + d4_assert(0 < count && count <= kSegMax || + count == 0 && offset_ == _size + _slack); + return count; +} + +void c4_Column::SetupSegments() +{ + d4_assert(_segments.GetSize() == 0); + d4_assert(_gap == 0); + d4_assert(_slack == 0); + + // The last entry in the _segments array is either a partial block + // or a null pointer, so calling "fSegIndex(_size)" is always allowed. + + int n = fSegIndex(_size) + 1; + _segments.SetSize(n); + + // treat last block differently if it is a partial entry + int last = n; + if (fSegRest(_size)) + --last; // this block is partial, size is 1 .. kSegMax-1 + else + --n; // the last block is left as a null pointer + + int id = -1; + if (_position < 0) { // special aside id, figure out the real position + d4_assert(_persist != 0); + id = ~_position; + _position = _persist->LookupAside(id); + d4_assert(_position >= 0); + } + + if (IsMapped()) { + // setup for mapped files is quick, just fill in the pointers + d4_assert(_position > 1); + d4_assert(_position + (n-1) * kSegMax <= Strategy()._dataSize); + const t4_byte* map = Strategy()._mapStart + _position; + + for (int i = 0; i < n; ++i) { + _segments.SetAt(i, (t4_byte*) map); // loses const + map += kSegMax; + } + } else { + int chunk = kSegMax; + t4_i32 pos = _position; + + // allocate buffers, load them if necessary + for (int i = 0; i < n; ++i) { + if (i == last) + chunk = fSegRest(_size); + + t4_byte* p = d4_new t4_byte [chunk]; + _segments.SetAt(i, p); + + if (_position > 0) { + d4_dbgdef(int n =) + Strategy().DataRead(pos, p, chunk); + d4_assert(n == chunk); + pos += chunk; + } + } + } + + if (id >= 0) { + d4_assert(_persist != 0); + _persist->ApplyAside(id, *this); + } + + Validate(); +} + + //@func Makes sure the requested data is in a modifiable buffer. +t4_byte* c4_Column::CopyNow(t4_i32 offset_) +{ + d4_assert(offset_ <= _size); + + _dirty = true; + + const t4_byte* ptr = LoadNow(offset_); + if (UsesMap(ptr)) { + if (offset_ >= _gap) + offset_ += _slack; + + // this will only force creation of a buffer + ptr = CopyData(offset_, offset_, 0); + d4_assert(!UsesMap(ptr)); + } + + return (t4_byte*) ptr; +} + + //@func Copies data, creating a buffer if needed. Must be in single segment. +t4_byte* c4_Column::CopyData(t4_i32 to_, t4_i32 from_, int count_) +{ + int i = fSegIndex(to_); + t4_byte* p = (t4_byte*) _segments.GetAt(i); + + if (UsesMap(p)) { + int n = kSegMax; + if (fSegOffset(i) + n > _size + _slack) + n = (int) (_size + _slack - fSegOffset(i)); + + d4_assert(n > 0); + + t4_byte* q = d4_new t4_byte [n]; + memcpy(q, p, n); // some copying can be avoided, overwritten below... + _segments.SetAt(i, q); + + p = q; + } + + p += fSegRest(to_); + + if (count_ > 0) { + d4_assert(fSegIndex(to_ + count_ - 1) == i); + + const t4_byte* src = (const t4_byte*) _segments.GetAt(fSegIndex(from_)); + d4_memmove(p, src + fSegRest(from_), count_); + } + + return p; +} + + /* + * Resizing a segmented vector can be a complicated operation. + * For now, simply making it work in all cases is the first priority. + * + * A major simplification - and good performance improvement - is caused + * by the trick of maintaining a "gap" in the data, which can be "moved" + * around to allow fast insertion as well as simple (delayed) deletion. + * + * The only complexity comes from the fact that the gap must end up being + * less than one full segment in size. Therefore, insertion and removal + * across segment boundaries needs to handle a variety of situations. + * + * Since complete segments can be inserted quickly, this approach avoids + * lots of copying when consecutive insertions/deletions are clustered. + * Even random changes move half as much (on average) as without a gap. + * + * The price is the overhead of up to one segment of empty space, and the + * complexity of this code (all the magic is within this c4_Column class). + */ + +void c4_Column::MoveGapUp(t4_i32 dest_) +{ + d4_assert(dest_ <= _size); + d4_assert(_gap < dest_); + d4_assert(_slack > 0); + + // forward loop to copy contents down, in little pieces if need be + while (_gap < dest_) { + int n = kSegMax - fSegRest(_gap); + t4_i32 curr = _gap + n; + if (curr > dest_) + curr = dest_; + + // copy to [_gap..curr), which is inside one segment + d4_assert(_gap < curr); + d4_assert(fSegIndex(_gap) == fSegIndex(curr - 1)); + + // copy from [_gap + _slack .. curr + _slack), of the same size + t4_i32 fromBeg = _gap + _slack; + t4_i32 fromEnd = curr + _slack; + + while (fromBeg < fromEnd) { + int k = kSegMax - fSegRest(fromBeg); + if (fromBeg + k > fromEnd) + k = (int) (fromEnd - fromBeg); + + d4_assert(k > 0); + + CopyData(_gap, fromBeg, k); + + _gap += k; + fromBeg += k; + } + + _gap = curr; + } + + d4_assert(_gap == dest_); +} + +void c4_Column::MoveGapDown(t4_i32 dest_) +{ + d4_assert(dest_ <= _size); + d4_assert(_gap > dest_); + d4_assert(_slack > 0); + + // reverse loop to copy contents up, in little pieces if need be + t4_i32 toEnd = _gap + _slack; + t4_i32 toBeg = dest_ + _slack; + + while (toEnd > toBeg) { + int n = fSegRest(toEnd); + t4_i32 curr = toEnd - (n ? n : kSegMax); + if (curr < toBeg) + curr = toBeg; + + // copy to [curr..toEnd), which is inside one segment + d4_assert(curr < toEnd); + d4_assert(fSegIndex(curr) == fSegIndex(toEnd - 1)); + + // copy from [fromBeg .. _gap), which has the same size + t4_i32 fromBeg = _gap - (toEnd - curr); + + while (_gap > fromBeg) { + int k = fSegRest(_gap); + if (k == 0) + k = kSegMax; + if (_gap - k < fromBeg) + k = (int) (_gap - fromBeg); + + d4_assert(k > 0); + + toEnd -= k; + _gap -= k; + + CopyData(toEnd, _gap, k); + } + } + + d4_assert(_gap == dest_); +} + +void c4_Column::MoveGapTo(t4_i32 pos_) +{ + d4_assert(pos_ <= _size); + + if (_slack == 0) // if there is no real gap, then just move it + _gap = pos_; + else if (_gap < pos_) // move the gap up, ie. some bytes down + MoveGapUp(pos_); + else if (_gap > pos_) // move the gap down, ie. some bytes up + if (_gap - pos_ > _size - _gap + fSegRest(pos_)) { + RemoveGap(); // it's faster to get rid of the gap instead + _gap = pos_; + } + else // normal case, move some bytes up + MoveGapDown(pos_); + + d4_assert(_gap == pos_); + + Validate(); +} + +void c4_Column::RemoveGap() +{ + if (_slack > 0) { + if (_gap < _size) + MoveGapUp(_size); + + d4_assert(_gap == _size); // the gap is now at the end + d4_assert(_slack < kSegMax); + + // Case 1: gap is at start of segment + // ================================== + // + // G G+S + // + // | | + // :----+xx: + // | | + // + // i i+1 (limit) + // + // Case 2: gap is inside segment + // ============================= + // + // G G+S + // + // | | + // :--+--+x: + // | | + // + // i i+1 (limit) + // + // Case 3: gap runs to end of segment + // ================================== + // + // G G+S + // + // | | + // :--+----:0000000: + // | | | + // + // i i+1 i+2 (limit) + // + // Case 4: gap is across two segments + // ================================== + // + // G G+S + // + // | | + // :--+----:-+xxxxx: + // | | | + // + // i i+1 i+2 (limit) + + int i = fSegIndex(_gap); + int n = fSegRest(_gap); + + if (n == 0) { // case 1 + ReleaseSegment(i); + _segments.SetAt(i, 0); + } else { + if (n + _slack > kSegMax) // case 4 + ReleaseSegment(i+1); + + // truncate rest of segment + t4_byte* p = d4_new t4_byte [n]; + memcpy(p, _segments.GetAt(i), n); + + ReleaseSegment(i); + _segments.SetAt(i, p); + _segments.SetSize(i + 1); + } + + _slack = 0; + } + + Validate(); +} + +void c4_Column::Grow(t4_i32 off_, t4_i32 diff_) +{ + d4_assert(off_ <= _size); + d4_assert(diff_ > 0); + + if (_segments.GetSize() == 0) + SetupSegments(); + + Validate(); + + _dirty = true; + + // move the gap so it starts where we want to insert + MoveGapTo(off_); + + t4_i32 bigSlack = _slack; + if (bigSlack < diff_) { // only do more if this isn't good enough + // number of segments to insert + int n = fSegIndex(diff_ - _slack + kSegMax - 1); + d4_assert(n > 0); + + int i1 = fSegIndex(_gap); + int i2 = fSegIndex(_gap + _slack); + + bool moveBack = false; + + if (i2 > i1) // cases 3 and 4 + ++i1; + else if (fSegRest(_gap)) // case 2 + moveBack = true; + + _segments.InsertAt(i1, 0, n); + for (int i = 0; i < n; ++i) + _segments.SetAt(i1 + i, d4_new t4_byte [(int) kSegMax]); + + bigSlack += fSegOffset(n); + + if (moveBack) { + d4_assert(i1 == fSegIndex(_gap)); + + // we have inserted too low, move bytes in front of gap back + CopyData(fSegOffset(i1), fSegOffset(i1 + n), fSegRest(_gap)); + } + } + + d4_assert(diff_ <= bigSlack && bigSlack < diff_ + kSegMax); + + _gap += diff_; + _slack = (int) (bigSlack - diff_); + _size += diff_; + + FinishSlack(); +} + +void c4_Column::Shrink(t4_i32 off_, t4_i32 diff_) +{ + d4_assert(off_ <= _size); + d4_assert(diff_ > 0); + + if (_segments.GetSize() == 0) + SetupSegments(); + + Validate(); + + _dirty = true; + + // the simplification here is that we have in fact simply *two* + // gaps and we must merge them together and end up with just one + + if (_slack > 0) { + if (_gap < off_) // if too low, move the gap up + MoveGapTo(off_); + else if (off_ + diff_ < _gap) // if too high, move down to end + MoveGapTo(off_ + diff_); + + // the gap is now inside, or adjacent to, the deleted area + d4_assert(off_ <= _gap && _gap <= off_ + diff_); + } + + _gap = off_; + + // check whether the merged gap would cross a segment boundary + int i1 = fSegIndex(_gap); + int i2 = fSegIndex(_gap + _slack + diff_); + + // drop complete segments, not a partially filled boundary + if (fSegRest(_gap)) + ++i1; + + // moved up (was after the next if in the 1.7 May 28 build) + _slack += diff_; + _size -= diff_; + + int n = i2 - i1; + if (n > 0) { + for (int i = i1; i < i2; ++i) + ReleaseSegment(i); + + _segments.RemoveAt(i1, n); + + // the logic in 1.7 of May 28 was warped (the assert "fix" was wrong) + d4_assert(_slack >= fSegOffset(n)); + _slack -= fSegOffset(n); + } + + d4_assert(0 <= _slack && _slack < 2 * kSegMax); + + // if the gap is at the end, get rid of a partial segment after it + if (_gap == _size) { + int i = fSegIndex(_size + _slack); + if (i != fSegIndex(_gap)) { + d4_assert(i == fSegIndex(_gap) + 1); + d4_assert(i == _segments.GetSize() - 1); + + ReleaseSegment(i); + _segments.SetAt(i, 0); + + _slack -= fSegRest(_size + _slack); + + d4_assert(_slack < kSegMax); + d4_assert(fSegRest(_gap + _slack) == 0); + } + } + + // the slack may still be too large to leave as is + if (_slack >= kSegMax) { + // move the bytes just after the end of the gap one segment down + int x = fSegRest(_gap + _slack); + int r = kSegMax - x; + if (_gap + r > _size) + r = (int) (_size - _gap); + + CopyData(_gap, _gap + _slack, r); + + int i = fSegIndex(_gap + kSegMax - 1); + ReleaseSegment(i); + + if (r + x < kSegMax) + _segments.SetAt(i, 0); + else + _segments.RemoveAt(i); + + _slack -= r + x; + _gap += r; + } + + // if we have no data anymore, make sure not to use the file map either + if (_size == 0 && _slack > 0) + CopyNow(0); + + FinishSlack(); +} + +void c4_Column::FinishSlack() +{ + Validate(); + + // optimization: if partial end segment easily fits in slack, move it down + t4_i32 gapEnd = _gap + _slack; + if (!fSegRest(gapEnd) && gapEnd >= _size + 500) { + // slack is at least 500 bytes more than the partial end segment + // also, the gap must end exactly on a segment boundary + int i = fSegIndex(gapEnd); + d4_assert(i == _segments.GetSize() - 1); + + int n = _size - _gap; + CopyData(gapEnd - n, gapEnd, n); + + ReleaseSegment(i); + _segments.SetAt(i, 0); + + _slack -= n; + d4_assert(_slack >= 500); + + Validate(); + } +} + +void c4_Column::SaveNow(c4_Strategy& strategy_, t4_i32 pos_) +{ + if (_segments.GetSize() == 0) + SetupSegments(); + + // write all segments + c4_ColIter iter (*this, 0, _size); + while (iter.Next(kSegMax)) { + int n = iter.BufLen(); + strategy_.DataWrite(pos_, iter.BufLoad(), n); + if (strategy_._failure != 0) + break; + pos_ += n; + } +} + +const t4_byte* c4_Column::FetchBytes(t4_i32 pos_, int len_, c4_Bytes& buffer_, bool forceCopy_) +{ + d4_assert(len_ > 0); + d4_assert(pos_ + len_ <= ColSize()); + d4_assert(0 <= _slack && _slack < kSegMax); + + c4_ColIter iter (*this, pos_, pos_ + len_); + iter.Next(); + + // most common case, all bytes are inside the same segment + if (!forceCopy_ && iter.BufLen() == len_) + return iter.BufLoad(); + + t4_byte* p = buffer_.SetBuffer(len_); + do { + d4_assert(iter.BufLen() > 0); + memcpy(p, iter.BufLoad(), iter.BufLen()); + p += iter.BufLen(); + } while (iter.Next()); + d4_assert(p == buffer_.Contents() + len_); + + return buffer_.Contents(); +} + +void c4_Column::StoreBytes(t4_i32 pos_, const c4_Bytes& buffer_) +{ + int n = buffer_.Size(); + if (n > 0) { + d4_assert(pos_ + n <= ColSize()); + + c4_ColIter iter (*this, pos_, pos_ + n); + + const t4_byte* p = buffer_.Contents(); + while (iter.Next(n)) { + d4_assert(iter.BufLen() > 0); + memcpy(iter.BufSave(), p, iter.BufLen()); + p += iter.BufLen(); + } + d4_assert(p == buffer_.Contents() + n); + } +} + + /* + PushValue and PullValue deal with variable-sized storage of + one unsigned integer value of up to 32 bits. Depending on the + magnitude of the integer, 1..6 bytes are used to represent it. + Each byte holds 7 significant bits and one continuation bit. + This saves storage, but it is also byte order independent. + Negative values are stored as a zero byte plus positive value. + */ + +t4_i32 c4_Column::PullValue(const t4_byte*& ptr_) +{ + t4_i32 mask = *ptr_ ? 0 : ~0; + + t4_i32 v = 0; + for (;;) { + v = (v << 7) + *ptr_; + if (*ptr_++ & 0x80) + break; + } + + return mask ^ (v - 0x80); // oops, last byte had bit 7 set +} + +void c4_Column::PushValue(t4_byte*& ptr_, t4_i32 v_) +{ + if (v_ < 0) { + v_ = ~v_; + *ptr_++ = 0; + } + + int n = 0; + do + n += 7; + while ((v_ >> n) && n < 32); + + while (n) { + n -= 7; + t4_byte b = (t4_byte) ((v_ >> n) & 0x7F); + if (!n) + b |= 0x80; // set bit 7 on the last byte + *ptr_++ = b; + } +} + +void c4_Column::InsertData(t4_i32 index_, t4_i32 count_, bool clear_) +{ + d4_assert(index_ <= ColSize()); + + if (count_ > 0) { + Grow(index_, count_); + + // clear the contents, in separate chunks if necessary + if (clear_) { + c4_ColIter iter (*this, index_, index_ + count_); + while (iter.Next()) + memset(iter.BufSave(), 0, iter.BufLen()); + } + } +} + +void c4_Column::RemoveData(t4_i32 index_, t4_i32 count_) +{ + d4_assert(index_ + count_ <= ColSize()); + + if (count_ > 0) + Shrink(index_, count_); +} + +///////////////////////////////////////////////////////////////////////////// + +void c4_ColOfInts::Get_0b(int) +{ + *(t4_i32*) _item = 0; +} + +void c4_ColOfInts::Get_1b(int index_) +{ + t4_i32 off = index_ >> 3; + d4_assert(off < ColSize()); + + *(t4_i32*) _item = (*LoadNow(off) >> (index_ & 7)) & 0x01; +} + +void c4_ColOfInts::Get_2b(int index_) +{ + t4_i32 off = index_ >> 2; + d4_assert(off < ColSize()); + + *(t4_i32*) _item = (*LoadNow(off) >> ((index_ & 3) << 1)) & 0x03; +} + +void c4_ColOfInts::Get_4b(int index_) +{ + t4_i32 off = index_ >> 1; + d4_assert(off < ColSize()); + + *(t4_i32*) _item = (*LoadNow(off) >> ((index_ & 1) << 2)) & 0x0F; +} + +void c4_ColOfInts::Get_8i(int index_) +{ + t4_i32 off = index_; + d4_assert(off < ColSize()); + + *(t4_i32*) _item = *(const signed char*) LoadNow(off); +} + +void c4_ColOfInts::Get_16i(int index_) +{ + t4_i32 off = index_ * (t4_i32) 2; + d4_assert(off + 2 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + _item[0] = vec[0]; + _item[1] = vec[1]; + + *(t4_i32*) _item = *(const short*) _item; +} + +void c4_ColOfInts::Get_16r(int index_) +{ + t4_i32 off = index_ * (t4_i32) 2; + d4_assert(off + 2 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + // 2003-02-02 - gcc 3.2.1 on linux (!) fails to compile this + // sign-extension trick properly, use a temp buffer instead: + //*(t4_i32*) _item = *(const short*) _item; + + t4_byte temp[2]; + temp[1] = vec[0]; + temp[0] = vec[1]; + *(t4_i32*) _item = *(const short*) temp; +} + +void c4_ColOfInts::Get_32i(int index_) +{ + t4_i32 off = index_ * (t4_i32) 4; + d4_assert(off + 4 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + _item[0] = vec[0]; + _item[1] = vec[1]; + _item[2] = vec[2]; + _item[3] = vec[3]; +} + +void c4_ColOfInts::Get_32r(int index_) +{ + t4_i32 off = index_ * (t4_i32) 4; + d4_assert(off + 4 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + _item[3] = vec[0]; + _item[2] = vec[1]; + _item[1] = vec[2]; + _item[0] = vec[3]; +} + +void c4_ColOfInts::Get_64i(int index_) +{ + t4_i32 off = index_ * (t4_i32) 8; + d4_assert(off + 8 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + for (int i = 0; i < 8; ++i) + _item[i] = vec[i]; +} + +void c4_ColOfInts::Get_64r(int index_) +{ + t4_i32 off = index_ * (t4_i32) 8; + d4_assert(off + 8 <= ColSize()); + + const t4_byte* vec = LoadNow(off); + + for (int i = 0; i < 8; ++i) + _item[7-i] = vec[i]; +} + +///////////////////////////////////////////////////////////////////////////// + + static int fBitsNeeded(t4_i32 v) + { + if ((v >> 4) == 0) { + static int bits[] = { 0, 1, 2, 2, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4 }; + return bits[(int) v]; + } + + if (v < 0) // first flip all bits if bit 31 is set + v = ~ v; // ... bit 31 is now always zero + + // then check if bits 15-31 used (32b), 7-31 used (16b), else (8b) + return v >> 15 ? 32 : v >> 7 ? 16 : 8; + } + +bool c4_ColOfInts::Set_0b(int, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + return v == 0; +} + +bool c4_ColOfInts::Set_1b(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_ >> 3; + d4_assert(off < ColSize()); + + index_ &= 7; + + t4_byte* p = CopyNow(off); + *p = (*p & ~(1 << index_)) | (((t4_byte) v & 1) << index_); + + return (v >> 1) == 0; +} + +bool c4_ColOfInts::Set_2b(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_ >> 2; + d4_assert(off < ColSize()); + + const int n = (index_ & 3) << 1; + + t4_byte* p = CopyNow(off); + *p = (*p & ~(0x03 << n)) | (((t4_byte) v & 0x03) << n); + + return (v >> 2) == 0; +} + +bool c4_ColOfInts::Set_4b(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_ >> 1; + d4_assert(off < ColSize()); + + const int n = (index_ & 1) << 2; + + t4_byte* p = CopyNow(off); + *p = (*p & ~(0x0F << n)) | (((t4_byte) v & 0x0F) << n); + + return (v >> 4) == 0; +} + +// avoid a bug in MS EVC 3.0's code gen for ARM (i.e. WinCE) +#ifdef _ARM_ +#pragma optimize("g",off) +#endif + +bool c4_ColOfInts::Set_8i(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_; + d4_assert(off < ColSize()); + + *(char*) CopyNow(off) = (char) v; + + return v == (signed char) v; +} + +#ifdef _ARM_ +#pragma optimize("",on) +#endif + +bool c4_ColOfInts::Set_16i(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_ * (t4_i32) 2; + d4_assert(off + 2 <= ColSize()); + + *(short*) CopyNow(off) = (short) v; + + return v == (short) v; +} + +bool c4_ColOfInts::Set_16r(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_byte buf [2]; + *(short*) buf = (short) v; + + t4_i32 off = index_ * (t4_i32) 2; + d4_assert(off + 2 <= ColSize()); + + t4_byte* vec = CopyNow(off); + + vec[1] = buf[0]; + vec[0] = buf[1]; + + return v == (short) v; +} + +bool c4_ColOfInts::Set_32i(int index_, const t4_byte* item_) +{ + t4_i32 v = *(const t4_i32*) item_; + + t4_i32 off = index_ * (t4_i32) 4; + d4_assert(off + 4 <= ColSize()); + + *(t4_i32*) CopyNow(off) = (t4_i32) v; + + return true; +} + +bool c4_ColOfInts::Set_32r(int index_, const t4_byte* item_) +{ + t4_i32 off = index_ * (t4_i32) 4; + d4_assert(off + 4 <= ColSize()); + + t4_byte* vec = CopyNow(off); + + vec[3] = item_[0]; + vec[2] = item_[1]; + vec[1] = item_[2]; + vec[0] = item_[3]; + + return true; +} + +bool c4_ColOfInts::Set_64i(int index_, const t4_byte* item_) +{ + t4_i32 off = index_ * (t4_i32) 8; + d4_assert(off + 8 <= ColSize()); + + t4_byte* vec = CopyNow(off); + + for (int i = 0; i < 8; ++i) + vec[i] = item_[i]; + + return true; +} + +bool c4_ColOfInts::Set_64r(int index_, const t4_byte* item_) +{ + t4_i32 off = index_ * (t4_i32) 8; + d4_assert(off + 8 <= ColSize()); + + t4_byte* vec = CopyNow(off); + + for (int i = 0; i < 8; ++i) + vec[7-i] = item_[i]; + + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_ColOfInts::c4_ColOfInts (c4_Persist* persist_, int width_) + : c4_Column (persist_), + _getter (&c4_ColOfInts::Get_0b), _setter (&c4_ColOfInts::Set_0b), + _currWidth (0), _dataWidth (width_), _numRows (0), _mustFlip (false) +{ +} + +void c4_ColOfInts::ForceFlip() +{ + _mustFlip = true; +} + +int c4_ColOfInts::RowCount() const +{ + d4_assert(_numRows >= 0); + + return _numRows; +} + +int c4_ColOfInts::CalcAccessWidth(int numRows_, t4_i32 colSize_) +{ + d4_assert(numRows_ > 0); + + int w = (int) ((colSize_ << 3) / numRows_); + + // deduce sub-byte sizes for small vectors, see c4_ColOfInts::Set + if (numRows_ <= 7 && 0 < colSize_ && colSize_ <= 6) { + static t4_byte realWidth [][6] = { + // sz = 1: 2: 3: 4: 5: 6: + { 8, 16, 1, 32, 2, 4 }, // n = 1 + { 4, 8, 1, 16, 2, 0 }, // n = 2 + { 2, 4, 8, 1, 0, 16 }, // n = 3 + { 2, 4, 0, 8, 1, 0 }, // n = 4 + { 1, 2, 4, 0, 8, 0 }, // n = 5 + { 1, 2, 4, 0, 0, 8 }, // n = 6 + { 1, 2, 0, 4, 0, 0 }, // n = 7 + }; + + w = realWidth [numRows_-1] [(int) colSize_ - 1]; + d4_assert(w > 0); + } + + return (w & (w - 1)) == 0 ? w : -1; +} + +void c4_ColOfInts::SetRowCount(int numRows_) +{ + _numRows = numRows_; + if (numRows_ > 0) { + int w = CalcAccessWidth(numRows_, ColSize()); + d4_assert(w >= 0); + SetAccessWidth(w); + } +} + +void c4_ColOfInts::FlipBytes() +{ + if (_currWidth > 8) { + int step = _currWidth >> 3; + + c4_ColIter iter (*this, 0, ColSize()); + while (iter.Next(step)) { + t4_byte* data = iter.BufSave(); + d4_assert(data != 0); + + for (int j = 0; j < step; ++j) { + t4_byte c = data[j]; + data[j] = data[step-j-1]; + data[step-j-1] = c; + } + } + } +} + +void c4_ColOfInts::SetAccessWidth(int bits_) +{ + d4_assert((bits_ & (bits_ - 1)) == 0); + + int l2bp1 = 0; // "log2 bits plus one" needed to represent value + while (bits_) { + ++l2bp1; + bits_ >>= 1; + } + d4_assert(0 <= l2bp1 && l2bp1 < 8); + + _currWidth = (1 << l2bp1) >> 1; + + if (l2bp1 > 4 && (_mustFlip || Persist() != 0 && Strategy()._bytesFlipped)) + l2bp1 += 3; // switch to the trailing entries for byte flipping + + // Metrowerks Codewarrior 11 is dumb, it requires the "&c4_ColOfInts::" + + static tGetter gTab [] = + { + &c4_ColOfInts::Get_0b, // 0: 0 bits/entry + &c4_ColOfInts::Get_1b, // 1: 1 bits/entry + &c4_ColOfInts::Get_2b, // 2: 2 bits/entry + &c4_ColOfInts::Get_4b, // 3: 4 bits/entry + + &c4_ColOfInts::Get_8i, // 4: 8 bits/entry + &c4_ColOfInts::Get_16i, // 5: 16 bits/entry + &c4_ColOfInts::Get_32i, // 6: 32 bits/entry + &c4_ColOfInts::Get_64i, // 7: 64 bits/entry + + &c4_ColOfInts::Get_16r, // 8: 16 bits/entry, reversed + &c4_ColOfInts::Get_32r, // 9: 32 bits/entry, reversed + &c4_ColOfInts::Get_64r, // 10: 64 bits/entry, reversed + }; + + static tSetter sTab [] = + { + &c4_ColOfInts::Set_0b, // 0: 0 bits/entry + &c4_ColOfInts::Set_1b, // 1: 1 bits/entry + &c4_ColOfInts::Set_2b, // 2: 2 bits/entry + &c4_ColOfInts::Set_4b, // 3: 4 bits/entry + + &c4_ColOfInts::Set_8i, // 4: 8 bits/entry + &c4_ColOfInts::Set_16i, // 5: 16 bits/entry + &c4_ColOfInts::Set_32i, // 6: 32 bits/entry + &c4_ColOfInts::Set_64i, // 7: 64 bits/entry + + &c4_ColOfInts::Set_16r, // 8: 16 bits/entry, reversed + &c4_ColOfInts::Set_32r, // 9: 32 bits/entry, reversed + &c4_ColOfInts::Set_64r, // 10: 64 bits/entry, reversed + }; + + d4_assert(l2bp1 < sizeof gTab / sizeof *gTab); + + _getter = gTab[l2bp1]; + _setter = sTab[l2bp1]; + + d4_assert(_getter != 0 && _setter != 0); +} + +int c4_ColOfInts::ItemSize(int) +{ + return _currWidth >= 8 ? _currWidth >> 3 : - _currWidth; +} + +const void* c4_ColOfInts::Get(int index_, int& length_) +{ + d4_assert(sizeof _item >= _dataWidth); + + (this->*_getter)(index_); + + length_ = _dataWidth; + return _item; +} + +void c4_ColOfInts::Set(int index_, const c4_Bytes& buf_) +{ + d4_assert(buf_.Size() == _dataWidth); + + if ((this->*_setter)(index_, buf_.Contents())) + return; + + d4_assert(buf_.Size() == sizeof (t4_i32)); + + int n = fBitsNeeded(*(const t4_i32*) buf_.Contents()); + if (n > _currWidth) { + int k = RowCount(); + + t4_i32 oldEnd = ColSize(); + t4_i32 newEnd = ((t4_i32) k * n + 7) >> 3; + + if (newEnd > oldEnd) { + InsertData(oldEnd, newEnd - oldEnd, _currWidth == 0); + + // 14-5-2002: need to get rid of gap in case it risks not being a + // multiple of the increased size (bug, see s46 regression test) + // + // Example scenario: gap size is odd, data gets resized to 2/4-byte + // ints, data at end fits without moving gap to end, then we end + // up with a vector that has an int split *across* the gap - this + // commits just fine, but access to that split int is now bad. + // + // Lesson: need stricter/simpler consistency, it's way too complex! + if (n > 8) + RemoveGap(); + } + + // data value exceeds width, expand to new size and repeat + if (_currWidth > 0) { + d4_assert(n % _currWidth == 0); // must be expanding by a multiple + + // To expand, we start by inserting a new appropriate chunk + // at the end, and expand the entries in place (last to first). + + tGetter oldGetter = _getter; + SetAccessWidth(n); + + d4_assert(sizeof _item >= _dataWidth); + + // this expansion in place works because it runs backwards + while (--k >= 0) { + (this->*oldGetter)(k); + (this->*_setter)(k, _item); + } + } else { + if (_dataWidth > (int) sizeof (t4_i32)) + n = _dataWidth << 3; // don't trust setter result, use max instead + + SetAccessWidth(n); + } + + // now repeat the failed call to _setter + /* bool f = */ (this->*_setter)(index_, buf_.Contents()); + //? d4_assert(f); + } +} + +t4_i32 c4_ColOfInts::GetInt(int index_) +{ + int n; + const void* p = Get(index_, n); + d4_assert(n == sizeof (t4_i32)); + return *(const t4_i32*) p; +} + +void c4_ColOfInts::SetInt(int index_, t4_i32 value_) +{ + Set(index_, c4_Bytes (&value_, sizeof value_)); +} + +int c4_ColOfInts::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + d4_assert(b1_.Size() == sizeof (t4_i32)); + d4_assert(b2_.Size() == sizeof (t4_i32)); + + t4_i32 v1 = *(const t4_i32*) b1_.Contents(); + t4_i32 v2 = *(const t4_i32*) b2_.Contents(); + + return v1 == v2 ? 0 : v1 < v2 ? -1 : +1; +} + +void c4_ColOfInts::Insert(int index_, const c4_Bytes& buf_, int count_) +{ + d4_assert(buf_.Size() == _dataWidth); + d4_assert(count_ > 0); + + bool clear = true; + const t4_byte* ptr = buf_.Contents(); + + for (int i = 0; i < _dataWidth; ++i) + if (*ptr++) { + clear = false; + break; + } + + ResizeData(index_, count_, clear); + + if (!clear) + while (--count_ >= 0) + Set(index_++, buf_); +} + +void c4_ColOfInts::Remove(int index_, int count_) +{ + d4_assert(count_ > 0); + + ResizeData(index_, - count_); +} + +void c4_ColOfInts::ResizeData(int index_, int count_, bool clear_) +{ + _numRows += count_; + + if (!(_currWidth & 7)) { // not 1, 2, or 4 + const t4_i32 w = (t4_i32) (_currWidth >> 3); + if (count_ > 0) + InsertData(index_ * w, count_ * w, clear_); + else + RemoveData(index_ * w, - count_ * w); + return; + } + + d4_assert(_currWidth == 1 || _currWidth == 2 || _currWidth == 4); + + /* _currwidth 1: 2: 4: + * shiftPos 3 2 1 shift the offset right this much + * maskPos 7 3 1 mask the offset with this + */ + + const int shiftPos = _currWidth == 4 ? 1 : 4 - _currWidth; + const int maskPos = (1 << shiftPos) - 1; + + // the following code is similar to c4_Column::Resize, but at bit level + + // turn insertion into deletion by inserting entire bytes + if (count_ > 0) { + unsigned off = (unsigned) index_ >> shiftPos; + int gapBytes = (count_ + maskPos) >> shiftPos; + + InsertData(off, gapBytes, clear_); + + // oops, we might have inserted too low by a few entries + const int bits = (index_ & maskPos) * _currWidth; + if (bits) { + const int maskLow = (1 << bits) - 1; + + // move the first few bits to start of inserted range + t4_byte* p = CopyNow(off + gapBytes); + t4_byte one = *p & maskLow; + *p &= ~maskLow; + + * CopyNow(off) = one; + } + + index_ += count_; + count_ -= gapBytes << shiftPos; + d4_assert(count_ <= 0); + } + + // now perform a deletion using a forward loop to copy down + if (count_ < 0) { + c4_Bytes temp; + + while (index_ < _numRows) { + int length; + const void* ptr = Get(index_ - count_, length); + Set(index_++, c4_Bytes (ptr, length)); + } + } + else { + d4_assert(count_ == 0); + } + + FixSize(false); +} + +void c4_ColOfInts::FixSize(bool fudge_) +{ + int n = RowCount(); + t4_i32 needBytes = ((t4_i32) n * _currWidth + 7) >> 3; + + // use a special trick to mark sizes less than 1 byte in storage + if (fudge_ && 1 <= n && n <= 4 && (_currWidth & 7)) { + const int shiftPos = _currWidth == 4 ? 1 : 4 - _currWidth; + + static t4_byte fakeSizes [3][4] = { // n: 1: 2: 3: 4: + { 6, 1, 2, 2 }, // 4-bit entries: 4b 8b 12b 16b + { 5, 5, 1, 1 }, // 2-bit entries: 2b 4b 6b 8b + { 3, 3, 4, 5 }, // 1-bit entries: 1b 2b 3b 4b + }; + + // The idea is to use an "impossible" size (ie. 5, for n = 2) + // to give information about the current bit packing density. + d4_assert(needBytes <= 2); + needBytes = fakeSizes [shiftPos-1] [n-1]; + } + + t4_i32 currSize = ColSize(); + + if (needBytes < currSize) + RemoveData(needBytes, currSize - needBytes); + else if (needBytes > currSize) + InsertData(currSize, needBytes - currSize, true); +} + +///////////////////////////////////////////////////////////////////////////// + +bool c4_ColIter::Next() +{ + _pos += _len; + + _len = _column.AvailAt(_pos); + _ptr = _column.LoadNow(_pos); + + if (!_ptr) + _len = 0; + else if (_pos + _len >= _limit) + _len = _limit - _pos; + else { // 19990831 - optimization to avoid most copying + // while the end is adjacent to the next segment, extend it + while (_ptr + _len == _column.LoadNow(_pos + _len)) { + int n = _column.AvailAt(_pos + _len); + if (n == 0) + break; // may be a short column (strings) + + _len += n; + + if (_pos + _len >= _limit) { + _len = _limit - _pos; + break; + } + } + } + + return _len > 0; +} + +bool c4_ColIter::Next(int max_) +{ + _pos += _len; + + _len = _column.AvailAt(_pos); + _ptr = _column.LoadNow(_pos); + + if (!_ptr) + _len = 0; + else if (_pos + _len > _limit) + _len = _limit - _pos; + + if (_len <= 0) + return false; + + if (_len > max_) + _len = max_; + + return true; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/column.h b/akregator/src/mk4storage/metakit/src/column.h new file mode 100644 index 00000000..3f6e4f15 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/column.h @@ -0,0 +1,212 @@ +// column.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Definition of the column classes + */ + +#ifndef __COLUMN_H__ +#define __COLUMN_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_Column; // a column in a table + class c4_ColIter; // an iterator over column data + class c4_ColCache; // manages a cache for columns + + class c4_Persist; // not defined here + class c4_Strategy; // not defined here + +///////////////////////////////////////////////////////////////////////////// + +class c4_Column +{ + c4_PtrArray _segments; + t4_i32 _position; + t4_i32 _size; + c4_Persist* _persist; + t4_i32 _gap; + int _slack; + bool _dirty; + +public: + c4_Column (c4_Persist* persist_); + //: Constructs a column using the specified persistence manager. + ~c4_Column (); + + void SetBuffer(t4_i32); + //: Allocate a new buffer of the specified size. + + c4_Persist* Persist() const; + //: Returns persistence manager for this column, or zero. + c4_Strategy& Strategy() const; + //: Returns the associated strategy pointer. + t4_i32 Position() const; + //: Special access for the DUMP program. + t4_i32 ColSize() const; + //: Returns the number of bytes as stored on disk. + bool IsDirty() const; + //: Returns true if contents needs to be saved. + + void SetLocation(t4_i32, t4_i32); + //: Sets the position and size of this column on file. + void PullLocation(const t4_byte*& ptr_); + //: Extract position and size of this column. + + int AvailAt(t4_i32 offset_) const; + //: Returns number of bytes we can access at once. + const t4_byte* LoadNow(t4_i32); + //: Makes sure the data is loaded into memory. + t4_byte* CopyNow(t4_i32); + //: Makes sure a copy of the data is in memory. + void Grow(t4_i32, t4_i32); + //: Grows the buffer by inserting space. + void Shrink(t4_i32, t4_i32); + //: Shrinks the buffer by removing space. + void SaveNow(c4_Strategy&, t4_i32 pos_); + //: Save the buffer to file. + + const t4_byte* FetchBytes(t4_i32 pos_, int len_, c4_Bytes& buffer_, bool forceCopy_); + //: Returns pointer to data, use buffer only if non-contiguous. + void StoreBytes(t4_i32 pos_, const c4_Bytes& buffer_); + //: Stores a copy of the buffer in the column. + + bool RequiresMap() const; + void ReleaseAllSegments(); + + static t4_i32 PullValue(const t4_byte*& ptr_); + static void PushValue(t4_byte*& ptr_, t4_i32 v_); + + void InsertData(t4_i32 index_, t4_i32 count_, bool clear_); + void RemoveData(t4_i32 index_, t4_i32 count_); + void RemoveGap(); + + enum { kSegBits = 12, kSegMax = 1 << kSegBits, kSegMask = kSegMax - 1 }; + +private: + static int fSegIndex(t4_i32 offset_); + static t4_i32 fSegOffset(int index_); + static int fSegRest(t4_i32 offset_); + + bool UsesMap(const t4_byte*) const; + bool IsMapped() const; + + void ReleaseSegment(int); + void SetupSegments(); + void Validate() const; + void FinishSlack(); + + void MoveGapUp(t4_i32 pos_); + void MoveGapDown(t4_i32 pos_); + void MoveGapTo(t4_i32 pos_); + + t4_byte* CopyData(t4_i32, t4_i32, int); +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_ColOfInts : public c4_Column +{ +public: + c4_ColOfInts (c4_Persist* persist_, int width_ =sizeof (t4_i32)); + + int RowCount() const; + void SetRowCount(int numRows_); + + void FlipBytes(); + + int ItemSize(int index_); + const void* Get(int index_, int& length_); + void Set(int index_, const c4_Bytes& buf_); + + t4_i32 GetInt(int index_); + void SetInt(int index_, t4_i32 value_); + + void Insert(int index_, const c4_Bytes& buf_, int count_); + void Remove(int index_, int count_); + + static int CalcAccessWidth(int numRows_, t4_i32 colSize_); + + void SetAccessWidth(int bits_); + void FixSize(bool fudge_); + void ForceFlip(); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); + +private: + typedef void (c4_ColOfInts::*tGetter) (int); + typedef bool (c4_ColOfInts::*tSetter) (int, const t4_byte*); + + void Get_0b(int index_); + void Get_1b(int index_); + void Get_2b(int index_); + void Get_4b(int index_); + void Get_8i(int index_); + void Get_16i(int index_); + void Get_16r(int index_); + void Get_32i(int index_); + void Get_32r(int index_); + void Get_64i(int index_); + void Get_64r(int index_); + + bool Set_0b(int index_, const t4_byte* item_); + bool Set_1b(int index_, const t4_byte* item_); + bool Set_2b(int index_, const t4_byte* item_); + bool Set_4b(int index_, const t4_byte* item_); + bool Set_8i(int index_, const t4_byte* item_); + bool Set_16i(int index_, const t4_byte* item_); + bool Set_16r(int index_, const t4_byte* item_); + bool Set_32i(int index_, const t4_byte* item_); + bool Set_32r(int index_, const t4_byte* item_); + bool Set_64i(int index_, const t4_byte* item_); + bool Set_64r(int index_, const t4_byte* item_); + + void ResizeData(int index_, int count_, bool clear_ =false); + + tGetter _getter; + tSetter _setter; + + union { + t4_byte _item[8]; // holds temp result (careful with alignment!) + double _aligner; // needed for SPARC + }; + + int _currWidth; // number of bits used for one entry (0..64) + int _dataWidth; // number of bytes used for passing a value along + int _numRows; + bool _mustFlip; +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_ColIter +{ + c4_Column& _column; + t4_i32 _limit; + t4_i32 _pos; + int _len; + const t4_byte* _ptr; + +public: + c4_ColIter (c4_Column& col_, t4_i32 offset_, t4_i32 limit_); +// ~c4_ColIter (); + + bool Next(); + bool Next(int max_); + + const t4_byte* BufLoad() const; + t4_byte* BufSave(); + int BufLen() const; +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_INLINE +#include "column.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/column.inl b/akregator/src/mk4storage/metakit/src/column.inl new file mode 100644 index 00000000..58f34437 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/column.inl @@ -0,0 +1,89 @@ +// column.inl -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Inlined members of the column classes + */ + +///////////////////////////////////////////////////////////////////////////// +// c4_Column + +d4_inline int c4_Column::fSegIndex(t4_i32 offset_) +{ + // limited by max array: 1 << (kSegBits + 15) with 16-bit ints + return (int) (offset_ >> kSegBits); +} + +d4_inline t4_i32 c4_Column::fSegOffset(int index_) +{ + return (t4_i32) index_ << kSegBits; +} + +d4_inline int c4_Column::fSegRest(t4_i32 offset_) +{ + return ((int) offset_ & kSegMask); +} + +d4_inline c4_Persist* c4_Column::Persist() const +{ + return _persist; +} + +d4_inline t4_i32 c4_Column::Position() const +{ + return _position; +} + +d4_inline t4_i32 c4_Column::ColSize() const +{ + return _size; +} + +d4_inline bool c4_Column::IsDirty() const +{ + return _dirty; +} + +d4_inline void c4_Column::SetBuffer(t4_i32 length_) +{ + SetLocation(0, length_); + _dirty = true; +} + +d4_inline const t4_byte* c4_Column::LoadNow(t4_i32 offset_) +{ + if (_segments.GetSize() == 0) + SetupSegments(); + + if (offset_ >= _gap) + offset_ += _slack; + + t4_byte* ptr = (t4_byte*) _segments.GetAt(fSegIndex(offset_)); + return ptr + fSegRest(offset_); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_ColIter + +d4_inline c4_ColIter::c4_ColIter (c4_Column& col_, t4_i32 offset_, t4_i32 limit_) + : _column (col_), _limit (limit_), _pos (offset_), _len (0), _ptr (0) +{ +} + +d4_inline const t4_byte* c4_ColIter::BufLoad() const +{ + return _ptr; +} + +d4_inline t4_byte* c4_ColIter::BufSave() +{ + return _column.CopyNow(_pos); +} + +d4_inline int c4_ColIter::BufLen() const +{ + return _len; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/custom.cpp b/akregator/src/mk4storage/metakit/src/custom.cpp new file mode 100644 index 00000000..a6275cea --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/custom.cpp @@ -0,0 +1,1066 @@ +// custom.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Implementation of many custom viewer classes + */ + +#include "header.h" + +#include "custom.h" +#include "format.h" + +///////////////////////////////////////////////////////////////////////////// + +class c4_CustomHandler : public c4_Handler +{ + c4_CustomSeq* _seq; + +public: + c4_CustomHandler (const c4_Property& prop_, c4_CustomSeq* seq_); + virtual ~c4_CustomHandler (); + + virtual int ItemSize(int index_); + virtual const void* Get(int index_, int& length_); + virtual void Set(int index_, const c4_Bytes& buf_); + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_); + virtual void Remove(int index_, int count_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_CustomHandler::c4_CustomHandler (const c4_Property& prop_, + c4_CustomSeq* seq_) + : c4_Handler (prop_), _seq (seq_) +{ + d4_assert(_seq != 0); +} + +c4_CustomHandler::~c4_CustomHandler () +{ +} + +int c4_CustomHandler::ItemSize(int index_) +{ + c4_Bytes& buf = _seq->Buffer(); + + int colnum = _seq->PropIndex(Property().GetId()); + d4_assert(colnum >= 0); + + if (!_seq->DoGet(index_, colnum, buf)) + return 0; + + return buf.Size(); +} + +const void* c4_CustomHandler::Get(int index_, int& length_) +{ + c4_Bytes& buf = _seq->Buffer(); + + int colnum = _seq->PropIndex(Property().GetId()); + d4_assert(colnum >= 0); + + if (!_seq->DoGet(index_, colnum, buf)) + ClearBytes(buf); + + length_ = buf.Size(); + return buf.Contents(); +} + +void c4_CustomHandler::Set(int index_, const c4_Bytes& buf_) +{ + int colnum = _seq->PropIndex(Property().GetId()); + d4_assert(colnum >= 0); + + _seq->DoSet(index_, colnum, buf_); +} + +void c4_CustomHandler::Insert(int, const c4_Bytes&, int) +{ + d4_assert(0); //! not yet +} + +void c4_CustomHandler::Remove(int, int) +{ + d4_assert(0); //! not yet +} + +c4_Handler* c4_CustomSeq::CreateHandler(const c4_Property& prop_) +{ + return d4_new c4_CustomHandler (prop_, this); +} + +///////////////////////////////////////////////////////////////////////////// + +c4_CustomSeq::c4_CustomSeq (c4_CustomViewer* viewer_) + : c4_HandlerSeq (0), _viewer (viewer_), _inited (false) +{ + d4_assert(_viewer != 0); + + // set up handlers to match a template obtained from the viewer + c4_View v = viewer_->GetTemplate(); + + for (int i = 0; i < v.NumProperties(); ++i) + PropIndex(v.NthProperty(i)); + + _inited = true; +} + +c4_CustomSeq::~c4_CustomSeq () +{ + delete _viewer; +} + +int c4_CustomSeq::NumRows() const +{ + return _inited ? _viewer->GetSize() : 0; +} + +bool c4_CustomSeq::RestrictSearch(c4_Cursor cursor_, int& pos_, int& count_) +{ + if (count_ > 0) + { + int n; + int o = _viewer->Lookup(cursor_, n); + // a -1 result means: "don't know, please scan all" + if (o < 0) + return count_ > 0; + + if (n > 0) + { + if (pos_ < o) + { + count_ -= o - pos_; + pos_ = o; + } + + if (pos_ + count_ > o + n) + count_ = o + n - pos_; + + if (count_ > 0) + return true; + } + } + + count_ = 0; + return false; +} + +void c4_CustomSeq::InsertAt(int p_, c4_Cursor c_, int n_) +{ + _viewer->InsertRows(p_, c_, n_); +} + +void c4_CustomSeq::RemoveAt(int p_, int n_) +{ + _viewer->RemoveRows(p_, n_); +} + +void c4_CustomSeq::Move(int, int) +{ + d4_assert(false); //! not yet +} + +bool c4_CustomSeq::DoGet(int row_, int col_, c4_Bytes& buf_) const +{ + d4_assert(_inited); + + return _viewer->GetItem(row_, col_, buf_); +} + +void c4_CustomSeq::DoSet(int row_, int col_, const c4_Bytes& buf_) +{ + d4_assert(_inited); + + d4_dbgdef(const bool f =) + _viewer->SetItem(row_, col_, buf_); + d4_assert(f); +} + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_CustomViewer + * + * Abstract base class for definition of custom views. + * + * A custom view is a view which can be accessed like any other view, using + * row and property operations, but which is fully managed by a customized + * "viewer" class. The viewer will eventually handle all requests for the + * view, such as defining its structure and size, as well as providing the + * actual data values when requested. + * + * Custom views cannot propagate changes. + * + * To implement a custom view, you must derive your viewer from this base + * class and define each of the virtual members. Then create a new object + * of this type on the heap and pass it to the c4_View constructor. Your + * viewer will automatically be destroyed when the last reference to its + * view goes away. See the DBF2MK sample code for an example of a viewer. + */ + +c4_CustomViewer::~c4_CustomViewer () +{ +} + + /// Locate a row in this view, try to use native searches +int c4_CustomViewer::Lookup(c4_Cursor, int& count_) +{ + count_ = GetSize(); + return 0; // not implemented, return entire view range +} + + /// Store one data item, supplied as a generic data value +bool c4_CustomViewer::SetItem(int, int, const c4_Bytes&) +{ + return false; // default is not modifiable +} + + /// Insert one or more copies of a row (if possible) +bool c4_CustomViewer::InsertRows(int, c4_Cursor, int) +{ + return false; // default is not modifiable +} + + /// Remove one or more rows (this is not always possible) +bool c4_CustomViewer::RemoveRows(int, int) +{ + return false; // default is not modifiable +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_SliceViewer : public c4_CustomViewer +{ + c4_View _parent; + int _first, _limit, _step; + +public: + c4_SliceViewer (c4_Sequence& seq_, int first_, int limit_, int step_); + virtual ~c4_SliceViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +c4_SliceViewer::c4_SliceViewer (c4_Sequence& seq_, int first_, int limit_, int step_) + : _parent (&seq_), _first (first_), _limit (limit_), _step (step_) +{ + d4_assert(_step != 0); +} + +c4_SliceViewer::~c4_SliceViewer () +{ +} + +c4_View c4_SliceViewer::GetTemplate() +{ + return _parent.Clone(); // could probably return _parent just as well +} + +int c4_SliceViewer::GetSize() +{ + int n = _limit >= 0 ? _limit : _parent.GetSize(); + if (n < _first) + n = _first; + + int k = _step < 0 ? -_step : _step; + return (n - _first + k - 1) / k; +} + +bool c4_SliceViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + row_ = _first + _step * (_step > 0 ? row_ : row_ - GetSize() + 1); + + return _parent.GetItem(row_, col_, buf_); +} + +bool c4_SliceViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + row_ = _first + _step * (_step > 0 ? row_ : row_ - GetSize() + 1); + + _parent.SetItem(row_, col_, buf_); + return true; +} + +bool c4_SliceViewer::InsertRows(int pos_, c4_Cursor value_, int count_) +{ + if (_step != 1) + return false; + + pos_ = _first + _step * (_step > 0 ? pos_ : pos_ - GetSize() + 1); + if (_limit >= 0) + _limit += count_; + + _parent.InsertAt(pos_, *value_, count_); + return true; +} + +bool c4_SliceViewer::RemoveRows(int pos_, int count_) +{ + if (_step != 1) + return false; + + pos_ = _first + _step * (_step > 0 ? pos_ : pos_ - GetSize() + 1); + if (_limit >= 0) + _limit -= count_; + + _parent.RemoveAt(pos_, count_); + return true; +} + +c4_CustomViewer* f4_CustSlice(c4_Sequence& seq_, int first_, int limit_, int step_) +{ + return d4_new c4_SliceViewer (seq_, first_, limit_, step_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_ProductViewer : public c4_CustomViewer +{ + c4_View _parent, _argView, _template; + +public: + c4_ProductViewer (c4_Sequence& seq_, const c4_View& view_); + virtual ~c4_ProductViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); +}; + +c4_ProductViewer::c4_ProductViewer (c4_Sequence& seq_, const c4_View& view_) + : _parent (&seq_), _argView (view_), _template (_parent.Clone()) +{ + for (int i = 0; i < _argView.NumProperties(); ++i) + _template.AddProperty(_argView.NthProperty(i)); +} + +c4_ProductViewer::~c4_ProductViewer () +{ +} + +c4_View c4_ProductViewer::GetTemplate() +{ + return _template; +} + +int c4_ProductViewer::GetSize() +{ + return _parent.GetSize() * _argView.GetSize(); +} + +bool c4_ProductViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + + if (col_ < v.NumProperties()) + { + row_ /= _argView.GetSize(); + } + else + { + v = _argView; + row_ %= _argView.GetSize(); + col_ = v.FindProperty(_template.NthProperty(col_).GetId()); + + d4_assert(col_ >= 0); + } + + return v.GetItem(row_, col_, buf_); +} + +c4_CustomViewer* f4_CustProduct(c4_Sequence& seq_, const c4_View& view_) +{ + return d4_new c4_ProductViewer (seq_, view_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_RemapWithViewer : public c4_CustomViewer +{ + c4_View _parent, _argView; + +public: + c4_RemapWithViewer (c4_Sequence& seq_, const c4_View& view_); + virtual ~c4_RemapWithViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + bool SetItem(int row_, int col_, const c4_Bytes& buf_); +}; + +c4_RemapWithViewer::c4_RemapWithViewer (c4_Sequence& seq_, const c4_View& view_) + : _parent (&seq_), _argView (view_) +{ +} + +c4_RemapWithViewer::~c4_RemapWithViewer () +{ +} + +c4_View c4_RemapWithViewer::GetTemplate() +{ + return _parent.Clone(); // could probably return _parent just as well +} + +int c4_RemapWithViewer::GetSize() +{ + return _argView.GetSize(); +} + +bool c4_RemapWithViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + const c4_Property& map = _argView.NthProperty(0); + d4_assert(map.Type() == 'I'); + + row_ = ((const c4_IntProp&) map) (_argView[row_]); + + return _parent.GetItem(row_, col_, buf_); +} + +bool c4_RemapWithViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + const c4_Property& map = _argView.NthProperty(0); + d4_assert(map.Type() == 'I'); + + row_ = ((const c4_IntProp&) map) (_argView[row_]); + + _parent.SetItem(row_, col_, buf_); + return true; +} + +c4_CustomViewer* f4_CustRemapWith(c4_Sequence& seq_, const c4_View& view_) +{ + return d4_new c4_RemapWithViewer (seq_, view_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_PairViewer : public c4_CustomViewer +{ + c4_View _parent, _argView, _template; + +public: + c4_PairViewer (c4_Sequence& seq_, const c4_View& view_); + virtual ~c4_PairViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +c4_PairViewer::c4_PairViewer (c4_Sequence& seq_, const c4_View& view_) + : _parent (&seq_), _argView (view_), _template (_parent.Clone()) +{ + for (int i = 0; i < _argView.NumProperties(); ++i) + _template.AddProperty(_argView.NthProperty(i)); +} + +c4_PairViewer::~c4_PairViewer () +{ +} + +c4_View c4_PairViewer::GetTemplate() +{ + return _template; +} + +int c4_PairViewer::GetSize() +{ + return _parent.GetSize(); +} + +bool c4_PairViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + + if (col_ >= v.NumProperties()) + { + v = _argView; + col_ = v.FindProperty(_template.NthProperty(col_).GetId()); + d4_assert(col_ >= 0); + } + + return v.GetItem(row_, col_, buf_); +} + +bool c4_PairViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + c4_View v = _parent; + + if (col_ >= v.NumProperties()) + { + v = _argView; + col_ = v.FindProperty(_template.NthProperty(col_).GetId()); + d4_assert(col_ >= 0); + } + + v.SetItem(row_, col_, buf_); + return true; +} + +bool c4_PairViewer::InsertRows(int pos_, c4_Cursor value_, int count_) +{ + _parent.InsertAt(pos_, *value_, count_); + _argView.InsertAt(pos_, *value_, count_); + return true; +} + +bool c4_PairViewer::RemoveRows(int pos_, int count_) +{ + _parent.RemoveAt(pos_, count_); + _argView.RemoveAt(pos_, count_); + return true; +} + +c4_CustomViewer* f4_CustPair(c4_Sequence& seq_, const c4_View& view_) +{ + return d4_new c4_PairViewer (seq_, view_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_ConcatViewer : public c4_CustomViewer +{ + c4_View _parent, _argView; + +public: + c4_ConcatViewer (c4_Sequence& seq_, const c4_View& view_); + virtual ~c4_ConcatViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + bool SetItem(int row_, int col_, const c4_Bytes& buf_); +}; + +c4_ConcatViewer::c4_ConcatViewer (c4_Sequence& seq_, const c4_View& view_) + : _parent (&seq_), _argView (view_) +{ +} + +c4_ConcatViewer::~c4_ConcatViewer () +{ +} + +c4_View c4_ConcatViewer::GetTemplate() +{ + return _parent.Clone(); // could probably return _parent just as well +} + +int c4_ConcatViewer::GetSize() +{ + return _parent.GetSize() + _argView.GetSize(); +} + +bool c4_ConcatViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + + if (row_ >= _parent.GetSize()) + { + v = _argView; + row_ -= _parent.GetSize(); + col_ = v.FindProperty(_parent.NthProperty(col_).GetId()); + + if (col_ < 0) + return false; + } + + return v.GetItem(row_, col_, buf_); +} + +bool c4_ConcatViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + c4_View v = _parent; + + if (row_ >= _parent.GetSize()) + { + v = _argView; + row_ -= _parent.GetSize(); + col_ = v.FindProperty(_parent.NthProperty(col_).GetId()); + d4_assert(col_ >= 0); + } + + v.SetItem(row_, col_, buf_); + return true; +} + +c4_CustomViewer* f4_CustConcat(c4_Sequence& seq_, const c4_View& view_) +{ + return d4_new c4_ConcatViewer (seq_, view_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_RenameViewer : public c4_CustomViewer +{ + c4_View _parent, _template; + +public: + c4_RenameViewer (c4_Sequence& seq_, const c4_Property& old_, + const c4_Property& new_); + virtual ~c4_RenameViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + virtual bool SetItem(int row_, int col_, const c4_Bytes& buf_); + //virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + //virtual bool RemoveRows(int pos_, int count_=1); +}; + +c4_RenameViewer::c4_RenameViewer (c4_Sequence& seq_, const c4_Property& old_, + const c4_Property& new_) + : _parent (&seq_) +{ + for (int i = 0; i < _parent.NumProperties(); ++i) + { + const c4_Property& prop = _parent.NthProperty(i); + _template.AddProperty(prop.GetId() == old_.GetId() ? new_ : prop); + } +} + +c4_RenameViewer::~c4_RenameViewer () +{ +} + +c4_View c4_RenameViewer::GetTemplate() +{ + return _template; +} + +int c4_RenameViewer::GetSize() +{ + return _parent.GetSize(); +} + +bool c4_RenameViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + return _parent.GetItem(row_, col_, buf_); +} + +bool c4_RenameViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + _parent.SetItem(row_, col_, buf_); + return true; +} + +c4_CustomViewer* f4_CustRename(c4_Sequence& seq_, const c4_Property& old_, + const c4_Property& new_) +{ + return d4_new c4_RenameViewer (seq_, old_, new_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_GroupByViewer : public c4_CustomViewer +{ + c4_View _parent, _keys, _sorted, _temp; + c4_Property _result; + c4_DWordArray _map; + + int ScanTransitions(int lo_, int hi_, + t4_byte* flags_, const c4_View& match_) const; + +public: + c4_GroupByViewer (c4_Sequence& seq_, const c4_View& keys_, + const c4_Property& result_); + virtual ~c4_GroupByViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); +}; + +c4_GroupByViewer::c4_GroupByViewer (c4_Sequence& seq_, const c4_View& keys_, + const c4_Property& result_) + : _parent (&seq_), _keys (keys_), _result (result_) +{ + _sorted = _parent.SortOn(_keys); + int n = _sorted.GetSize(); + + c4_Bytes temp; + t4_byte* buf = temp.SetBufferClear(n); + + int groups = 0; + if (n > 0) + { + ++buf[0]; // the first entry is always a transition + groups = 1 + ScanTransitions(1, n, buf, _sorted.Project(_keys)); + } + + // set up a map pointing to each transition + _map.SetSize(groups + 1); + int j = 0; + + for (int i = 0; i < n; ++i) + if (buf[i]) + _map.SetAt(j++, i); + + // also append an entry to point just past the end + _map.SetAt(j, n); + + d4_assert(_map.GetAt(0) == 0); + d4_assert(j == groups); +} + +c4_GroupByViewer::~c4_GroupByViewer () +{ +} + +int c4_GroupByViewer::ScanTransitions(int lo_, int hi_, + t4_byte* flags_, const c4_View& match_) const +{ + d4_assert(lo_ > 0); + + int m = hi_ - lo_; + d4_assert(m >= 0); + + // done if nothing left or if entire range is identical + if (m == 0 || match_ [lo_-1] == match_ [hi_-1]) + return 0; + + // range has a transition, done if it is exactly of size one + if (m == 1) + { + ++(flags_[lo_]); + return 1; + } + + // use binary splitting if the range has enough entries + if (m >= 5) + return ScanTransitions(lo_, lo_ + m / 2, flags_, match_) + + ScanTransitions(lo_ + m / 2, hi_, flags_, match_); + + // else use a normal linear scan + int n = 0; + + for (int i = lo_; i < hi_; ++i) + if (match_ [i] != match_ [i-1]) + { + ++(flags_[i]); + ++n; + } + + return n; +} + +c4_View c4_GroupByViewer::GetTemplate() +{ + c4_View v = _keys.Clone(); + v.AddProperty(_result); + + return v; +} + +int c4_GroupByViewer::GetSize() +{ + d4_assert(_map.GetSize() > 0); + + return _map.GetSize() - 1; +} + +bool c4_GroupByViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + if (col_ < _keys.NumProperties()) + return _sorted.GetItem(_map.GetAt(row_), col_, buf_); + + d4_assert(col_ == _keys.NumProperties()); + + t4_i32 count; + switch (_result.Type()) + { + case 'I': count = _map.GetAt(row_ + 1) - _map.GetAt(row_); + buf_ = c4_Bytes (&count, sizeof count, true); + break; + case 'V': _temp = _sorted.Slice(_map.GetAt(row_), _map.GetAt(row_ + 1)) + .ProjectWithout(_keys); + buf_ = c4_Bytes (&_temp, sizeof _temp, true); + break; + default: d4_assert(0); + } + + return true; +} + +c4_CustomViewer* f4_CustGroupBy(c4_Sequence& seq_, const c4_View& template_, + const c4_Property& result_) +{ + return d4_new c4_GroupByViewer (seq_, template_, result_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_JoinPropViewer : public c4_CustomViewer +{ + c4_View _parent, _template; + c4_ViewProp _sub; + int _subPos, _subWidth; + c4_DWordArray _base, _offset; + +public: + c4_JoinPropViewer (c4_Sequence& seq_, const c4_ViewProp& sub_, bool outer_); + virtual ~c4_JoinPropViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); +}; + +c4_JoinPropViewer::c4_JoinPropViewer (c4_Sequence& seq_, + const c4_ViewProp& sub_, bool outer_) + : _parent (&seq_), + _sub (sub_), _subPos (_parent.FindProperty(sub_.GetId())), _subWidth (0) +{ + d4_assert(_subPos >= 0); + + for (int k = 0; k < _parent.NumProperties(); ++k) + { + if (k != _subPos) + _template.AddProperty(_parent.NthProperty(k)); + else // if there are no rows, then this join does very little anyway + //! OOPS: if this is an unattached view, then the subviews can differ + if (_parent.GetSize() > 0) + { + c4_View view = sub_ (_parent[0]); + for (int l = 0; l < view.NumProperties(); ++l) + { + _template.AddProperty(view.NthProperty(l)); + ++_subWidth; + } + } + } + + _base.SetSize(0, 5); + _offset.SetSize(0, 5); + + for (int i = 0; i < _parent.GetSize(); ++i) + { + c4_View v = _sub (_parent[i]); + + int n = v.GetSize(); + if (n == 0 && outer_) + { + _base.Add(i); + _offset.Add(~ (t4_i32) 0); // special null entry for outer joins + } + else + for (int j = 0; j < n; ++j) + { + _base.Add(i); + _offset.Add(j); + } + } +} + +c4_JoinPropViewer::~c4_JoinPropViewer () +{ +} + +c4_View c4_JoinPropViewer::GetTemplate() +{ + return _template; +} + +int c4_JoinPropViewer::GetSize() +{ + return _base.GetSize(); +} + +bool c4_JoinPropViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + int r = _base.GetAt(row_); + + if (col_ >= _subPos) + if (col_ >= _subPos + _subWidth) + { + col_ -= _subWidth - 1; + } + else + { + v = _sub (_parent[r]); + r = _offset.GetAt(row_); + if (r < 0) + return false; // if this is a null row in an outer join + + col_ = v.FindProperty(_template.NthProperty(col_).GetId()); + if (col_ < 0) + return false; // if subview doesn't have all properties + } + + return v.GetItem(r, col_, buf_); +} + +c4_CustomViewer* f4_CustJoinProp(c4_Sequence& seq_, + const c4_ViewProp& sub_, bool outer_) +{ + return d4_new c4_JoinPropViewer (seq_, sub_, outer_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_JoinViewer : public c4_CustomViewer +{ + c4_View _parent, _argView, _template; + c4_DWordArray _base, _offset; + +public: + c4_JoinViewer (c4_Sequence& seq_, const c4_View& keys_, + const c4_View& view_, bool outer_); + virtual ~c4_JoinViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); +}; + +c4_JoinViewer::c4_JoinViewer (c4_Sequence& seq_,const c4_View& keys_, + const c4_View& view_, bool outer_) + : _parent (&seq_), _argView (view_.SortOn(keys_)) +{ + // why not in GetTemplate, since we don't need to know this... + _template = _parent.Clone(); + for (int l = 0; l < _argView.NumProperties(); ++l) + _template.AddProperty(_argView.NthProperty(l)); + + c4_View sorted = _parent.SortOn(keys_).Project(keys_); + c4_View temp = _argView.Project(keys_); + + _base.SetSize(0, 5); + _offset.SetSize(0, 5); + + int j = 0, n = 0; + + for (int i = 0; i < sorted.GetSize(); ++i) + { + int orig = _parent.GetIndexOf(sorted[i]); + d4_assert(orig >= 0); + + if (i > 0 && sorted[i] == sorted[i-1]) + { + // if last key was same, repeat the same join + int last = _offset.GetSize() - n; + for (int k = 0; k < n; ++k) + { + _base.Add(orig); + _offset.Add(_offset.GetAt(last + k)); + } + } + else // no, this is a new combination + { + bool match = false; + + // advance until the temp view entry is >= this sorted entry + while (j < temp.GetSize()) + if (sorted[i] <= temp[j]) + { + match = sorted[i] == temp[j]; + break; + } + else + ++j; + + n = 0; + + if (match) + { + do { + _base.Add(orig); + _offset.Add(j); + ++n; + } while (++j < temp.GetSize() && temp[j] == temp[j-1]); + } + else if (outer_) + { + // no match, add an entry anyway if this is an outer join + _base.Add(orig); + _offset.Add(~ (t4_i32) 0); // special null entry + ++n; + } + } + } +} + +c4_JoinViewer::~c4_JoinViewer () +{ +} + +c4_View c4_JoinViewer::GetTemplate() +{ + return _template; +} + +int c4_JoinViewer::GetSize() +{ + return _base.GetSize(); +} + +bool c4_JoinViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + int r = _base.GetAt(row_); + + if (col_ >= v.NumProperties()) + { + v = _argView; + r = _offset.GetAt(row_); + if (r < 0) + return false; // if this is a null row in an outer join + + col_ = v.FindProperty(_template.NthProperty(col_).GetId()); + if (col_ < 0) + return false; // if second view doesn't have all properties + } + + return v.GetItem(r, col_, buf_); +} + +#if 0 +bool c4_JoinViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + c4_View v = _parent; + + int o = 0; + int r = _offset.GetAt(row_); + + if (r < 0) + { + o = ~r; + if (o == 0) + return false; // if this is a null row in an outer join + r -= o; + } + + if (col_ >= v.NumProperties()) + { + v = _argView; + r = _o; + + col_ = v.FindProperty(_template.NthProperty(col_)); + if (col_ < 0) + return false; // if second view doesn't have all properties + } + + return v.GetItem(r, col_, buf_); +} +#endif + +c4_CustomViewer* f4_CustJoin(c4_Sequence& seq_, const c4_View& keys_, + const c4_View& view_, bool outer_) +{ + return d4_new c4_JoinViewer (seq_, keys_, view_, outer_); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/custom.h b/akregator/src/mk4storage/metakit/src/custom.h new file mode 100644 index 00000000..80555d3b --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/custom.h @@ -0,0 +1,63 @@ +// custom.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Encapsulation of many custom viewer classes + */ + +#ifndef __CUSTOM_H__ +#define __CUSTOM_H__ + +#ifndef __FIELD_H__ +#include "field.h" +#endif +#ifndef __STORE_H__ +#include "handler.h" +#endif + +///////////////////////////////////////////////////////////////////////////// + +class c4_CustomSeq : public c4_HandlerSeq +{ + c4_CustomViewer* _viewer; + bool _inited; + +public: + c4_CustomSeq (c4_CustomViewer* viewer_); + virtual ~c4_CustomSeq (); + + virtual int NumRows() const; + + virtual bool RestrictSearch(c4_Cursor, int&, int&); + + virtual void InsertAt(int, c4_Cursor, int =1); + virtual void RemoveAt(int, int =1); + virtual void Move(int from_, int); + + bool DoGet(int row_, int col_, c4_Bytes& buf_) const; + void DoSet(int row_, int col_, const c4_Bytes& buf_); + +private: // this *is* used, as override + virtual c4_Handler* CreateHandler(const c4_Property&); +}; + +///////////////////////////////////////////////////////////////////////////// + + extern c4_CustomViewer* f4_CustSlice(c4_Sequence&, int, int , int); + extern c4_CustomViewer* f4_CustProduct(c4_Sequence&, const c4_View&); + extern c4_CustomViewer* f4_CustRemapWith(c4_Sequence&, const c4_View&); + extern c4_CustomViewer* f4_CustPair(c4_Sequence&, const c4_View&); + extern c4_CustomViewer* f4_CustConcat(c4_Sequence&, const c4_View&); + extern c4_CustomViewer* f4_CustRename(c4_Sequence&, + const c4_Property&, const c4_Property&); + extern c4_CustomViewer* f4_CustGroupBy(c4_Sequence&, + const c4_View&, const c4_Property&); + extern c4_CustomViewer* f4_CustJoinProp(c4_Sequence&, + const c4_ViewProp&, bool); + extern c4_CustomViewer* f4_CustJoin(c4_Sequence&, + const c4_View&, const c4_View&, bool); + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/derived.cpp b/akregator/src/mk4storage/metakit/src/derived.cpp new file mode 100644 index 00000000..3baf5e3e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/derived.cpp @@ -0,0 +1,1003 @@ +// derived.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Derived views are virtual views which track changes + */ + +#include "header.h" +#include "handler.h" +#include "store.h" +#include "derived.h" + +#include <stdlib.h> // qsort + +///////////////////////////////////////////////////////////////////////////// +// Implemented in this file + +// class c4_Sequence; + class c4_DerivedSeq; + class c4_FilterSeq; + class c4_SortSeq; + class c4_ProjectSeq; + +///////////////////////////////////////////////////////////////////////////// + +class c4_FilterSeq : public c4_DerivedSeq +{ +protected: + c4_DWordArray _rowMap; + c4_DWordArray _revMap; + c4_Row _lowRow; + c4_Row _highRow; + c4_Bytes _rowIds; + +protected: + c4_FilterSeq (c4_Sequence& seq_); + virtual ~c4_FilterSeq (); + + void FixupReverseMap(); + int PosInMap(int index_) const; + bool Match(int index_, c4_Sequence& seq_, + const int* =0, const int* =0) const; + bool MatchOne(int prop_, const c4_Bytes& data_) const; + +public: + c4_FilterSeq (c4_Sequence& seq_, c4_Cursor low_, c4_Cursor high_); + + virtual int RemapIndex(int, const c4_Sequence*) const; + + virtual int NumRows() const; + + virtual int Compare(int, c4_Cursor) const; + virtual bool Get(int, int, c4_Bytes&); + + virtual void InsertAt(int, c4_Cursor, int =1); + virtual void RemoveAt(int, int =1); + virtual void Set(int, const c4_Property&, const c4_Bytes&); + virtual void SetSize(int); + + virtual c4_Notifier* PreChange(c4_Notifier& nf_); + virtual void PostChange(c4_Notifier& nf_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FilterSeq::c4_FilterSeq (c4_Sequence& seq_) + : c4_DerivedSeq (seq_) +{ + _rowMap.SetSize(_seq.NumRows()); + _revMap.SetSize(_seq.NumRows()); + d4_assert(NumRows() == _seq.NumRows()); + + for (int i = 0; i < NumRows(); ++i) + { + _rowMap.SetAt(i, i); + _revMap.SetAt(i, i); + } +} + +c4_FilterSeq::c4_FilterSeq (c4_Sequence& seq_, c4_Cursor low_, + c4_Cursor high_) + : c4_DerivedSeq (seq_), _lowRow (*low_), _highRow (*high_) +{ + d4_assert((&_lowRow)._index == 0); + d4_assert((&_highRow)._index == 0); + + // use a sneaky way to obtain the sequence pointers and indices + c4_Sequence* lowSeq = (& _lowRow)._seq; + c4_Sequence* highSeq = (& _highRow)._seq; + d4_assert(lowSeq && highSeq); + + // prepare column numbers to avoid looking them up on every row + // lowCols is a vector of column numbers to use for the low limits + // highCols is a vector of column numbers to use for the high limits + int nl = lowSeq->NumHandlers(), nh = highSeq->NumHandlers(); + c4_Bytes lowVec, highVec; + int* lowCols = (int*) lowVec.SetBufferClear(nl * sizeof (int)); + int* highCols = (int*) highVec.SetBufferClear(nh * sizeof (int)); + + for (int il = 0; il < nl; ++il) + lowCols[il] = seq_.PropIndex(lowSeq->NthPropId(il)); + for (int ih = 0; ih < nh; ++ih) + highCols[ih] = seq_.PropIndex(highSeq->NthPropId(ih)); + + // set _rowIds flag buffer for fast matching + { + int max = -1; + + { + for (int i1 = 0; i1 < nl; ++i1) + { + int n = lowSeq->NthPropId(i1); + if (max < n) + max = n; + } + for (int i2 = 0; i2 < nh; ++i2) + { + int n = highSeq->NthPropId(i2); + if (max < n) + max = n; + } + } + + t4_byte* p = _rowIds.SetBufferClear(max + 1); + + { + for (int i1 = 0; i1 < nl; ++i1) + p[lowSeq->NthPropId(i1)] |= 1; + for (int i2 = 0; i2 < nh; ++i2) + p[highSeq->NthPropId(i2)] |= 2; + } + } + + // now go through all rows and select the ones that are in range + + _rowMap.SetSize(_seq.NumRows()); // avoid growing, use safe upper bound + + int n = 0; + + for (int i = 0; i < _seq.NumRows(); ++i) + if (Match(i, _seq, lowCols, highCols)) + _rowMap.SetAt(n++, i); + + _rowMap.SetSize(n); + + FixupReverseMap(); +} + +c4_FilterSeq::~c4_FilterSeq () +{ +} + +void c4_FilterSeq::FixupReverseMap() +{ + int n = _seq.NumRows(); + + _revMap.SetSize(0); + + if (n > 0) + { + _revMap.InsertAt(0, ~ (t4_i32) 0, n); //! + + for (int i = 0; i < _rowMap.GetSize(); ++i) + _revMap.SetAt((int) _rowMap.GetAt(i), i); + } +} + +bool c4_FilterSeq::MatchOne(int prop_, const c4_Bytes& data_) const +{ + d4_assert(prop_ < _rowIds.Size()); + + t4_byte flag = _rowIds.Contents()[prop_]; + d4_assert(flag); + + if (flag & 1) + { + c4_Sequence* lowSeq = (& _lowRow)._seq; + + c4_Handler& h = lowSeq->NthHandler(lowSeq->PropIndex(prop_)); + if (h.Compare(0, data_) > 0) + return false; + } + + if (flag & 2) + { + c4_Sequence* highSeq = (& _highRow)._seq; + + c4_Handler& h = highSeq->NthHandler(highSeq->PropIndex(prop_)); + if (h.Compare(0, data_) < 0) + return false; + } + + return true; +} + +bool c4_FilterSeq::Match(int index_, c4_Sequence& seq_, + const int* lowCols_, const int* highCols_) const +{ + // use a sneaky way to obtain the sequence pointers and indices + c4_Sequence* lowSeq = (& _lowRow)._seq; + c4_Sequence* highSeq = (& _highRow)._seq; + d4_assert(lowSeq && highSeq); + + int nl = lowSeq->NumHandlers(), nh = highSeq->NumHandlers(); + + c4_Bytes data; + + // check each of the lower limits + for (int cl = 0; cl < nl; ++cl) + { + c4_Handler& hl = lowSeq->NthHandler(cl); + + int n = lowCols_ ? lowCols_[cl] + : seq_.PropIndex(lowSeq->NthPropId(cl)); + if (n >= 0) + { + c4_Handler& h = seq_.NthHandler(n); + const c4_Sequence* hc = seq_.HandlerContext(n); + int i = seq_.RemapIndex(index_, hc); + + h.GetBytes(i, data); + } + else + hl.ClearBytes(data); + + if (hl.Compare(0, data) > 0) + return false; + } + + // check each of the upper limits + for (int ch = 0; ch < nh; ++ch) + { + c4_Handler& hh = highSeq->NthHandler(ch); + + int n = highCols_ ? highCols_[ch] + : seq_.PropIndex(highSeq->NthPropId(ch)); + if (n >= 0) + { + c4_Handler& h = seq_.NthHandler(n); + const c4_Sequence* hc = seq_.HandlerContext(n); + int i = seq_.RemapIndex(index_, hc); + + h.GetBytes(i, data); + } + else + hh.ClearBytes(data); + + if (hh.Compare(0, data) < 0) + return false; + } + + return true; +} + +int c4_FilterSeq::RemapIndex(int index_, const c4_Sequence* seq_) const +{ + return seq_ == this ? index_ + : _seq.RemapIndex((int) _rowMap.GetAt(index_), seq_); +} + +int c4_FilterSeq::NumRows() const +{ + return _rowMap.GetSize(); +} + +int c4_FilterSeq::Compare(int index_, c4_Cursor cursor_) const +{ + return _seq.Compare((int) _rowMap.GetAt(index_), cursor_); +} + +bool c4_FilterSeq::Get(int index_, int propId_, c4_Bytes& bytes_) +{ + return _seq.Get((int) _rowMap.GetAt(index_), propId_, bytes_); +} + +void c4_FilterSeq::InsertAt(int, c4_Cursor, int) +{ + d4_assert(0); +} + +void c4_FilterSeq::RemoveAt(int, int) +{ + d4_assert(0); +} + +void c4_FilterSeq::Set(int, const c4_Property&, const c4_Bytes&) +{ + d4_assert(0); +} + +void c4_FilterSeq::SetSize(int) +{ + d4_assert(0); +} + +int c4_FilterSeq::PosInMap(int index_) const +{ + int i = 0; + + while (i < NumRows()) + if ((int) _rowMap.GetAt(i) >= index_) + break; + else + ++i; + + return i; +} + +c4_Notifier* c4_FilterSeq::PreChange(c4_Notifier& nf_) +{ + if (!GetDependencies()) + return 0; + + c4_Notifier* chg = d4_new c4_Notifier (this); + + bool pass = false; + + switch (nf_._type) + { + case c4_Notifier::kSet: + pass = nf_._propId >= _rowIds.Size() || + _rowIds.Contents()[nf_._propId] == 0; + // fall through... + + case c4_Notifier::kSetAt: + { + int r = (int) _revMap.GetAt(nf_._index); + + bool includeRow = r >= 0; + if (!pass) + if (nf_._type == c4_Notifier::kSetAt) + { + d4_assert(nf_._cursor != 0); + includeRow = Match(nf_._cursor->_index, + *nf_._cursor->_seq); + } + else // set just one property, and it's not in a row yet + includeRow = MatchOne(nf_._propId, *nf_._bytes); + + if (r >= 0 && !includeRow) + chg->StartRemoveAt(r, 1); + else if (r < 0 && includeRow) + chg->StartInsertAt(PosInMap(nf_._index), *nf_._cursor, 1); + else if (includeRow) + { + d4_assert(r >= 0); + + if (nf_._type == c4_Notifier::kSetAt) + chg->StartSetAt(r, *nf_._cursor); + else + chg->StartSet(r, nf_._propId, *nf_._bytes); + } + } + break; + + case c4_Notifier::kInsertAt: + { + int i = PosInMap(nf_._index); + + d4_assert(nf_._cursor != 0); + if (Match(nf_._cursor->_index, *nf_._cursor->_seq)) + chg->StartInsertAt(i, *nf_._cursor, nf_._count); + } + break; + + case c4_Notifier::kRemoveAt: + { + int i = PosInMap(nf_._index); + int j = PosInMap(nf_._index + nf_._count); + d4_assert(j >= i); + + if (j > i) + chg->StartRemoveAt(i, j - i); + } + break; + + case c4_Notifier::kMove: + { + int i = PosInMap(nf_._index); + bool inMap = i < NumRows() && (int) _rowMap.GetAt(i) == nf_._index; + + if (inMap && nf_._index != nf_._count) + chg->StartMove(i, PosInMap(nf_._count)); + } + break; + } + + return chg; +} + +void c4_FilterSeq::PostChange(c4_Notifier& nf_) +{ + bool pass = false; + + switch (nf_._type) + { + case c4_Notifier::kSet: + pass = nf_._propId >= _rowIds.Size() || + _rowIds.Contents()[nf_._propId] == 0; + // fall through... + + case c4_Notifier::kSetAt: + { + int r = (int) _revMap.GetAt(nf_._index); + + bool includeRow = r >= 0; + if (!pass) + if (nf_._type == c4_Notifier::kSetAt) + { + d4_assert(nf_._cursor != 0); + includeRow = Match(nf_._cursor->_index, + *nf_._cursor->_seq); + } + else // set just one property, and it's not in a row yet + includeRow = MatchOne(nf_._propId, *nf_._bytes); + + if (r >= 0 && !includeRow) + _rowMap.RemoveAt(r); + else if (r < 0 && includeRow) + _rowMap.InsertAt(PosInMap(nf_._index), nf_._index); + else + break; + + FixupReverseMap(); + } + break; + + case c4_Notifier::kInsertAt: + { + int i = PosInMap(nf_._index); + + if (Match(nf_._index, _seq)) + { + _rowMap.InsertAt(i, 0, nf_._count); + + for (int j = 0; j < nf_._count; ++j) + _rowMap.SetAt(i++, nf_._index + j); + } + + while (i < NumRows()) + _rowMap.ElementAt(i++) += nf_._count; + + FixupReverseMap(); + } + break; + + case c4_Notifier::kRemoveAt: + { + int i = PosInMap(nf_._index); + int j = PosInMap(nf_._index + nf_._count); + d4_assert(j >= i); + + if (j > i) + _rowMap.RemoveAt(i, j - i); + + while (i < NumRows()) + _rowMap.ElementAt(i++) -= nf_._count; + + FixupReverseMap(); + } + break; + + case c4_Notifier::kMove: + { + int i = PosInMap(nf_._index); + bool inMap = i < NumRows() && (int) _rowMap.GetAt(i) == nf_._index; + + if (inMap && nf_._index != nf_._count) + { + int j = PosInMap(nf_._count); + + _rowMap.RemoveAt(i); + + if (j > i) + --j; + + _rowMap.InsertAt(j, nf_._count); + + FixupReverseMap(); + } + } + break; + } +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_SortSeq : public c4_FilterSeq +{ +public: + typedef t4_i32 T; + + c4_SortSeq (c4_Sequence& seq_, c4_Sequence* down_); + virtual ~c4_SortSeq (); + + virtual c4_Notifier* PreChange(c4_Notifier& nf_); + virtual void PostChange(c4_Notifier& nf_); + +private: + struct c4_SortInfo + { + c4_Handler* _handler; + const c4_Sequence* _context; + c4_Bytes _buffer; + + int CompareOne(c4_Sequence& seq_, T a, T b) + { + _handler->GetBytes(seq_.RemapIndex((int) b, _context), _buffer, true); + return _handler->Compare(seq_.RemapIndex((int) a, _context), _buffer); + } + }; + + bool LessThan(T a, T b); + bool TestSwap( T& first , T& second ); + void MergeSortThis( T* ar, int size , T scratch[] ); + void MergeSort( T ar[] , int size ); + + virtual int Compare(int, c4_Cursor) const; + int PosInMap(c4_Cursor cursor_) const; + + c4_SortInfo* _info; + c4_Bytes _down; + int _width; +}; + +///////////////////////////////////////////////////////////////////////////// + +bool c4_SortSeq::LessThan(T a, T b) +{ + if (a == b) + return false; + + // go through each of the columns and compare values, but since + // handler access is used, we must be careful to remap indices + + c4_SortInfo* info; + + for (info = _info; info->_handler; ++info) + { + int f = info->CompareOne(_seq, a, b); + if (f) + { + int n = info - _info; + if (_width < n) + _width = n; + + return (_down.Contents()[n] ? -f : f) < 0; + } + } + + _width = info - _info; + return a < b; +} + +inline bool c4_SortSeq::TestSwap( T& first , T& second ) +{ + if ( LessThan( second , first ) ) + { + T temp = first; first = second; second = temp; + return true; + } + + return false; +} + +void c4_SortSeq::MergeSortThis( T* ar, int size , T scratch[] ) +{ + switch( size ) + { + //Handle the special cases for speed: + case 2: + TestSwap( ar[ 0 ] , ar[ 1 ] ); + break; + + case 3: + TestSwap( ar[ 0 ] , ar[ 1 ] ); + if ( TestSwap( ar[ 1 ] , ar[ 2 ] ) ) + TestSwap( ar[ 0 ] , ar[ 1 ] ); + break; + + case 4: + //Gotta optimize this.... + TestSwap( ar[ 0 ] , ar[ 1 ] ); + TestSwap( ar[ 2 ] , ar[ 3 ] ); + TestSwap( ar[ 0 ] , ar[ 2 ] ); + TestSwap( ar[ 1 ] , ar[ 3 ] ); + TestSwap( ar[ 1 ] , ar[ 2 ] ); + break; + + //Gotta do special case for list of five. + + default: + //Subdivide the list, recurse, and merge + { + int s1 = size / 2; + int s2 = size - s1; + T* from1_ = scratch; + T* from2_ = scratch + s1; + MergeSortThis( from1_ , s1 , ar ); + MergeSortThis( from2_ , s2 , ar + s1 ); + + T* to1_ = from1_ + s1; + T* to2_ = from2_ + s2; + + for (;;) + { + if ( LessThan( *from1_, *from2_) ) + { + *ar++ = *from1_++; + + if (from1_ >= to1_) + { + while( from2_ < to2_ ) + *ar++ = *from2_++; + break; + } + } + else + { + *ar++ = *from2_++; + + if (from2_ >= to2_) + { + while( from1_ < to1_ ) + *ar++ = *from1_++; + break; + } + } + } + } + } +} + +void c4_SortSeq::MergeSort( T ar[] , int size ) +{ + if ( size > 1 ) + { + T* scratch = d4_new T [size]; + memcpy(scratch , ar , size * sizeof (T)); + MergeSortThis(ar , size , scratch); + delete [] scratch; + } +} + +c4_SortSeq::c4_SortSeq (c4_Sequence& seq_, c4_Sequence* down_) + : c4_FilterSeq (seq_), _info (0), _width (-1) +{ + d4_assert(NumRows() == seq_.NumRows()); + + if (NumRows() > 0) + { + // down is a vector of flags, true to sort in reverse order + char* down = (char*) _down.SetBufferClear(NumHandlers()); + + // set the down flag for all properties to be sorted in reverse + if (down_) + for (int i = 0; i < NumHandlers(); ++i) + if (down_->PropIndex(NthPropId(i)) >= 0) + down[i] = 1; + + _width = -1; + int n = NumHandlers() + 1; + _info = d4_new c4_SortInfo [n]; + + int j; + + for (j = 0; j < NumHandlers(); ++j) + { + _info[j]._handler = & _seq.NthHandler(j); + _info[j]._context = _seq.HandlerContext(j); + } + + _info[j]._handler = 0; + + // everything is ready, go sort the row index vector + MergeSort((T*) &_rowMap.ElementAt(0), NumRows()); + + delete [] _info; + _info = 0; + + FixupReverseMap(); + } +} + +c4_SortSeq::~c4_SortSeq () +{ + d4_assert(!_info); +} + +int c4_SortSeq::Compare(int index_, c4_Cursor cursor_) const +{ + d4_assert(cursor_._seq != 0); + + const char* down = (const char*) _down.Contents(); + d4_assert(_down.Size() <= NumHandlers()); + + c4_Bytes data; + + for (int colNum = 0; colNum < NumHandlers(); ++colNum) + { + c4_Handler& h = NthHandler(colNum); + const c4_Sequence* hc = HandlerContext(colNum); + + if (!cursor_._seq->Get(cursor_._index, h.PropId(), data)) + h.ClearBytes(data); + + int f = h.Compare(RemapIndex(index_, hc), data); + if (f != 0) + return colNum < _down.Size() && down[colNum] ? -f : +f; + } + + return 0; +} + +int c4_SortSeq::PosInMap(c4_Cursor cursor_) const +{ + int i = 0; + + while (i < NumRows()) + if (Compare(i, cursor_) >= 0) + break; + else + ++i; + + d4_assert(i == NumRows() || Compare(i, cursor_) >= 0); + return i; +} + +c4_Notifier* c4_SortSeq::PreChange(c4_Notifier& /*nf_*/) +{ + if (!GetDependencies()) + return 0; + +#if 0 + c4_Notifier* chg = d4_new c4_Notifier (this); + + switch (nf_._type) + { + case c4_Notifier::kSetAt: + case c4_Notifier::kSet: + { + d4_assert(0); // also needs nested propagation + + /* + change can require a move *and* a change of contents + */ + } + break; + + case c4_Notifier::kInsertAt: + { + d4_assert(0); // this case isn't really difficult + } + break; + + case c4_Notifier::kRemoveAt: + { + d4_assert(0); // nested propagation is too difficult for now + // i.e. can only use sort as last derived view + /* + possible solution: + + if 1 row, simple + else if contig in map, also simple + else propagate reorder first, then delete contig + + it can be done here, as multiple notifications, + by simulating n-1 SetAt's of first row in others + needs some map juggling, allow temp dup entries? + + or perhaps more consistent with n separate removes + */ + } + break; + + case c4_Notifier::kMove: + { + // incorrect: may need to move if recnum matters (recs same) + } + break; + } + + return chg; +#endif + +// d4_assert(0); // fail, cannot handle a view dependent on this one yet + return 0; +} + +void c4_SortSeq::PostChange(c4_Notifier& nf_) +{ + switch (nf_._type) + { + case c4_Notifier::kSet: + if (_seq.PropIndex(nf_._propId) > _width) + break; // cannot affect sort order, valuable optimization + + case c4_Notifier::kSetAt: + { + int oi = (int) _revMap.GetAt(nf_._index); + d4_assert(oi >= 0); + + c4_Cursor cursor (_seq, nf_._index); + + // move the entry if the sort order has been disrupted + if ((oi > 0 && Compare(oi - 1, cursor) > 0) || + (oi+1 < NumRows() && Compare(oi+1, cursor) < 0)) + { + _rowMap.RemoveAt(oi); + _rowMap.InsertAt(PosInMap(cursor), nf_._index); + + FixupReverseMap(); + } + + _width = NumHandlers(); // sorry, no more optimization + } + break; + + case c4_Notifier::kInsertAt: + { + // if cursor was not set, it started out as a single Set + c4_Cursor cursor (_seq, nf_._index); + if (nf_._cursor) + cursor = *nf_._cursor; + + for (int n = 0; n < NumRows(); ++n) + if ((int) _rowMap.GetAt(n) >= nf_._index) + _rowMap.ElementAt(n) += nf_._count; + + int i = PosInMap(cursor); + _rowMap.InsertAt(i, 0, nf_._count); + + for (int j = 0; j < nf_._count; ++j) + _rowMap.SetAt(i++, nf_._index + j); + + FixupReverseMap(); + + _width = NumHandlers(); // sorry, no more optimization + } + break; + + case c4_Notifier::kRemoveAt: + { + int lo = nf_._index; + int hi = nf_._index + nf_._count; + + int j = 0; + for (int i = 0; i < NumRows(); ++i) + { + int n = (int) _rowMap.GetAt(i); + + if (n >= hi) + _rowMap.ElementAt(i) -= nf_._count; + + if (!(lo <= n && n < hi)) + _rowMap.SetAt(j++, _rowMap.GetAt(i)); + } + + d4_assert(j + nf_._count == NumRows()); + _rowMap.SetSize(j); + + FixupReverseMap(); + + _width = NumHandlers(); // sorry, no more optimization + } + break; + + case c4_Notifier::kMove: + { + // incorrect: may need to move if recnum matters (recs same) + } + break; + } +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_ProjectSeq : public c4_DerivedSeq +{ + c4_DWordArray _colMap; // a bit large, but bytes would be too small + bool _frozen; + int _omitCount; // if > 0 then this is a dynamic "project without" + +public: + c4_ProjectSeq (c4_Sequence& seq_, c4_Sequence& in_, bool, c4_Sequence* out_); + virtual ~c4_ProjectSeq (); + + virtual int NumHandlers() const; + virtual c4_Handler& NthHandler(int) const; + virtual const c4_Sequence* HandlerContext(int) const; + virtual int AddHandler(c4_Handler*); + + virtual bool Get(int, int, c4_Bytes&); + virtual void Set(int, const c4_Property&, const c4_Bytes&); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_ProjectSeq::c4_ProjectSeq (c4_Sequence& seq_, c4_Sequence& in_, + bool reorder_, c4_Sequence* out_) + : c4_DerivedSeq (seq_), _frozen (!reorder_ && !out_), _omitCount (0) +{ + // build the array with column indexes + for (int j = 0; j < in_.NumHandlers(); ++j) + { + int propId = in_.NthPropId(j); + int idx = _seq.PropIndex(propId); + + // if the j'th property is in the sequence, add it + if (idx >= 0) + { + // but only if it's not in the out_ view + if (out_ && out_->PropIndex(propId) >= 0) + ++_omitCount; + else + _colMap.Add(idx); + } + } + + // if only reordering, append remaining columns from original view + if (reorder_) + { + for (int i = 0; i < _seq.NumHandlers(); ++i) + { + int propId = _seq.NthPropId(i); + + // only consider properties we did not deal with before + if (in_.PropIndex(propId) < 0) + _colMap.Add(i); + } + + d4_assert(_colMap.GetSize() == _seq.NumHandlers()); + } +} + +c4_ProjectSeq::~c4_ProjectSeq () +{ +} + +int c4_ProjectSeq::NumHandlers() const +{ + return _frozen ? _colMap.GetSize() : _seq.NumHandlers() - _omitCount; +} + +c4_Handler& c4_ProjectSeq::NthHandler(int colNum_) const +{ + int n = colNum_ < _colMap.GetSize() ? _colMap.GetAt(colNum_) : colNum_; + return _seq.NthHandler(n); +} + +const c4_Sequence* c4_ProjectSeq::HandlerContext(int colNum_) const +{ + int n = colNum_ < _colMap.GetSize() ? _colMap.GetAt(colNum_) : colNum_; + return _seq.HandlerContext(n); +} + +int c4_ProjectSeq::AddHandler(c4_Handler* handler_) +{ + int n = _seq.AddHandler(handler_); + return _frozen ? _colMap.Add(n) : n - _omitCount; +} + +bool c4_ProjectSeq::Get(int index_, int propId_, c4_Bytes& buf_) +{ + // fixed in 1.8: check that the property is visible + return PropIndex(propId_) >= 0 && _seq.Get(index_, propId_, buf_); +} + +void c4_ProjectSeq::Set(int index_, const c4_Property& prop_, const c4_Bytes& bytes_) +{ + int n = _seq.NumHandlers(); + _seq.Set(index_, prop_, bytes_); + + // if the number of handlers changed, then one must have been added + if (n != _seq.NumHandlers()) + { + d4_assert(n == _seq.NumHandlers() - 1); + + if (_frozen) + _colMap.Add(n); + } +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Sequence* f4_CreateFilter(c4_Sequence& seq_, c4_Cursor l_, c4_Cursor h_) +{ + return d4_new c4_FilterSeq (seq_, l_, h_); +} + +c4_Sequence* f4_CreateSort(c4_Sequence& seq_, c4_Sequence* down_) +{ + return d4_new c4_SortSeq (seq_, down_); +} + +c4_Sequence* f4_CreateProject(c4_Sequence& seq_, c4_Sequence& in_, + bool reorder_, c4_Sequence* out_) +{ + return d4_new c4_ProjectSeq (seq_, in_, reorder_, out_); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/derived.h b/akregator/src/mk4storage/metakit/src/derived.h new file mode 100644 index 00000000..8bd934fc --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/derived.h @@ -0,0 +1,25 @@ +// derived.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Encapsulation of derived view classes + */ + +#ifndef __DERIVED_H__ +#define __DERIVED_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_Cursor; // not defined here + class c4_Sequence; // not defined here + + extern c4_Sequence* f4_CreateFilter(c4_Sequence&, c4_Cursor, c4_Cursor); + extern c4_Sequence* f4_CreateSort(c4_Sequence&, c4_Sequence* =0); + extern c4_Sequence* f4_CreateProject(c4_Sequence&, c4_Sequence&, + bool, c4_Sequence* =0); + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/field.cpp b/akregator/src/mk4storage/metakit/src/field.cpp new file mode 100644 index 00000000..3867d6b5 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/field.cpp @@ -0,0 +1,119 @@ +// field.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Implementation of the field structure tree + */ + +#include "header.h" +#include "field.h" + +#include <stdlib.h> // strtol + +#if !q4_INLINE +#include "field.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// +// Implemented in this file + + class c4_Field; + +///////////////////////////////////////////////////////////////////////////// +// c4_Field + +c4_Field::c4_Field (const char*& description_, c4_Field* parent_) + : _type (0) +{ + _indirect = this; + + size_t n = strcspn(description_, ",[]"); + const char* p = strchr(description_, ':'); + + if (p != 0 && p < description_ + n) { + _name = c4_String (description_, p - description_); + _type = p[1] & ~0x20; // force to upper case + } else { + _name = c4_String (description_, n); + _type = 'S'; + } + + description_ += n; + + if (*description_ == '[') { + ++description_; + _type = 'V'; + + if (*description_ == '^') { + ++description_; + _indirect = parent_; + d4_assert(*description_ == ']'); + } + + if (*description_ == ']') + ++description_; + else + do { + // 2004-01-20 ignore duplicate property names + // (since there is no good way to report errors at this point) + c4_Field* sf = d4_new c4_Field (description_, this); + for (int i = 0; i < NumSubFields(); ++i) + if (SubField(i).Name().CompareNoCase(sf->Name()) == 0) { + delete sf; + sf = 0; + break; + } + if (sf != 0) + _subFields.Add(sf); + } while (*description_++ == ','); + } +} + +c4_Field::~c4_Field () +{ + if (_indirect == this) { + //better? for (int i = NumSubFields(); --i >= 0 ;) + for (int i = 0; i < NumSubFields(); ++i) { + c4_Field* sf = & SubField(i); + if (sf != this) // careful with recursive subfields + delete sf; + } + } +} + +c4_String c4_Field::Description(bool anonymous_) const +{ + c4_String s = anonymous_ ? "?" : (const char*) Name(); + + if (Type() == 'V') + s += "[" + DescribeSubFields(anonymous_) + "]"; + else { + s += ":"; + s += (c4_String) Type(); + } + + return s; +} + +c4_String c4_Field::DescribeSubFields(bool) const +{ + d4_assert(Type() == 'V'); + + if (_indirect != this) + return "^"; + + c4_String s; + char c = 0; + + for (int i = 0; i < NumSubFields(); ++i) { + if (c != 0) + s += (c4_String) c; + s += SubField(i).Description(); + c = ','; + } + + return s; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/field.h b/akregator/src/mk4storage/metakit/src/field.h new file mode 100644 index 00000000..5dfc2573 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/field.h @@ -0,0 +1,64 @@ +// field.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Core class to represent fields + */ + +#ifndef __FIELD_H__ +#define __FIELD_H__ + +#ifndef __K4CONF_H__ +#error Please include "k4conf.h" before this header file +#endif + +///////////////////////////////////////////////////////////////////////////// + +class c4_Field +{ + c4_PtrArray _subFields; + c4_String _name; + char _type; + c4_Field* _indirect; + +public: +/* Construction / destruction */ + c4_Field (const char*&, c4_Field* =0); + //: Constructs a new field. + ~c4_Field (); + +/* Repeating and compound fields */ + int NumSubFields() const; + //: Returns the number of subfields. + c4_Field& SubField(int) const; + //: Returns the description of each subfield. + bool IsRepeating() const; + //: Returns true if this field contains subtables. + +/* Field name and description */ + const c4_String& Name() const; + //: Returns name of this field. + char Type() const; + //: Returns the type description of this field, if any. + char OrigType() const; + //: Similar, but report types which were originall 'M' as well. + c4_String Description(bool anonymous_ =false) const; + //: Describes the structure, omit names if anonymous. + c4_String DescribeSubFields(bool anonymous_ =false) const; + //: Describes just the subfields, omit names if anonymous. + +private: + c4_Field (const c4_Field&); // not implemented + void operator= (const c4_Field&); // not implemented +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_INLINE +#include "field.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/field.inl b/akregator/src/mk4storage/metakit/src/field.inl new file mode 100644 index 00000000..823e626e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/field.inl @@ -0,0 +1,37 @@ +// field.inl -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Inlined members of the field class + */ + +d4_inline bool c4_Field::IsRepeating() const +{ + return _type == 'V'; +} + +d4_inline int c4_Field::NumSubFields() const +{ + return _indirect->_subFields.GetSize(); +} + +d4_inline c4_Field& c4_Field::SubField(int index_) const +{ + return *(c4_Field*) _indirect->_subFields.GetAt(index_); +} + +d4_inline const c4_String& c4_Field::Name() const +{ + return _name; +} + +d4_inline char c4_Field::OrigType() const +{ + return _type; +} + +d4_inline char c4_Field::Type() const +{ + return _type == 'M' ? 'B' : _type; +} diff --git a/akregator/src/mk4storage/metakit/src/fileio.cpp b/akregator/src/mk4storage/metakit/src/fileio.cpp new file mode 100644 index 00000000..28ff7dca --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/fileio.cpp @@ -0,0 +1,434 @@ +// fileio.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Implementation of c4_FileStrategy and c4_FileStream + */ + +#include "header.h" +#include "mk4io.h" + +#if q4_WIN32 +#if q4_MSVC && !q4_STRICT +#pragma warning(disable: 4201) // nonstandard extension used : ... +#endif +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#if !defined (q4_WINCE) +#include <io.h> +#include <fcntl.h> +#include <sys/stat.h> +#endif +#endif + +#if q4_UNIX && HAVE_MMAP +#include <sys/types.h> +#include <sys/mman.h> +#endif + +#if q4_UNIX +#include <unistd.h> +#include <fcntl.h> +#endif + +#if q4_WINCE +#define _get_osfhandle(x) x +#endif + +#ifndef _O_NOINHERIT +#define _O_NOINHERIT 0 +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// The "Carbon" version of a build on Macintosh supports running under +// either MacOS 7..9 (which has no mmap), or MacOS X (which has mmap). +// The logic below was adapted from a contribution by Paul Snively, it +// decides at run time which case it is, and switches I/O calls to match. + +#if defined (q4_CARBON) && q4_CARBON +//#if q4_MAC && !defined (__MACH__) && (!q4_MWCW || __MWERKS__ >= 0x3000) +#undef HAVE_MMAP +#define HAVE_MMAP 1 + +#include <CFBundle.h> +#include <Folders.h> + +#define PROT_NONE 0x00 +#define PROT_READ 0x01 +#define PROT_WRITE 0x02 +#define PROT_EXEC 0x04 + +#define MAP_SHARED 0x0001 +#define MAP_PRIVATE 0x0002 + +#define MAP_FIXED 0x0010 +#define MAP_RENAME 0x0020 +#define MAP_NORESERVE 0x0040 +#define MAP_INHERIT 0x0080 +#define MAP_NOEXTEND 0x0100 +#define MAP_HASSEMAPHORE 0x0200 + +typedef unsigned long t4_u32; + +static t4_u32 sfwRefCount = 0; +static CFBundleRef systemFramework = NULL; + +static char* fake_mmap(char*, t4_u32, int, int, int, long long) + { return (char*) -1L; } +static int fake_munmap(char*, t4_u32) + { return 0; } + +static FILE* (*my_fopen)(const char*,const char*) = fopen; +static int (*my_fclose)(FILE*) = fclose; +static long (*my_ftell)(FILE*) = ftell; +static int (*my_fseek)(FILE*,long,int) = fseek; +static t4_u32 (*my_fread)(void* ptr,t4_u32,t4_u32,FILE*) = fread; +static t4_u32 (*my_fwrite)(const void* ptr,t4_u32,t4_u32,FILE*) = fwrite; +static int (*my_ferror)(FILE*) = ferror; +static int (*my_fflush)(FILE*) = fflush; +static int (*my_fileno)(FILE*) = fileno; +static char* (*my_mmap)(char*,t4_u32,int,int,int,long long) = fake_mmap; +static int (*my_munmap)(char*,t4_u32) = fake_munmap; + +static void InitializeIO() +{ + if (sfwRefCount++) return; // race condition, infinitesimal risk + + FSRef theRef; + if (FSFindFolder(kOnAppropriateDisk, kFrameworksFolderType, + false, &theRef) == noErr) { + CFURLRef fw = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &theRef); + if (fw) { + CFURLRef bd = + CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, + fw, CFSTR("System.framework"), false); + CFRelease(fw); + if (bd) { + systemFramework = CFBundleCreate(kCFAllocatorSystemDefault, bd); + CFRelease(bd); + } + } + if (!systemFramework || !CFBundleLoadExecutable(systemFramework)) + return; +#define F(x) CFBundleGetFunctionPointerForName(systemFramework, CFSTR(#x)) + my_fopen = (FILE* (*)(const char*,const char*)) F(fopen); + my_fclose = (int (*)(FILE*)) F(fclose); + my_ftell = (long (*)(FILE*)) F(ftell); + my_fseek = (int (*)(FILE*,long,int)) F(fseek); + my_fread = (t4_u32 (*)(void* ptr,t4_u32,t4_u32,FILE*)) F(fread); + my_fwrite = (t4_u32 (*)(const void* ptr,t4_u32,t4_u32,FILE*)) F(fwrite); + my_ferror = (int (*)(FILE*)) F(ferror); + my_fflush = (int (*)(FILE*)) F(fflush); + my_fileno = (int (*)(FILE*)) F(fileno); + my_mmap = (char* (*)(char*,t4_u32,int,int,int,long long)) F(mmap); + my_munmap = (int (*)(char*,t4_u32)) F(munmap); +#undef F + d4_assert(my_fopen && my_fclose && my_ftell && my_fseek && + my_fread && my_fwrite && my_ferror && my_fflush && + my_fileno && my_mmap && my_munmap); + } +} + +static void FinalizeIO() +{ + if (--sfwRefCount) return; // race condition, infinitesimal risk + + if (systemFramework) { + CFBundleUnloadExecutable(systemFramework); + CFRelease(systemFramework); + systemFramework = 0; + } +} + +#define fopen my_fopen +#define fclose my_fclose +#define ftell my_ftell +#define fseek my_fseek +#define fread my_fread +#define fwrite my_fwrite +#define ferror my_ferror +#define fflush my_fflush +#define fileno my_fileno +#define mmap my_mmap +#define munmap my_munmap + +#else + +#define InitializeIO() +#define FinalizeIO() + +#endif + +///////////////////////////////////////////////////////////////////////////// + +#if q4_CHECK +#include <stdlib.h> + +void f4_AssertionFailed(const char* cond_, const char* file_, int line_) +{ + fprintf(stderr, "Assertion failed: %s (file %s, line %d)\n", + cond_, file_, line_); + abort(); +} + +#endif //q4_CHECK + +///////////////////////////////////////////////////////////////////////////// +// c4_FileStream + +c4_FileStream::c4_FileStream (FILE* stream_, bool owned_) + : _stream (stream_), _owned (owned_) +{ +} + +c4_FileStream::~c4_FileStream () +{ + if (_owned) + fclose(_stream); +} + +int c4_FileStream::Read(void* buffer_, int length_) +{ + d4_assert(_stream != 0); + + return (int) fread(buffer_, 1, length_, _stream); +} + +bool c4_FileStream::Write(const void* buffer_, int length_) +{ + d4_assert(_stream != 0); + + return (int) fwrite(buffer_, 1, length_, _stream) == length_; +} + +///////////////////////////////////////////////////////////////////////////// +// c4_FileStrategy + +c4_FileStrategy::c4_FileStrategy (FILE* file_) + : _file (file_), _cleanup (0) +{ + InitializeIO(); + ResetFileMapping(); +} + +c4_FileStrategy::~c4_FileStrategy () +{ + _file = 0; + ResetFileMapping(); + + if (_cleanup) + fclose(_cleanup); + + d4_assert(_mapStart == 0); + FinalizeIO(); +} + +bool c4_FileStrategy::IsValid() const +{ + return _file != 0; +} + +t4_i32 c4_FileStrategy::FileSize() +{ + d4_assert(_file != 0); + + long size = -1; + + long old = ftell(_file); + if (old >= 0 && fseek(_file, 0, 2) == 0) { + long pos = ftell(_file); + if (fseek(_file, old, 0) == 0) + size = pos; + } + + if (size < 0) + _failure = ferror(_file); + + return size; +} + +t4_i32 c4_FileStrategy::FreshGeneration() +{ + d4_assert(false); + return 0; +} + +void c4_FileStrategy::ResetFileMapping() +{ +#if q4_WIN32 + if (_mapStart != 0) { + _mapStart -= _baseOffset; + d4_dbgdef(BOOL g =) + ::UnmapViewOfFile((char*) _mapStart); + d4_assert(g); + _mapStart = 0; + _dataSize = 0; + } + + if (_file != 0) { + t4_i32 len = FileSize(); + + if (len > 0) { + FlushFileBuffers((HANDLE) _get_osfhandle(_fileno(_file))); + HANDLE h = ::CreateFileMapping((HANDLE) _get_osfhandle(_fileno(_file)), + 0, PAGE_READONLY, 0, len, 0); + d4_assert(h); // check for errors, but can continue without mapping + + if (h) { + _mapStart = (t4_byte*) ::MapViewOfFile(h, FILE_MAP_READ, 0, 0, len); + d4_assert(_mapStart != 0); + + if (_mapStart != 0) { + _mapStart += _baseOffset; + _dataSize = len - _baseOffset; + } + + d4_dbgdef(BOOL f =) + ::CloseHandle(h); + d4_assert(f); + } + } + } +#elif HAVE_MMAP + if (_mapStart != 0) { + _mapStart -= _baseOffset; + munmap((char*) _mapStart, _baseOffset + _dataSize); // also loses const + _mapStart = 0; + _dataSize = 0; + } + + if (_file != 0) { + t4_i32 len = FileSize(); + + if (len > 0) { + _mapStart = (const t4_byte*) mmap(0, len, PROT_READ, MAP_SHARED, + fileno(_file), 0); + if (_mapStart != (void*) -1L) { + _mapStart += _baseOffset; + _dataSize = len - _baseOffset; + } else + _mapStart = 0; + } + } +#endif +} + +bool c4_FileStrategy::DataOpen(const char* fname_, int mode_) +{ + d4_assert(!_file); + +#if q4_WIN32 && !q4_BORC && !q4_WINCE + int flags = _O_BINARY | _O_NOINHERIT | (mode_ > 0 ? _O_RDWR : _O_RDONLY); + int fd = _open(fname_, flags); + if (fd != -1) + _cleanup = _file = _fdopen(fd, mode_ > 0 ? "r+b" : "rb"); +#else + _cleanup = _file = fopen(fname_, mode_ > 0 ? "r+b" : "rb"); +#if q4_UNIX + if (_file != 0) + fcntl(fileno(_file), F_SETFD, FD_CLOEXEC); +#endif //q4_UNIX +#endif //q4_WIN32 && !q4_BORC && !q4_WINCE + + if (_file != 0) { + ResetFileMapping(); + return true; + } + + if (mode_ > 0) { +#if q4_WIN32 && !q4_BORC && !q4_WINCE + fd = _open(fname_, flags | _O_CREAT, _S_IREAD | _S_IWRITE); + if (fd != -1) + _cleanup = _file = _fdopen(fd, "w+b"); +#else + _cleanup = _file = fopen(fname_, "w+b"); +#if q4_UNIX + if (_file != 0) + fcntl(fileno(_file), F_SETFD, FD_CLOEXEC); +#endif //q4_UNIX +#endif //q4_WIN32 && !q4_BORC && !q4_WINCE + } + + //d4_assert(_file != 0); + return false; +} + +int c4_FileStrategy::DataRead(t4_i32 pos_, void* buf_, int len_) +{ + d4_assert(_baseOffset + pos_ >= 0); + d4_assert(_file != 0); + + //printf("DataRead at %d len %d\n", pos_, len_); + return fseek(_file, _baseOffset + pos_, 0) != 0 ? -1 : + (int) fread(buf_, 1, len_, _file); +} + +void c4_FileStrategy::DataWrite(t4_i32 pos_, const void* buf_, int len_) +{ + d4_assert(_baseOffset + pos_ >= 0); + d4_assert(_file != 0); +#if 0 + if (_mapStart <= buf_ && buf_ < _mapStart + _dataSize) { + printf("DataWrite %08x at %d len %d (map %d)\n", buf_, pos_, len_, + (const t4_byte*) buf_ - _mapStart + _baseOffset); + } else { + printf("DataWrite %08x at %d len %d\n", buf_, pos_, len_); + } + fprintf(stderr, " _mapStart %08x _dataSize %d buf_ %08x len_ %d _baseOffset %d\n", + _mapStart, _dataSize, buf_, len_, _baseOffset); + printf(" _mapStart %08x _dataSize %d buf_ %08x len_ %d _baseOffset %d\n", + _mapStart, _dataSize, buf_, len_, _baseOffset); + fflush(stdout); +#endif + +#if q4_WIN32 || __hpux || __MACH__ +// if (buf_ >= _mapStart && buf_ <= _mapLimit - len_) + + // a horrendous hack to allow file mapping for Win95 on network drive + // must use a temp buf to avoid write from mapped file to same file + // + // 6-Feb-1999 -- this workaround is not thread safe + // 30-Nov-2001 -- changed to use the stack so now it is + // 28-Oct-2002 -- added HP/UX to the mix, to avoid hard lockup + char tempBuf [4096]; + d4_assert(len_ <= sizeof tempBuf); + buf_ = memcpy(tempBuf, buf_, len_); +#endif + + if (fseek(_file, _baseOffset + pos_, 0) != 0 || + (int) fwrite(buf_, 1, len_, _file) != len_) { + _failure = ferror(_file); + d4_assert(_failure != 0); + d4_assert(true); // always force an assertion failure in debug mode + } +} + +void c4_FileStrategy::DataCommit(t4_i32 limit_) +{ + d4_assert(_file != 0); + + if (fflush(_file) < 0) { + _failure = ferror(_file); + d4_assert(_failure != 0); + d4_assert(true); // always force an assertion failure in debug mode + return; + } + + if (limit_ > 0) { +#if 0 // can't truncate file in a portable way! + // unmap the file first, WinNT is more picky about this than Win95 + FILE* save = _file; + + _file = 0; + ResetFileMapping(); + _file = save; + + _file->SetLength(limit_); // now we can resize the file +#endif + ResetFileMapping(); // remap, since file length may have changed + } +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/format.cpp b/akregator/src/mk4storage/metakit/src/format.cpp new file mode 100644 index 00000000..aa23e739 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/format.cpp @@ -0,0 +1,1341 @@ +// format.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Format handlers deal with the representation of data + */ + +#include "header.h" +#include "handler.h" +#include "column.h" +#include "format.h" +#include "persist.h" + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatHandler : public c4_Handler +{ + c4_HandlerSeq& _owner; + +public: + c4_FormatHandler (const c4_Property& prop_, c4_HandlerSeq& owner_); + virtual ~c4_FormatHandler (); + + virtual bool IsPersistent() const; + +protected: + c4_HandlerSeq& Owner() const; +}; + +///////////////////////////////////////////////////////////////////////////// +// c4_FormatHandler + +c4_FormatHandler::c4_FormatHandler (const c4_Property& prop_, c4_HandlerSeq& owner_) + : c4_Handler (prop_), _owner (owner_) +{ +} + +c4_FormatHandler::~c4_FormatHandler () +{ +} + +d4_inline c4_HandlerSeq& c4_FormatHandler::Owner() const +{ + return _owner; +} + +bool c4_FormatHandler::IsPersistent() const +{ + return _owner.Persist() != 0; +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatX : public c4_FormatHandler +{ +public: + c4_FormatX (const c4_Property& prop_, c4_HandlerSeq& seq_, + int width_ =sizeof (t4_i32)); + + virtual void Define(int, const t4_byte**); + virtual void OldDefine(char type_, c4_Persist&); + virtual void FlipBytes(); + + virtual int ItemSize(int index_); + virtual const void* Get(int index_, int& length_); + virtual void Set(int index_, const c4_Bytes& buf_); + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_); + virtual void Remove(int index_, int count_); + + virtual void Commit(c4_SaveContext& ar_); + + virtual void Unmapped(); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); + +protected: + c4_ColOfInts _data; +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatX::c4_FormatX (const c4_Property& p_, c4_HandlerSeq& s_, int w_) + : c4_FormatHandler (p_, s_), _data (s_.Persist(), w_) +{ +} + +int c4_FormatX::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + return c4_ColOfInts::DoCompare(b1_, b2_); +} + +void c4_FormatX::Commit(c4_SaveContext& ar_) +{ + _data.FixSize(true); + ar_.CommitColumn(_data); + //_data.FixSize(false); +} + +void c4_FormatX::Define(int rows_, const t4_byte** ptr_) +{ + if (ptr_ != 0) + _data.PullLocation(*ptr_); + + _data.SetRowCount(rows_); +} + +void c4_FormatX::OldDefine(char, c4_Persist& pers_) +{ + pers_.FetchOldLocation(_data); + _data.SetRowCount(Owner().NumRows()); +} + +void c4_FormatX::FlipBytes() +{ + _data.FlipBytes(); +} + +int c4_FormatX::ItemSize(int index_) +{ + return _data.ItemSize(index_); +} + +const void* c4_FormatX::Get(int index_, int& length_) +{ + return _data.Get(index_, length_); +} + +void c4_FormatX::Set(int index_, const c4_Bytes& buf_) +{ + _data.Set(index_, buf_); +} + +void c4_FormatX::Insert(int index_, const c4_Bytes& buf_, int count_) +{ + _data.Insert(index_, buf_, count_); +} + +void c4_FormatX::Remove(int index_, int count_) +{ + _data.Remove(index_, count_); +} + +void c4_FormatX::Unmapped() +{ + _data.ReleaseAllSegments(); +} + +///////////////////////////////////////////////////////////////////////////// +#if !q4_TINY +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatL : public c4_FormatX +{ +public: + c4_FormatL (const c4_Property& prop_, c4_HandlerSeq& seq_); + + virtual void Define(int, const t4_byte**); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatL::c4_FormatL (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatX (prop_, seq_, sizeof (t4_i64)) +{ + // force maximum size, autosizing more than 32 bits won't work + _data.SetAccessWidth(8 * sizeof (t4_i64)); +} + +int c4_FormatL::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + d4_assert(b1_.Size() == sizeof (t4_i64)); + d4_assert(b2_.Size() == sizeof (t4_i64)); + + t4_i64 v1 = *(const t4_i64*) b1_.Contents(); + t4_i64 v2 = *(const t4_i64*) b2_.Contents(); + + return v1 == v2 ? 0 : v1 < v2 ? -1 : +1; +} + +void c4_FormatL::Define(int rows_, const t4_byte** ptr_) +{ + if (ptr_ == 0 && rows_ > 0) { + d4_assert(_data.ColSize() == 0); + _data.InsertData(0, rows_ * sizeof (t4_i64), true); + } + + c4_FormatX::Define(rows_, ptr_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatF : public c4_FormatX +{ +public: + c4_FormatF (const c4_Property& prop_, c4_HandlerSeq& seq_); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatF::c4_FormatF (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatX (prop_, seq_, sizeof (float)) +{ +} + +int c4_FormatF::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + d4_assert(b1_.Size() == sizeof (float)); + d4_assert(b2_.Size() == sizeof (float)); + + float v1 = *(const float*) b1_.Contents(); + float v2 = *(const float*) b2_.Contents(); + + return v1 == v2 ? 0 : v1 < v2 ? -1 : +1; +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatD : public c4_FormatX +{ +public: + c4_FormatD (const c4_Property& prop_, c4_HandlerSeq& seq_); + + virtual void Define(int, const t4_byte**); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatD::c4_FormatD (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatX (prop_, seq_, sizeof (double)) +{ + // force maximum size, autosizing more than 32 bits won't work + _data.SetAccessWidth(8 * sizeof (double)); +} + +int c4_FormatD::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + d4_assert(b1_.Size() == sizeof (double)); + d4_assert(b2_.Size() == sizeof (double)); + + double v1 = *(const double*) b1_.Contents(); + double v2 = *(const double*) b2_.Contents(); + + return v1 == v2 ? 0 : v1 < v2 ? -1 : +1; +} + +void c4_FormatD::Define(int rows_, const t4_byte** ptr_) +{ + if (ptr_ == 0 && rows_ > 0) { + d4_assert(_data.ColSize() == 0); + _data.InsertData(0, rows_ * sizeof (double), true); + } + + c4_FormatX::Define(rows_, ptr_); +} + +///////////////////////////////////////////////////////////////////////////// +#endif // !q4_TINY +///////////////////////////////////////////////////////////////////////////// + +/* + Byte properties are used for raw bytes and for indirect (memo) data. + + There are two columns: the actual data and the item sizes. If the data + is indirect, then the size is stored as a negative value. + + In addition, there is an in-memory-only vector of columns (_memos). + Columns are created when asked for, and stay around until released with + a commit call. If the column object exists and is not dirty, then it + is either a real column (size < 0), or simply a duplicate of the data + stored inline as bytes. +*/ + +class c4_FormatB : public c4_FormatHandler +{ +public: + c4_FormatB (const c4_Property& prop_, c4_HandlerSeq& seq_); + virtual ~c4_FormatB (); + + virtual void Define(int, const t4_byte**); + virtual void OldDefine(char type_, c4_Persist&); + virtual void Commit(c4_SaveContext& ar_); + + virtual int ItemSize(int index_); + virtual const void* Get(int index_, int& length_); + virtual void Set(int index_, const c4_Bytes& buf_); + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_); + virtual void Remove(int index_, int count_); + + virtual c4_Column* GetNthMemoCol(int index_, bool alloc_); + + virtual void Unmapped(); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); + +protected: + const void* GetOne(int index_, int& length_); + void SetOne(int index_, const c4_Bytes& buf_, bool ignoreMemos_ =false); + +private: + t4_i32 Offset(int index_) const; + bool ShouldBeMemo(int length_) const; + int ItemLenOffCol(int index_, t4_i32& off_, c4_Column*& col_); + bool CommitItem(c4_SaveContext& ar_, int index_); + void InitOffsets(c4_ColOfInts& sizes_); + + c4_Column _data; + c4_ColOfInts _sizeCol; // 2001-11-27: keep, to track position on disk + c4_Column _memoCol; // 2001-11-27: keep, to track position on disk + c4_DWordArray _offsets; + c4_PtrArray _memos; + bool _recalc; // 2001-11-27: remember when to redo _{size,memo}Col +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatB::c4_FormatB (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatHandler (prop_, seq_), _data (seq_.Persist()), + _sizeCol (seq_.Persist()), _memoCol (seq_.Persist()), _recalc (false) +{ + _offsets.SetSize(1, 100); + _offsets.SetAt(0, 0); +} + +c4_FormatB::~c4_FormatB () +{ + // cleanup allocated columns + //better? for (int i = _memos.GetSize(); --i >= 0 ;) + for (int i = 0; i < _memos.GetSize(); ++i) + delete (c4_Column*) _memos.GetAt(i); +} + +d4_inline t4_i32 c4_FormatB::Offset(int index_) const +{ + d4_assert((t4_i32) _offsets.GetAt(_offsets.GetSize() - 1) == _data.ColSize()); + d4_assert(_offsets.GetSize() == _memos.GetSize() + 1); + d4_assert(index_ < _offsets.GetSize()); + + // extend offset vectors for missing empty entries at end + int n = _offsets.GetSize(); + d4_assert(n > 0); + + if (index_ >= n) + index_ = n - 1; + + return _offsets.GetAt(index_); +} + +d4_inline bool c4_FormatB::ShouldBeMemo(int length_) const +{ + // items over 10000 bytes are always memos + // items up to 100 bytes are never memos + // + // else, memo only if the column would be under 1 Mb + // (assuming all items had the same size as this one) + // + // the effect is that as the number of rows increases, + // smaller and smaller items get turned into memos + // + // note that items which are no memo right now stay + // as is, and so do memos which have not been modified + + int rows = _memos.GetSize() + 1; // avoids divide by zero + return length_ > 10000 || length_ > 100 && length_ > 1000000 / rows; +} + +int c4_FormatB::ItemLenOffCol(int index_, t4_i32& off_, c4_Column*& col_) +{ + col_ = (c4_Column*) _memos.GetAt(index_); + if (col_ != 0) { + off_ = 0; + return col_->ColSize(); + } + + col_ = &_data; + off_ = Offset(index_); + return Offset(index_ + 1) - off_; +} + +c4_Column* c4_FormatB::GetNthMemoCol(int index_, bool alloc_) +{ + t4_i32 start; + c4_Column* col; + int n = ItemLenOffCol(index_, start, col); + + if (col == &_data && alloc_) { + col = d4_new c4_Column (_data.Persist()); + _memos.SetAt(index_, col); + + if (n > 0) + if (_data.IsDirty()) { + c4_Bytes temp; + _data.FetchBytes(start, n, temp, true); + col->SetBuffer(n); + col->StoreBytes(0, temp); + } + else + col->SetLocation(_data.Position() + start, n); + } + + return col; +} + +void c4_FormatB::Unmapped() +{ + _data.ReleaseAllSegments(); + _sizeCol.ReleaseAllSegments(); + _memoCol.ReleaseAllSegments(); + + for (int i = 0; i < _memos.GetSize(); ++i) { + c4_Column* cp = (c4_Column*) _memos.GetAt(i); + if (cp != 0) + cp->ReleaseAllSegments(); + } +} + +void c4_FormatB::Define(int, const t4_byte** ptr_) +{ + d4_assert(_memos.GetSize() == 0); + + if (ptr_ != 0) { + _data.PullLocation(*ptr_); + if (_data.ColSize() > 0) + _sizeCol.PullLocation(*ptr_); + _memoCol.PullLocation(*ptr_); + } + + // everything below this point could be delayed until use + // in that case, watch out that column space use is properly tracked + + InitOffsets(_sizeCol); + + if (_memoCol.ColSize() > 0) { + c4_Bytes walk; + _memoCol.FetchBytes(0, _memoCol.ColSize(), walk, true); + + const t4_byte* p = walk.Contents(); + + for (int row = 0; p < walk.Contents() + walk.Size(); ++row) { + row += c4_Column::PullValue(p); + d4_assert(row < _memos.GetSize()); + + c4_Column* mc = d4_new c4_Column (_data.Persist()); + d4_assert(mc != 0); + _memos.SetAt(row, mc); + + mc->PullLocation(p); + } + + d4_assert(p == walk.Contents() + walk.Size()); + } +} + +void c4_FormatB::OldDefine(char type_, c4_Persist& pers_) +{ + int rows = Owner().NumRows(); + + c4_ColOfInts sizes (_data.Persist()); + + if (type_ == 'M') { + InitOffsets(sizes); + + c4_ColOfInts szVec (_data.Persist()); + pers_.FetchOldLocation(szVec); + szVec.SetRowCount(rows); + + c4_ColOfInts posVec (_data.Persist()); + pers_.FetchOldLocation(posVec); + posVec.SetRowCount(rows); + + for (int r = 0; r < rows; ++r) { + t4_i32 sz = szVec.GetInt(r); + if (sz > 0) { + c4_Column* mc = d4_new c4_Column (_data.Persist()); + d4_assert(mc != 0); + _memos.SetAt(r, mc); + + mc->SetLocation(posVec.GetInt(r), sz); + } + } + } else { + pers_.FetchOldLocation(_data); + + if (type_ == 'B') { + pers_.FetchOldLocation(sizes); + +#if !q4_OLD_IS_ALWAYS_V2 + + // WARNING - HUGE HACK AHEAD - THIS IS NOT 100% FULLPROOF! + // + // The above is correct for MK versions 2.0 and up, but *NOT* + // for MK 1.8.6 datafiles, which store sizes first (OUCH!!!). + // This means that there is not a 100% safe way to auto-convert + // both 1.8.6 and 2.0 files - since there is no way to detect + // unambiguously which version a datafile is. All we can do, + // is to carefully check both vectors, and *hope* that only one + // of them is valid as sizes vector. This problem applies to + // the 'B' (bytes) property type only, and only pre 2.0 files. + // + // To build a version which *always* converts assuming 1.8.6, + // add flag "-Dq4_OLD_IS_PRE_V2" to the compiler command line. + // Conversely, "-Dq4_OLD_IS_ALWAYS_V2" forces 2.0 conversion. + + if (rows > 0) { + t4_i32 s1 = sizes.ColSize(); + t4_i32 s2 = _data.ColSize(); + +#if !q4_OLD_IS_PRE_V2 + // if the size vector is clearly impossible, swap vectors + bool fix = c4_ColOfInts::CalcAccessWidth(rows, s1) < 0; + + // if the other vector might be valid as well, check further + if (!fix && c4_ColOfInts::CalcAccessWidth(rows, s2) >= 0) { + sizes.SetRowCount(rows); + t4_i32 total = 0; + for (int i = 0; i < rows; ++i) { + t4_i32 w = sizes.GetInt(i); + if (w < 0 || total > s2) { + total = -1; + break; + } + total += w; + } + + // if the sizes don't add up, swap vectors + fix = total != s2; + } + + if (fix) +#endif + { + t4_i32 p1 = sizes.Position(); + t4_i32 p2 = _data.Position(); + _data.SetLocation(p1, s1); + sizes.SetLocation(p2, s2); + } + } +#endif + InitOffsets(sizes); + } else { + d4_assert(type_ == 'S'); + + sizes.SetRowCount(rows); + + t4_i32 pos = 0; + t4_i32 lastEnd = 0; + int k = 0; + + c4_ColIter iter (_data, 0, _data.ColSize()); + while (iter.Next()) { + const t4_byte* p = iter.BufLoad(); + for (int j = 0; j < iter.BufLen(); ++j) + if (!p[j]) { + sizes.SetInt(k++, pos + j + 1 - lastEnd); + lastEnd = pos + j + 1; + } + + pos += iter.BufLen(); + } + + d4_assert(pos == _data.ColSize()); + + if (lastEnd < pos) { // last entry had no zero byte + _data.InsertData(pos++, 1, true); + sizes.SetInt(k, pos - lastEnd); + } + + InitOffsets(sizes); + + // get rid of entries with just a null byte + for (int r = 0; r < rows; ++r) + if (c4_FormatB::ItemSize(r) == 1) + SetOne(r, c4_Bytes ()); + } + } +} + +void c4_FormatB::InitOffsets(c4_ColOfInts& sizes_) +{ + int rows = Owner().NumRows(); + + if (sizes_.RowCount() != rows) { + sizes_.SetRowCount(rows); + } + + _memos.SetSize(rows); + _offsets.SetSize(rows + 1); + + if (_data.ColSize() > 0) { + t4_i32 total = 0; + + for (int r = 0; r < rows; ++r) { + int n = sizes_.GetInt(r); + d4_assert(n >= 0); + total += n; + _offsets.SetAt(r + 1, total); + } + + d4_assert(total == _data.ColSize()); + } + +} + +int c4_FormatB::ItemSize(int index_) +{ + t4_i32 start; + c4_Column* col; + return ItemLenOffCol(index_, start, col); +} + +const void* c4_FormatB::GetOne(int index_, int& length_) +{ + t4_i32 start; + c4_Column* cp; + length_ = ItemLenOffCol(index_, start, cp); + d4_assert(length_ >= 0); + + if (length_ == 0) + return ""; + + return cp->FetchBytes(start, length_, Owner().Buffer(), false); +} + +const void* c4_FormatB::Get(int index_, int& length_) +{ + return GetOne(index_, length_); +} + +void c4_FormatB::SetOne(int index_, const c4_Bytes& xbuf_, bool ignoreMemos_) +{ + // this fixes bug in 2.4.0 when copying string from higher row + // TODO: this fix is very conservative, figure out when to copy + // (can probably look at pointer to see whether it's from us) + int sz = xbuf_.Size(); + c4_Bytes buf_ (xbuf_.Contents(), sz, 0 < sz && sz <= c4_Column::kSegMax); + + c4_Column* cp = &_data; + t4_i32 start = Offset(index_); + int len = Offset(index_ + 1) - start; + + if (!ignoreMemos_ && _memos.GetAt(index_) != 0) + len = ItemLenOffCol(index_, start, cp); + + int m = buf_.Size(); + int n = m - len; + + if (n > 0) + cp->Grow(start, n); + else if (n < 0) + cp->Shrink(start, - n); + else if (m == 0) + return; // no size change and no contents + + _recalc = true; + + cp->StoreBytes(start, buf_); + + if (n && cp == &_data) { // if size has changed + int k = _offsets.GetSize() - 1; + + // if filling in an empty entry at end: extend offsets first + if (m > 0 && index_ >= k) { + _offsets.InsertAt(k, _offsets.GetAt(k), index_ - k + 1); + + k = index_ + 1; + d4_assert(k == _offsets.GetSize() - 1); + } + + // adjust following entry offsets + while (++index_ <= k) + _offsets.ElementAt(index_) += n; + } + + d4_assert((t4_i32) _offsets.GetAt(_offsets.GetSize() - 1) == _data.ColSize()); +} + +void c4_FormatB::Set(int index_, const c4_Bytes& buf_) +{ + SetOne(index_, buf_); +} + +int c4_FormatB::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + int n = b1_.Size(); + if (n > b2_.Size()) + n = b2_.Size(); + + int f = memcmp(b1_.Contents(), b2_.Contents(), n); + return f ? f : b1_.Size() - b2_.Size(); +} + +void c4_FormatB::Insert(int index_, const c4_Bytes& buf_, int count_) +{ + d4_assert(count_ > 0); + + _recalc = true; + + int m = buf_.Size(); + t4_i32 off = Offset(index_); + + _memos.InsertAt(index_, 0, count_); + + // insert the appropriate number of bytes + t4_i32 n = count_ * (t4_i32) m; + if (n > 0) { + _data.Grow(off, n); + + // store as many copies as needed, but may have to do it in chunks + int spos = 0; + + c4_ColIter iter (_data, off, off + n); + while (iter.Next(m - spos)) { + memcpy(iter.BufSave(), buf_.Contents() + spos, iter.BufLen()); + + spos += iter.BufLen(); + if (spos >= m) + spos = 0; + } + + d4_assert(spos == 0); // must have copied an exact multiple of the data + } + + // define offsets of the new entries + _offsets.InsertAt(index_, 0, count_); + d4_assert(_offsets.GetSize() <= _memos.GetSize() + 1); + + while (--count_ >= 0) { + _offsets.SetAt(index_++, off); + off += m; + } + + d4_assert(index_ < _offsets.GetSize()); + + // adjust all following entries + while (index_ < _offsets.GetSize()) + _offsets.ElementAt(index_++) += n; + + d4_assert((t4_i32) _offsets.GetAt(index_ - 1) == _data.ColSize()); + d4_assert(index_ <= _memos.GetSize() + 1); +} + +void c4_FormatB::Remove(int index_, int count_) +{ + _recalc = true; + + t4_i32 off = Offset(index_); + t4_i32 n = Offset(index_ + count_) - off; + d4_assert(n >= 0); + + // remove the columns, if present + for (int i = 0; i < count_; ++i) + delete (c4_Column*) _memos.GetAt(index_ + i); + _memos.RemoveAt(index_, count_); + + if (n > 0) + _data.Shrink(off, n); + + _offsets.RemoveAt(index_, count_); + + d4_assert(index_ < _offsets.GetSize()); + + // adjust all following entries + while (index_ < _offsets.GetSize()) + _offsets.ElementAt(index_++) -= n; + + d4_assert((t4_i32) _offsets.GetAt(index_ - 1) == _data.ColSize()); + d4_assert(index_ <= _memos.GetSize() + 1); +} + +void c4_FormatB::Commit(c4_SaveContext& ar_) +{ + int rows = _memos.GetSize(); + d4_assert(rows > 0); + + bool full = _recalc || ar_.Serializing(); + + if (!full) + for (int i = 0; i < rows; ++i) { + c4_Column* col = (c4_Column*) _memos.GetAt(i); + if (col != 0) { + full = true; + break; + } + } + d4_assert(_recalc || _sizeCol.RowCount() == rows); + + if (full) { + _memoCol.SetBuffer(0); + _sizeCol.SetBuffer(0); + _sizeCol.SetAccessWidth(0); + _sizeCol.SetRowCount(rows); + + int skip = 0; + + c4_Column* saved = ar_.SetWalkBuffer(&_memoCol); + + for (int r = 0; r < rows; ++r) { + ++skip; + + t4_i32 start; + c4_Column* col; + int len = ItemLenOffCol(r, start, col); + + bool oldMemo = col != &_data; + bool newMemo = ShouldBeMemo(len); + + if (!oldMemo && newMemo) { + col = GetNthMemoCol(r, true); + d4_assert(col != &_data); + //? start = 0; + } + + c4_Bytes temp; + + if (newMemo) { // it now is a memo, inlined data will be empty + ar_.StoreValue(skip - 1); + skip = 0; + ar_.CommitColumn(*col); + } else if (!oldMemo) { // it was no memo, done if it hasn't become one + _sizeCol.SetInt(r, len); + continue; + } else { // it was a memo, but it no longer is + d4_assert(start == 0); + if (len > 0) + { + _sizeCol.SetInt(r, len); + col->FetchBytes(start, len, temp, true); + delete (c4_Column*) _memos.GetAt(r); // 28-11-2001: fix mem leak + _memos.SetAt(r, 0); // 02-11-2001: fix for use after commit + } + } + + SetOne(r, temp, true); // bypass current memo pointer + } + + ar_.SetWalkBuffer(saved); + } + + ar_.CommitColumn(_data); + + if (_data.ColSize() > 0) { + _sizeCol.FixSize(true); + ar_.CommitColumn(_sizeCol); + //_sizeCol.FixSize(false); + } + + ar_.CommitColumn(_memoCol); + + // need a way to find out when the data has been committed (on 2nd pass) + // both _sizeCol and _memoCol will be clean again when it has + // but be careful because dirty flag is only useful if size is nonzero + if (_recalc && !ar_.Serializing()) + _recalc = _sizeCol.ColSize() > 0 && _sizeCol.IsDirty() || + _memoCol.ColSize() > 0 && _memoCol.IsDirty(); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatS : public c4_FormatB +{ +public: + c4_FormatS (const c4_Property& prop_, c4_HandlerSeq& seq_); + + virtual int ItemSize(int index_); + virtual const void* Get(int index_, int& length_); + virtual void Set(int index_, const c4_Bytes& buf_); + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatS::c4_FormatS (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatB (prop_, seq_) +{ +} + +int c4_FormatS::ItemSize(int index_) +{ + int n = c4_FormatB::ItemSize(index_) - 1; + return n >= 0 ? n : 0; +} + +const void* c4_FormatS::Get(int index_, int& length_) +{ + const void* ptr = GetOne(index_, length_); + + if (length_ == 0) { + length_ = 1; + ptr = ""; + } + + d4_assert(((const char*) ptr)[length_-1] == 0); + return ptr; +} + +void c4_FormatS::Set(int index_, const c4_Bytes& buf_) +{ + int m = buf_.Size(); + if (--m >= 0) { + d4_assert(buf_.Contents()[m] == 0); + if (m == 0) { + SetOne(index_, c4_Bytes ()); // don't store data for empty strings + return; + } + } + + SetOne(index_, buf_); +} + +int c4_FormatS::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + c4_String v1 ((const char*) b1_.Contents(), b1_.Size()); + c4_String v2 ((const char*) b2_.Contents(), b2_.Size()); + + return v1.CompareNoCase(v2); +} + +void c4_FormatS::Insert(int index_, const c4_Bytes& buf_, int count_) +{ + d4_assert(count_ > 0); + + int m = buf_.Size(); + if (--m >= 0) { + d4_assert(buf_.Contents()[m] == 0); + if (m == 0) { + c4_FormatB::Insert(index_, c4_Bytes (), count_); + return; + } + } + + c4_FormatB::Insert(index_, buf_, count_); +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_FormatV : public c4_FormatHandler +{ +public: + c4_FormatV (const c4_Property& prop_, c4_HandlerSeq& seq_); + virtual ~c4_FormatV (); + + virtual void Define(int rows_, const t4_byte** ptr_); + virtual void OldDefine(char type_, c4_Persist&); + virtual void Commit(c4_SaveContext& ar_); + + virtual void FlipBytes(); + + virtual int ItemSize(int index_); + virtual const void* Get(int index_, int& length_); + virtual void Set(int index_, const c4_Bytes& buf_); + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_); + virtual void Remove(int index_, int count_); + + virtual void Unmapped(); + virtual bool HasSubview(int index_); + + static int DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_); + +private: + c4_HandlerSeq& At(int index_); + void Replace(int index_, c4_HandlerSeq* seq_); + void SetupAllSubviews(); + void ForgetSubview(int index_); + + c4_Column _data; + c4_PtrArray _subSeqs; + bool _inited; +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FormatV::c4_FormatV (const c4_Property& prop_, c4_HandlerSeq& seq_) + : c4_FormatHandler (prop_, seq_), _data (seq_.Persist()), _inited (false) +{ +} + +c4_FormatV::~c4_FormatV () +{ + for (int i = 0; i < _subSeqs.GetSize(); ++i) + ForgetSubview(i); +} + +c4_HandlerSeq& c4_FormatV::At(int index_) +{ + d4_assert(_inited); + + c4_HandlerSeq*& hs = (c4_HandlerSeq*&) _subSeqs.ElementAt(index_); + if (hs == 0) { + hs = d4_new c4_HandlerSeq (Owner(), this); + hs->IncRef(); + } + + return *hs; +} + +void c4_FormatV::SetupAllSubviews() +{ + d4_assert(!_inited); + _inited = true; + + if (_data.ColSize() > 0) { + c4_Bytes temp; + _data.FetchBytes(0, _data.ColSize(), temp, true); + const t4_byte* ptr = temp.Contents(); + + for (int r = 0; r < _subSeqs.GetSize(); ++r) { + // don't materialize subview if it is empty + // duplicates code which is in c4_HandlerSeq::Prepare + const t4_byte* p2 = ptr; + d4_dbgdef(t4_i32 sias =) + c4_Column::PullValue(p2); + d4_assert(sias == 0); // not yet + + if (c4_Column::PullValue(p2) > 0) + At(r).Prepare(&ptr, false); + else + ptr = p2; + } + + d4_assert(ptr == temp.Contents() + temp.Size()); + } +} + +void c4_FormatV::Define(int rows_, const t4_byte** ptr_) +{ + if (_inited) { + // big oops: a root handler already contains data + + for (int i = 0; i < _subSeqs.GetSize(); ++i) + ForgetSubview(i); + + _inited = false; + } + + _subSeqs.SetSize(rows_); + if (ptr_ != 0) + _data.PullLocation(*ptr_); +} + +void c4_FormatV::OldDefine(char, c4_Persist& pers_) +{ + int rows = Owner().NumRows(); + _subSeqs.SetSize(rows); + + for (int i = 0; i < rows; ++i) { + int n = pers_.FetchOldValue(); + if (n) { + // 14-11-2000: do not create again (this causes a mem leak) + // 04-12-2000: but do create if absent (fixes occasional crash) + c4_HandlerSeq* hs = (c4_HandlerSeq*) _subSeqs.GetAt(i); + if (hs == 0) { + hs = d4_new c4_HandlerSeq (Owner(), this); + _subSeqs.SetAt(i, hs); + hs->IncRef(); + } + hs->SetNumRows(n); + hs->OldPrepare(); + } + } +} + +void c4_FormatV::FlipBytes() +{ + if (!_inited) + SetupAllSubviews(); + + for (int i = 0; i < _subSeqs.GetSize(); ++i) + At(i).FlipAllBytes(); +} + +int c4_FormatV::ItemSize(int index_) +{ + if (!_inited) + SetupAllSubviews(); + + // 06-02-2002: avoid creating empty subview + c4_HandlerSeq* hs = (c4_HandlerSeq*&) _subSeqs.ElementAt(index_); + return hs == 0 ? 0 : hs->NumRows(); +} + +const void* c4_FormatV::Get(int index_, int& length_) +{ + if (!_inited) + SetupAllSubviews(); + + At(index_); // forces existence of a real entry + c4_HandlerSeq*& e = (c4_HandlerSeq*&) _subSeqs.ElementAt(index_); + + length_ = sizeof (c4_HandlerSeq**); + return &e; +} + +void c4_FormatV::Set(int index_, const c4_Bytes& buf_) +{ + d4_assert(buf_.Size() == sizeof (c4_Sequence*)); + + if (!_inited) + SetupAllSubviews(); + + c4_HandlerSeq* value = *(c4_HandlerSeq* const*) buf_.Contents(); + + if (value != & At(index_)) + Replace(index_, value); +} + +void c4_FormatV::Replace(int index_, c4_HandlerSeq* seq_) +{ + if (!_inited) + SetupAllSubviews(); + + c4_HandlerSeq*& curr = (c4_HandlerSeq*&) _subSeqs.ElementAt(index_); + if (seq_ == curr) + return; + + if (curr != 0) { + d4_assert(&curr->Parent() == &Owner()); + curr->DetachFromParent(); + curr->DetachFromStorage(true); + + curr->DecRef(); + curr = 0; + } + + if (seq_) { + int n = seq_->NumRows(); + + c4_HandlerSeq& t = At(index_); + d4_assert(t.NumRows() == 0); + + t.Resize(n); + + c4_Bytes data; + + // this dest seq has only the persistent handlers + // and maybe in a different order + // create any others we need as temporary properties + for (int i = 0; i < seq_->NumHandlers(); ++i) { + c4_Handler& h1 = seq_->NthHandler(i); + + int j = t.PropIndex(h1.Property()); + d4_assert(j >= 0); + + c4_Handler& h2 = t.NthHandler(j); + + for (int k = 0; k < n; ++k) + if (seq_->Get(k, h1.PropId(), data)) + h2.Set(k, data); + } + } +} + +int c4_FormatV::DoCompare(const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + d4_assert(b1_.Size() == sizeof (c4_Sequence*)); + d4_assert(b2_.Size() == sizeof (c4_Sequence*)); + + c4_View v1 = *(c4_Sequence* const*) b1_.Contents(); + c4_View v2 = *(c4_Sequence* const*) b2_.Contents(); + + return v1.Compare(v2); +} + +void c4_FormatV::Insert(int index_, const c4_Bytes& buf_, int count_) +{ + d4_assert(buf_.Size() == sizeof (c4_Sequence*)); + d4_assert(count_ > 0); + + // can only insert an empty entry! + d4_assert(*(c4_Sequence* const*) buf_.Contents() == 0); + + if (!_inited) + SetupAllSubviews(); + + _subSeqs.InsertAt(index_, 0, count_); + _data.SetBuffer(0); // 2004-01-18 force dirty +} + +void c4_FormatV::Remove(int index_, int count_) +{ + d4_assert(count_ > 0); + + if (!_inited) + SetupAllSubviews(); + + for (int i = 0; i < count_; ++i) + ForgetSubview(index_ + i); + + _subSeqs.RemoveAt(index_, count_); + _data.SetBuffer(0); // 2004-01-18 force dirty +} + +void c4_FormatV::Unmapped() +{ + if (_inited) + for (int i = 0; i < _subSeqs.GetSize(); ++i) + if (HasSubview(i)) { + c4_HandlerSeq& hs = At(i); + hs.UnmappedAll(); + if (hs.NumRefs() == 1 && hs.NumRows() == 0) + ForgetSubview(i); + } + + _data.ReleaseAllSegments(); +} + +bool c4_FormatV::HasSubview(int index_) +{ + if (!_inited) + SetupAllSubviews(); + + return _subSeqs.ElementAt(index_) != 0; +} + +void c4_FormatV::ForgetSubview(int index_) +{ + c4_HandlerSeq*& seq = (c4_HandlerSeq*&) _subSeqs.ElementAt(index_); + if (seq != 0) { + d4_assert(&seq->Parent() == &Owner()); + seq->DetachFromParent(); + seq->DetachFromStorage(true); + seq->UnmappedAll(); + seq->DecRef(); + seq = 0; + } +} + +void c4_FormatV::Commit(c4_SaveContext& ar_) +{ + if (!_inited) + SetupAllSubviews(); + + int rows = _subSeqs.GetSize(); + d4_assert(rows > 0); + + c4_Column temp (0); + c4_Column* saved = ar_.SetWalkBuffer(&temp); + + for (int r = 0; r < rows; ++r) + if (HasSubview(r)) { + c4_HandlerSeq& hs = At(r); + ar_.CommitSequence(hs, false); + if (hs.NumRefs() == 1 && hs.NumRows() == 0) + ForgetSubview(r); + } else { + ar_.StoreValue(0); // sias + ar_.StoreValue(0); // row count + } + + ar_.SetWalkBuffer(saved); + + c4_Bytes buf; + temp.FetchBytes(0, temp.ColSize(), buf, true); + + bool changed = temp.ColSize() != _data.ColSize(); + + if (!changed) { + c4_Bytes buf2; + _data.FetchBytes(0, _data.ColSize(), buf2, true); + changed = buf != buf2; + } + + if (changed) { + _data.SetBuffer(buf.Size()); + _data.StoreBytes(0, buf); + } + + ar_.CommitColumn(_data); +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Handler* f4_CreateFormat(const c4_Property& prop_, c4_HandlerSeq& seq_) +{ + switch (prop_.Type()) { + case 'I': return d4_new c4_FormatX (prop_, seq_); +#if !q4_TINY + case 'L': return d4_new c4_FormatL (prop_, seq_); + case 'F': return d4_new c4_FormatF (prop_, seq_); + case 'D': return d4_new c4_FormatD (prop_, seq_); +#endif + case 'B': return d4_new c4_FormatB (prop_, seq_); + case 'S': return d4_new c4_FormatS (prop_, seq_); + case 'V': return d4_new c4_FormatV (prop_, seq_); + } + + d4_assert(0); + // 2004-01-16 turn bad definition type into an int property to avoid crash + return d4_new c4_FormatX (c4_IntProp (prop_.Name()), seq_); +} + +///////////////////////////////////////////////////////////////////////////// + +int f4_ClearFormat(char type_) +{ + switch (type_) { + case 'I': return sizeof (t4_i32); +#if !q4_TINY + case 'L': return sizeof (t4_i64); + case 'F': return sizeof (float); + case 'D': return sizeof (double); +#endif + case 'S': return 1; + case 'V': return sizeof (c4_Sequence*); + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////// + +int f4_CompareFormat(char type_, const c4_Bytes& b1_, const c4_Bytes& b2_) +{ + switch (type_) { + case 'I': return c4_FormatX::DoCompare(b1_, b2_); +#if !q4_TINY + case 'L': return c4_FormatL::DoCompare(b1_, b2_); + case 'F': return c4_FormatF::DoCompare(b1_, b2_); + case 'D': return c4_FormatD::DoCompare(b1_, b2_); +#endif + case 'B': return c4_FormatB::DoCompare(b1_, b2_); + case 'S': return c4_FormatS::DoCompare(b1_, b2_); + case 'V': return c4_FormatV::DoCompare(b1_, b2_); + } + + d4_assert(0); + return 0; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/format.h b/akregator/src/mk4storage/metakit/src/format.h new file mode 100644 index 00000000..721e5548 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/format.h @@ -0,0 +1,23 @@ +// format.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Encapsulation of all format handlers + */ + +#ifndef __FORMAT_H__ +#define __FORMAT_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_Handler; // not defined here + + extern c4_Handler* f4_CreateFormat(const c4_Property&, c4_HandlerSeq&); + extern int f4_ClearFormat(char); + extern int f4_CompareFormat(char, const c4_Bytes&, const c4_Bytes&); + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/gnuc.h b/akregator/src/mk4storage/metakit/src/gnuc.h new file mode 100644 index 00000000..da112ac6 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/gnuc.h @@ -0,0 +1,13 @@ +// gnuc.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Configuration header for GNU C++ + */ + +#define q4_GNUC 1 + +#if !defined (q4_BOOL) +#define q4_BOOL 1 +#endif diff --git a/akregator/src/mk4storage/metakit/src/handler.cpp b/akregator/src/mk4storage/metakit/src/handler.cpp new file mode 100644 index 00000000..6c68c5c3 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/handler.cpp @@ -0,0 +1,500 @@ +// handler.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Handlers store data in column-wise format + */ + +#include "header.h" +#include "handler.h" +#include "format.h" +#include "field.h" +#include "column.h" +#include "persist.h" + +#if !q4_INLINE +#include "handler.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// +// c4_Handler + +void c4_Handler::ClearBytes(c4_Bytes& buf_) const +{ + static char zeros[8]; + + int n = f4_ClearFormat(Property().Type()); + d4_assert(n <= sizeof zeros); + + buf_ = c4_Bytes (zeros, n); +} + +int c4_Handler::Compare(int index_, const c4_Bytes& buf_) +{ + // create a copy for small data, since ints use a common _item buffer + c4_Bytes copy (buf_.Contents(), buf_.Size(), buf_.Size() <= 8); + + c4_Bytes data; + GetBytes(index_, data); + + return f4_CompareFormat(Property().Type(), data, copy); +} + +void c4_Handler::Commit(c4_SaveContext&) +{ + d4_assert(0); +} + +void c4_Handler::OldDefine(char, c4_Persist&) +{ + d4_assert(0); +} + + // this is how the old "Get" was, keep it until no longer needed +void c4_Handler::GetBytes(int index_, c4_Bytes& buf_, bool copySmall_) +{ + int n; + const void* p = Get(index_, n); + buf_ = c4_Bytes (p, n, copySmall_ && n <= 8); +} + +void c4_Handler::Move(int from_, int to_) +{ + if (from_ != to_) { + c4_Bytes data; + GetBytes(from_, data); + + Remove(from_, 1); + + if (to_ > from_) + --to_; + + Insert(to_, data, 1); + } +} + +///////////////////////////////////////////////////////////////////////////// +// c4_HandlerSeq + +c4_HandlerSeq::c4_HandlerSeq (c4_Persist* persist_) + : _persist (persist_), _field (0), _parent (0), _numRows (0) +{ +} + +c4_HandlerSeq::c4_HandlerSeq (c4_HandlerSeq& owner_, c4_Handler* handler_) + : _persist (owner_.Persist()), _field (owner_.FindField(handler_)), + _parent (&owner_), _numRows (0) +{ + for (int i = 0; i < NumFields(); ++i) { + c4_Field& field = Field(i); + c4_Property prop (field.Type(), field.Name()); + + d4_dbgdef(int n =) + AddHandler(f4_CreateFormat(prop, *this)); + d4_assert(n == i); + } +} + +c4_HandlerSeq::~c4_HandlerSeq () +{ + const bool rootLevel = _parent == this; + c4_Persist* pers = _persist; + + if (rootLevel && pers != 0) + pers->DoAutoCommit(); + + DetachFromParent(); + DetachFromStorage(true); + + for (int i = 0; i < NumHandlers(); ++i) + delete & NthHandler(i); + _handlers.SetSize(0); + + ClearCache(); + + if (rootLevel) { + delete _field; + + d4_assert(pers != 0); + delete pers; + } +} + +c4_Persist* c4_HandlerSeq::Persist() const +{ + return _persist; +} + +void c4_HandlerSeq::DefineRoot() +{ + d4_assert(_field == 0); + d4_assert(_parent == 0); + + SetNumRows(1); + + const char* desc = "[]"; + _field = d4_new c4_Field (desc); + d4_assert(!*desc); + + _parent = this; +} + +c4_Handler* c4_HandlerSeq::CreateHandler(const c4_Property& prop_) +{ + return f4_CreateFormat(prop_, *this); +} + +c4_Field& c4_HandlerSeq::Definition() const +{ + d4_assert(_field != 0); + + return *_field; +} + +void c4_HandlerSeq::DetachFromParent() +{ + if (_field != 0) { + const char* desc = "[]"; + c4_Field f (desc); + d4_assert(!*desc); + Restructure(f, false); + _field = 0; + } + + _parent = 0; +} + +void c4_HandlerSeq::DetachFromStorage(bool full_) +{ + if (_persist != 0) { + int limit = full_ ? 0 : NumFields(); + + // get rid of all handlers which might do I/O + for (int c = NumHandlers(); --c >= 0; ) { + c4_Handler& h = NthHandler(c); + + // all nested fields are detached recursively + if (IsNested(c)) + for (int r = 0; r < NumRows(); ++r) + if (h.HasSubview(r)) + SubEntry(c, r).DetachFromStorage(full_); + + if (c >= limit) { + if (h.IsPersistent()) { + delete &h; + _handlers.RemoveAt(c); + ClearCache(); + } + } + } + + if (full_) { + //UnmappedAll(); + _persist = 0; + } + } +} + +void c4_HandlerSeq::DetermineSpaceUsage() +{ + for (int c = 0; c < NumFields(); ++c) + if (IsNested(c)) { + c4_Handler& h = NthHandler(c); + for (int r = 0; r < NumRows(); ++r) + if (h.HasSubview(r)) + SubEntry(c, r).DetermineSpaceUsage(); + } +} + +void c4_HandlerSeq::SetNumRows(int numRows_) +{ + d4_assert(_numRows >= 0); + + _numRows = numRows_; +} + +int c4_HandlerSeq::AddHandler(c4_Handler* handler_) +{ + d4_assert(handler_ != 0); + + return _handlers.Add(handler_); +} + +const char* c4_HandlerSeq::Description() +{ + // 19-01-2003: avoid too dense code, since Sun CC seems to choke on it + //return _field != 0 ? UseTempBuffer(Definition().DescribeSubFields()) : 0; + if (_field == 0) + return 0; + c4_String s = _field->DescribeSubFields(); + return UseTempBuffer(s); +} + +void c4_HandlerSeq::Restructure(c4_Field& field_, bool remove_) +{ + //d4_assert(_field != 0); + + // all nested fields must be set up, before we shuffle them around + for (int k = 0; k < NumHandlers(); ++k) + if (IsNested(k)) { + c4_Handler& h = NthHandler(k); + for (int n = 0; n < NumRows(); ++n) + if (h.HasSubview(n)) + SubEntry(k, n); + } + + for (int i = 0; i < field_.NumSubFields(); ++i) { + c4_Field& nf = field_.SubField(i); + c4_Property prop (nf.Type(), nf.Name()); + + int n = PropIndex(prop.GetId()); + if (n == i) + continue; + + if (n < 0) { + _handlers.InsertAt(i, f4_CreateFormat(prop, *this)); + NthHandler(i).Define(NumRows(), 0); + } else { + // move the handler to the front + d4_assert(n > i); + _handlers.InsertAt(i, _handlers.GetAt(n)); + _handlers.RemoveAt(++n); + } + + ClearCache(); // we mess with the order of handler, keep clearing it + + d4_assert(PropIndex(prop.GetId()) == i); + } + + c4_Field* ofld = _field; + // special case if we're "restructuring a view out of persistence", see below + + _field = remove_ ? 0 : &field_; + + // let handler do additional init once all have been prepared + //for (int n = 0; n < NumHandlers(); ++n) + // NthHandler(n).Define(NumRows(), 0); + + const char* desc = "[]"; + c4_Field temp (desc); + + // all nested fields are restructured recursively + for (int j = 0; j < NumHandlers(); ++j) + if (IsNested(j)) { + c4_Handler& h = NthHandler(j); + for (int n = 0; n < NumRows(); ++n) + if (h.HasSubview(n)) { + c4_HandlerSeq& seq = SubEntry(j, n); + if (j < NumFields()) + seq.Restructure(field_.SubField(j), false); + else if (seq._field != 0) + seq.Restructure(temp, true); + } + } + + if (_parent == this) + delete ofld; // the root table owns its field structure tree +} + +int c4_HandlerSeq::NumFields() const +{ + return _field != 0 ? _field->NumSubFields() : 0; +} + +char c4_HandlerSeq::ColumnType(int index_) const +{ + return NthHandler(index_).Property().Type(); +} + +bool c4_HandlerSeq::IsNested(int index_) const +{ + return ColumnType(index_) == 'V'; +} + +c4_Field& c4_HandlerSeq::Field(int index_) const +{ + d4_assert(_field != 0); + + return _field->SubField(index_); +} + +void c4_HandlerSeq::Prepare(const t4_byte** ptr_, bool selfDesc_) +{ + if (ptr_ != 0) { + d4_dbgdef(t4_i32 sias =) + c4_Column::PullValue(*ptr_); + d4_assert(sias == 0); // not yet + + if (selfDesc_) { + t4_i32 n = c4_Column::PullValue(*ptr_); + if (n > 0) { + c4_String s = "[" + c4_String ((const char*) *ptr_, n) + "]"; + const char* desc = s; + + c4_Field* f = d4_new c4_Field (desc); + d4_assert(!*desc); + + Restructure(*f, false); + *ptr_ += n; + } + } + + int rows = (int) c4_Column::PullValue(*ptr_); + if (rows > 0) { + SetNumRows(rows); + + for (int i = 0; i < NumFields(); ++i) + NthHandler(i).Define(rows, ptr_); + } + } +} + +void c4_HandlerSeq::OldPrepare() +{ + d4_assert(_persist != 0); + + for (int i = 0; i < NumFields(); ++i) { + char origType = _field->SubField(i).OrigType(); + NthHandler(i).OldDefine(origType, *_persist); + } +} + +void c4_HandlerSeq::FlipAllBytes() +{ + for (int i = 0; i < NumHandlers(); ++i) { + c4_Handler& h = NthHandler(i); + h.FlipBytes(); + } +} + + // New 19990903: swap rows in tables without touching the memo fields + // or subviews on disk. This is used by the new c4_View::RelocateRows. + +void c4_HandlerSeq::ExchangeEntries(int srcPos_, c4_HandlerSeq& dst_, int dstPos_) +{ + d4_assert(NumHandlers() == dst_.NumHandlers()); + + c4_Bytes t1, t2; + + for (int col = 0; col < NumHandlers(); ++col) + { + if (IsNested(col)) + { + d4_assert(dst_.IsNested(col)); + + int n; + c4_HandlerSeq** e1 = (c4_HandlerSeq**) NthHandler(col).Get(srcPos_, n); + c4_HandlerSeq** e2 = (c4_HandlerSeq**) dst_.NthHandler(col).Get(dstPos_, n); + d4_assert(*e1 != 0 && *e2 != 0); + + // swap the two entries + c4_HandlerSeq* e = *e1; + *e1 = *e2; + *e2 = e; + + // shorthand, *after* the swap + c4_HandlerSeq& t1 = SubEntry(col, srcPos_); + c4_HandlerSeq& t2 = dst_.SubEntry(col, dstPos_); + + // adjust the parents + t1._parent = this; + t2._parent = &dst_; + + // reattach the proper field structures + t1.Restructure(Field(col), false); + t2.Restructure(dst_.Field(col), false); + } + else + { + d4_assert(ColumnType(col) == dst_.ColumnType(col)); + + c4_Handler& h1 = NthHandler(col); + c4_Handler& h2 = dst_.NthHandler(col); + +#if 0 // memo's are 'B' now, but tricky to deal with, so copy them for now + if (ColumnType(col) == 'M') + { + c4_Column* c1 = h1.GetNthMemoCol(srcPos_, true); + c4_Column* c2 = h2.GetNthMemoCol(dstPos_, true); + + t4_i32 p1 = c1 ? c1->Position() : 0; + t4_i32 p2 = c2 ? c2->Position() : 0; + + t4_i32 s1 = c1 ? c1->ColSize() : 0; + t4_i32 s2 = c2 ? c2->ColSize() : 0; + + d4_assert(false); // broken + //!h1.SetNthMemoPos(srcPos_, p2, s2, c2); + //!h2.SetNthMemoPos(dstPos_, p1, s1, c1); + } +#endif + // 10-4-2002: Need to use copies in case either item points into + // memory that could move, or if access re-uses a shared buffer. + // The special cases are sufficiently tricky that it's NOT being + // optimized for now (temp bufs, mmap ptrs, c4_Bytes buffering). + + int n1, n2; + const void* p1 = h1.Get(srcPos_, n1); + const void* p2 = h2.Get(dstPos_, n2); + + c4_Bytes t1 (p1, n1, true); + c4_Bytes t2 (p2, n2, true); + + h1.Set(srcPos_, t2); + h2.Set(dstPos_, t1); + } + } +} + +c4_HandlerSeq& c4_HandlerSeq::SubEntry(int col_, int row_) const +{ + d4_assert(IsNested(col_)); + + c4_Bytes temp; + NthHandler(col_).GetBytes(row_, temp); + + d4_assert(temp.Size() == sizeof (c4_HandlerSeq**)); + c4_HandlerSeq** p = (c4_HandlerSeq**) temp.Contents(); // loses const + + d4_assert(p != 0 && *p != 0); + + return **p; +} + +c4_Field* c4_HandlerSeq::FindField(const c4_Handler* handler_) +{ + for (int i = 0; i < NumFields(); ++i) + if (handler_ == &NthHandler(i)) + return &Field(i); + return 0; +} + +void c4_HandlerSeq::UnmappedAll() +{ + for (int i = 0; i < NumFields(); ++i) + NthHandler(i).Unmapped(); +} + + // construct meta view from a pre-parsed field tree structure + // this will one day be converted to directly parse the description string +void c4_HandlerSeq::BuildMeta(int parent_, int colnum_, c4_View& meta_, + const c4_Field& field_) +{ + c4_IntProp pP ("P"), pC ("C"); + c4_ViewProp pF ("F"); + c4_StringProp pN ("N"), pT ("T"); + + int n = meta_.Add(pP [parent_] + pC [colnum_]); + c4_View fields = pF (meta_[n]); + + for (int i = 0; i < field_.NumSubFields(); ++i) { + const c4_Field& f = field_.SubField(i); + char type = f.Type(); + fields.Add(pN [f.Name()] + pT [c4_String (&type, 1)]); + if (type == 'V') + BuildMeta(n, i, meta_, f); + } +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/handler.h b/akregator/src/mk4storage/metakit/src/handler.h new file mode 100644 index 00000000..6003f625 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/handler.h @@ -0,0 +1,150 @@ +// handler.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Definition of the main handler classes + */ + +#ifndef __HANDLER_H__ +#define __HANDLER_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_Handler; // data representation handler + +// class c4_Sequence; + class c4_HandlerSeq; // a sequence built from handlers + + class c4_Column; // not defined here + class c4_Field; // not defined here + class c4_Persist; // not defined here + class c4_SaveContext; // not defined here + +///////////////////////////////////////////////////////////////////////////// + +class c4_Handler +{ + c4_Property _property; + +public: + c4_Handler (const c4_Property& _prop); + //: Constructor (this is an abstract base class). + virtual ~c4_Handler (); + + virtual void Define(int, const t4_byte**); + //: Called when the corresponding table has been fully defined. + virtual void FlipBytes(); + //: Called to reverse the internal byte order of foreign data. + virtual void Commit(c4_SaveContext& ar_); + //: Commit the associated column(s) to file. + virtual void OldDefine(char, c4_Persist&); + + const c4_Property& Property() const; + //: Returns the property associated with this handler. + int PropId() const; + //: Returns the id of the property associated with this handler. + + void ClearBytes(c4_Bytes& buf_) const; + //: Returns the default value for items of this type. + + virtual int ItemSize(int index_) = 0; + //: Return width of specified data item. + void GetBytes(int index_, c4_Bytes& buf_, bool copySmall_ =false); + //: Used for backward compatibility, should probably be replaced. + virtual const void* Get(int index_, int& length_) = 0; + //: Retrieves the data item at the specified index. + virtual void Set(int index_, const c4_Bytes& buf_) = 0; + //: Stores a new data item at the specified index. + + int Compare(int index_, const c4_Bytes& buf_); + //: Compares an entry with a specified data item. + + virtual void Insert(int index_, const c4_Bytes& buf_, int count_) = 0; + //: Inserts 1 or more data items at the specified index. + virtual void Remove(int index_, int count_) = 0; + //: Removes 1 or more data items at the specified index. + void Move(int from_, int to_); + //: Move a data item to another position. + + virtual c4_Column* GetNthMemoCol(int index_, bool alloc_ =false); + //: Special access to underlying data of memo entries + + virtual bool IsPersistent() const; + //: True if this handler might do I/O to satisfy fetches + + virtual void Unmapped(); + //: Make sure this handler stops using file mappings + + virtual bool HasSubview(int index_); + //: True if this subview has materialized into an object +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_HandlerSeq : public c4_Sequence +{ + c4_PtrArray _handlers; + c4_Persist* _persist; + c4_Field* _field; + c4_HandlerSeq* _parent; + int _numRows; + +public: + c4_HandlerSeq (c4_Persist*); + c4_HandlerSeq (c4_HandlerSeq& owner_, c4_Handler* handler_); + + virtual int NumRows() const; + virtual void SetNumRows(int); + + virtual int NumHandlers() const; + virtual c4_Handler& NthHandler(int) const; + virtual const c4_Sequence* HandlerContext(int) const; + virtual int AddHandler(c4_Handler*); + + void DefineRoot(); + void Restructure(c4_Field&, bool remove_); + void DetachFromParent(); + void DetachFromStorage(bool full_); + void DetermineSpaceUsage(); + + c4_Field& Definition() const; + const char* Description(); + c4_HandlerSeq& Parent() const; + virtual c4_Persist* Persist() const; + + c4_Field& Field(int) const; + int NumFields() const; + char ColumnType(int index_) const; + bool IsNested(int) const; + + void Prepare(const t4_byte** ptr_, bool selfDesc_); + void OldPrepare(); + + void FlipAllBytes(); + void ExchangeEntries(int srcPos_, c4_HandlerSeq& dst_, int dstPos_); + + c4_HandlerSeq& SubEntry(int, int) const; + + c4_Field* FindField(const c4_Handler* handler_); + + void UnmappedAll(); + + static void BuildMeta(int, int, c4_View&, const c4_Field&); + +protected: + virtual c4_Handler* CreateHandler(const c4_Property&); + + virtual ~c4_HandlerSeq (); +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_INLINE +#include "handler.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/handler.inl b/akregator/src/mk4storage/metakit/src/handler.inl new file mode 100644 index 00000000..75321f9c --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/handler.inl @@ -0,0 +1,90 @@ +// handler.inl -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Inlined members of the handler classes + */ + +///////////////////////////////////////////////////////////////////////////// +// c4_Handler + +d4_inline c4_Handler::c4_Handler (const c4_Property& prop_) + : _property (prop_) +{ +} + +d4_inline c4_Handler::~c4_Handler () +{ +} + +d4_inline void c4_Handler::Define(int, const t4_byte**) +{ +} + +d4_inline void c4_Handler::FlipBytes() +{ +} + +d4_inline const c4_Property& c4_Handler::Property() const +{ + return _property; +} + +d4_inline int c4_Handler::PropId() const +{ + return _property.GetId(); +} + +d4_inline c4_Column* c4_Handler::GetNthMemoCol(int, bool alloc_) +{ + return 0; +} + +d4_inline bool c4_Handler::IsPersistent() const +{ + return false; +} + +d4_inline void c4_Handler::Unmapped() +{ +} + +d4_inline bool c4_Handler::HasSubview(int) +{ + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// c4_HandlerSeq + +d4_inline int c4_HandlerSeq::NumRows() const +{ + d4_assert(_numRows >= 0); + + return _numRows; +} + +d4_inline int c4_HandlerSeq::NumHandlers() const +{ + return _handlers.GetSize(); +} + +d4_inline c4_Handler& c4_HandlerSeq::NthHandler(int index_) const +{ + d4_assert(_handlers.GetAt(index_) != 0); + + return *(c4_Handler*) _handlers.GetAt(index_); +} + +d4_inline const c4_Sequence* c4_HandlerSeq::HandlerContext(int) const +{ + return this; +} + +d4_inline c4_HandlerSeq& c4_HandlerSeq::Parent() const +{ + return *_parent; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/header.h b/akregator/src/mk4storage/metakit/src/header.h new file mode 100644 index 00000000..2f8648b8 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/header.h @@ -0,0 +1,215 @@ +// header.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * The internal header included in all source files + */ + +#ifndef __HEADER_H__ +#define __HEADER_H__ + +///////////////////////////////////////////////////////////////////////////// + +#include "config.h" + +///////////////////////////////////////////////////////////////////////////// +// A number of preprocessor options are used in the source code +// +// q4_DOS MS-DOS real-mode OS +// q4_MAC Apple Macintosh OS +// q4_UNIX Unix, any flavor +// q4_VMS DEC OpenVMS OS +// q4_WIN Microsoft Windows OS, any flavor +// q4_WIN32 Microsoft Windows OS, 32-bit +// q4_WINCE Microsoft Windows OS, embedded +// +// q4_MFC Microsoft MFC framework +// q4_STD Standard STL version +// q4_UNIV Universal version +// +// q4_BOOL compiler supports bool datatype +// q4_CHECK enable assertion checks +// q4_FIX manual header fix (see above) +// q4_INLINE enable inline expansion +// q4_KITDLL compile as DLL (shared library) +// q4_MULTI compile for multi-threading +// q4_NOLIB do not add automatic lib linkage (MSVC5) +// q4_NO_NS don't use namespaces for STL +// q4_OK assume all software is perfect +// q4_STRICT do not disable any compiler warnings +// q4_TINY small version, no floating point +// +///////////////////////////////////////////////////////////////////////////// + +#define __K4CONF_H__ // skip section in "mk4.h", since we use "header.h" + + // if neither MFC nor STD are specified, default to Universal version +#if !q4_MFC && !q4_STD && !defined (q4_UNIV) +#define q4_UNIV 1 +#endif + +///////////////////////////////////////////////////////////////////////////// +// You can either use '#define q4_xxx 1' to flag the choice of an OS, or +// use a '#define d4_OS_H "file.h"' to force inclusion of a header later. + +#if defined (__MINGW32__) +#define d4_OS_H "win.h" +#elif defined (MSDOS) && defined (__GNUC__) +#define q4_DOS 1 +#elif defined(unix) || defined(__unix__) || defined(__GNUC__) || \ + defined(_AIX) || defined(__hpux) +#define q4_UNIX 1 +#elif defined (__VMS) +#define q4_VMS 1 +#elif defined (macintosh) +#define q4_MAC 1 +#elif !defined (d4_OS_H) +#define d4_OS_H "win.h" +#endif + +///////////////////////////////////////////////////////////////////////////// +// Use '#define q4_xxx 1' to flag the choice of a CPU. + +#if defined (_M_I86) || defined (_M_IX86) || defined (i386) +#define q4_I86 1 +#if defined (_M_I86SM) +#define q4_TINY 1 +#endif +#elif defined (__powerc) +#define q4_PPC 1 +#elif defined (__alpha) +#define q4_AXP 1 +#define q4_LONG64 1 +#elif defined (__VMS) +#define q4_VAX 1 +#else +#define q4_M68K 1 +#endif + +///////////////////////////////////////////////////////////////////////////// +// Use '#define q4_xxx 1' to flag the choice of an IDE, and optionally also +// add '#include "file.h"' to force inclusion of a header file right here. + +#if defined (__BORLANDC__) // Borland C++ +#include "borc.h" +#elif defined (__DECCXX) // DEC C++ +#define q4_DECC 1 +#elif defined (__GNUC__) // GNU C++ +#include "gnuc.h" +#elif defined (__MWERKS__) // Metrowerks CodeWarrior C++ +#include "mwcw.h" +#elif defined (_MSC_VER) // Microsoft Visual C++ +#include "msvc.h" +#elif defined (__SC__) // Symantec C++ +#define q4_SYMC 1 +#elif defined (__WATCOMC__) // Watcom C++ +#define q4_WATC 1 +#endif + +///////////////////////////////////////////////////////////////////////////// +// Some of the options take precedence over others + +#if !q4_BOOL && !q4_STD // define a bool datatype +#define false 0 +#define true 1 +#define bool int +#endif + +#if !q4_CHECK // disable assertions +#undef d4_assert +#define d4_dbgdef(x) +#define d4_assert(x) +#endif + +#if q4_NO_NS // don't use namespaces +#define d4_std +#else +#define d4_std std +#endif + +#if HAVE_MEMMOVE +#define d4_memmove(d,s,n) memmove(d,s,n) +#elif HAVE_BCOPY +#define d4_memmove(d,s,n) bcopy(s,d,n) +#else +#define d4_memmove f4_memmove +extern void f4_memmove(void* d, const void* s, int n); +#endif + +typedef unsigned char t4_byte; // create typedefs for t4_byte, etc. + +///////////////////////////////////////////////////////////////////////////// +// Include header files which contain additional os/cpu/ide/fw specifics + +#ifdef d4_OS_H // operating system dependencies +#include d4_OS_H +#endif + +///////////////////////////////////////////////////////////////////////////// +// Several defines should always be set + +#ifndef d4_assert // assertion macro +#include <assert.h> +#define d4_assert assert +#endif + +#ifndef d4_dbgdef // conditionally compiled +#ifdef NDEBUG +#define d4_dbgdef(x) +#else +#define d4_dbgdef(x) x +#endif +#endif + +#ifndef d4_new // heap allocator +#define d4_new new +#endif + +#ifndef d4_reentrant // thread-local storage +#define d4_reentrant +#endif + +///////////////////////////////////////////////////////////////////////////// +// Debug logging option, called internally where properties are modified + +#if q4_LOGPROPMODS +void f4_DoLogProp(const c4_Handler*, int, const char*, int); +#else +#define f4_LogPropMods(a,b) 0 +#endif + +///////////////////////////////////////////////////////////////////////////// +// Public definitions, plus a few more framework-specific ones + +#include "mk4.h" + +#if q4_MFC +#include "mfc.h" +#elif q4_STD +#include "std.h" +#elif q4_UNIV +#include "univ.h" +#endif + +#ifdef _MSC_VER +#pragma warning(disable: 4100 4127 4135 4244 4511 4512 4514) +#endif + +#include <string.h> + +///////////////////////////////////////////////////////////////////////////// +// Report unexpected combinations of settings + +#if !q4_FIX +#if (q4_DOS+q4_MAC+q4_UNIX+q4_VMS+q4_WIN) != 1 +#error Exactly one operating system should have been defined +#endif +#if (q4_MFC+q4_STD+q4_UNIV) != 1 +#error Exactly one container library should have been defined +#endif +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/mfc.h b/akregator/src/mk4storage/metakit/src/mfc.h new file mode 100644 index 00000000..6e6d3e24 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/mfc.h @@ -0,0 +1,34 @@ +// mfc.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Configuration header for MFC-based builds + */ + +#define q4_MFC 1 + +#if q4_WIN && !q4_WIN32 +#include <afxwin.h> +#else +#include <afxcoll.h> +#endif + +#undef d4_assert +#define d4_assert ASSERT + +#undef d4_assertThis +#define d4_assertThis d4_assert(AfxIsValidAddress(this, sizeof *this, FALSE)) + +#undef d4_new +#define d4_new DEBUG_NEW + +typedef class CString c4_String; +typedef class CPtrArray c4_PtrArray; +typedef class CDWordArray c4_DWordArray; +typedef class CStringArray c4_StringArray; + + // MSVC 1.52 thinks a typedef has no constructor, so use a define instead +#if !q4_OK && q4_MSVC && _MSC_VER == 800 +#define c4_String CString +#endif diff --git a/akregator/src/mk4storage/metakit/src/msvc.h b/akregator/src/mk4storage/metakit/src/msvc.h new file mode 100644 index 00000000..a53e8b6d --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/msvc.h @@ -0,0 +1,39 @@ +// msvc.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Configuration header for Microsoft Visual C++ + */ + +#define q4_MSVC 1 + + // get rid of several common warning messages +#if !q4_STRICT +//#pragma warning(disable: 4244) // conversion ..., possible loss of data +//#pragma warning(disable: 4135) // conversion between diff. integral types +//#pragma warning(disable: 4511) // copy constructor could not be generated +//#pragma warning(disable: 4512) // assignment op could not be generated +//#pragma warning(disable: 4514) // unreferenced inline removed +#pragma warning(disable: 4710) // function ... not inlined +#pragma warning(disable: 4711) // ... selected for automatic inline expansion +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif + +#if _MSC_VER >= 1100 +#define q4_BOOL 1 // 5.0 supports the bool datatype +#else +#define q4_NO_NS 1 // 4.x doesn't use namespaces for STL +#endif + +#if defined (_MT) +#define q4_MULTI 1 // uses multi-threading +#endif + +#if defined (_DEBUG) && !defined (q4_CHECK) // use assertions in debug build +#define q4_CHECK 1 +#endif + +#if !q4_STD && !q4_UNIV && !defined (q4_MFC) +#define d4_FW_H "mfc.h" // default for MSVC is to use MFC +#endif diff --git a/akregator/src/mk4storage/metakit/src/mwcw.h b/akregator/src/mk4storage/metakit/src/mwcw.h new file mode 100644 index 00000000..1c863b96 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/mwcw.h @@ -0,0 +1,31 @@ +// mwcw.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Configuration header for Metrowerks CodeWarrior + */ + +#define q4_MWCW 1 + +///////////////////////////////////////////////////////////////////////////// + +#if q4_68K +#if !__option(IEEEdoubles) +#error Cannot build Metakit with 10-byte doubles +#endif +#endif + +#if __option(bool) +#define q4_BOOL 1 + // undo previous defaults, because q4_BOOL is not set early enough +#undef false +#undef true +#undef bool +#endif + +#undef _MSC_VER + +#pragma export on + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/persist.cpp b/akregator/src/mk4storage/metakit/src/persist.cpp new file mode 100644 index 00000000..65a9e94e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/persist.cpp @@ -0,0 +1,1185 @@ +// persist.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Implementation of the main file management classes + */ + +#include "header.h" +#include "column.h" +#include "persist.h" +#include "handler.h" +#include "store.h" +#include "field.h" + +///////////////////////////////////////////////////////////////////////////// + +class c4_FileMark +{ + enum { + kStorageFormat = 0x4C4A, // b0 = 'J', b1 = <4C> (on Intel) + kReverseFormat = 0x4A4C // b0 = <4C>, b1 = 'J' + }; + + t4_byte _data [8]; + +public: + c4_FileMark (); + c4_FileMark (t4_i32 pos_, bool flipped_, bool extend_); + c4_FileMark (t4_i32 pos_, int len_); + + t4_i32 Offset() const; + t4_i32 OldOffset() const; + + bool IsHeader() const; + bool IsOldHeader() const; + bool IsFlipped() const; +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_FileMark::c4_FileMark () +{ + d4_assert(sizeof *this == 8); +} + +c4_FileMark::c4_FileMark (t4_i32 pos_, bool flipped_, bool extend_) +{ + d4_assert(sizeof *this == 8); + *(short*) _data = flipped_ ? kReverseFormat : kStorageFormat; + _data[2] = extend_ ? 0x0A : 0x1A; + _data[3] = 0; + t4_byte* p = _data + 4; + for (int i = 24; i >= 0; i -= 8) + *p++ = (t4_byte) (pos_ >> i); + d4_assert(p == _data + sizeof _data); +} + +c4_FileMark::c4_FileMark (t4_i32 pos_, int len_) +{ + d4_assert(sizeof *this == 8); + t4_byte* p = _data; + *p++ = 0x80; + for (int j = 16; j >= 0; j -= 8) + *p++ = (t4_byte) (len_ >> j); + for (int i = 24; i >= 0; i -= 8) + *p++ = (t4_byte) (pos_ >> i); + d4_assert(p == _data + sizeof _data); +} + +t4_i32 c4_FileMark::Offset() const +{ + t4_i32 v = 0; + for (int i = 4; i < 8; ++i) + v = (v << 8) + _data[i]; + return v; +} + +t4_i32 c4_FileMark::OldOffset() const +{ + t4_i32 v = 0; + for (int i = 8; --i >= 4; ) + v = (v << 8) + _data[i]; + return v; +} + +bool c4_FileMark::IsHeader() const +{ + return (_data[0] == 'J' || _data[0] == 'L') && + (_data[0] ^ _data[1]) == ('J' ^ 'L') && _data[2] == 0x1A; +} + +bool c4_FileMark::IsOldHeader() const +{ + return IsHeader() && _data[3] == 0x80; +} + +bool c4_FileMark::IsFlipped() const +{ + return *(short*) _data == kReverseFormat; + +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_Allocator : public c4_DWordArray +{ +public: + c4_Allocator (); + + void Initialize(t4_i32 first_ =1); + + t4_i32 AllocationLimit() const; + + t4_i32 Allocate(t4_i32 len_); + void Occupy(t4_i32 pos_, t4_i32 len_); + void Release(t4_i32 pos_, t4_i32 len_); + void Dump(const char* str_); + +private: + int Locate(t4_i32 pos_) const; + void InsertPair(int i_, t4_i32 from_, t4_i32 to_); + t4_i32 ReduceFrags(int goal_, int sHi_, int sLo_); +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Allocation of blocks is maintained in a separate data structure. +// There is no allocation overhead in the allocation arena itself. +// +// A single vector of "walls" is maintained, sorted by position: +// +// * Each transition between free and allocated is a single entry. +// The number of entries is <num-free-ranges> + <num-used-ranges>. +// * By definition, free areas start at the positions indicated +// by the entries on even indices. Allocated ones use odd entries. +// * There is an extra <0,0> free slot at the very beginning. This +// simplifies boundary conditions at the start of the arena. +// * Position zero cannot be allocated, first slot starts at 1. +// +// Properties of this approach: +// +// * No allocation overhead for adjacent allocated areas. On the +// other hand, the allocator does not know the size of used slots. +// * Alternate function allows marking a specific range as occupied. +// * Allocator can be initialized as either all free or all in-use. +// * Allocation info contains only integers, it could be stored. +// * To extend allocated slots: "occupy" extra bytes at the end. +// * Generic: can be used for memory, disk files, and array entries. + +c4_Allocator::c4_Allocator () +{ + Initialize(); +} + +void c4_Allocator::Initialize(t4_i32 first_) +{ + SetSize(0, 1000); // empty, and growing in large chunks + Add(0); // fake block at start + Add(0); // ... only used to avoid merging + + // if occupied, add a tiny free slot at the end, else add entire range + const t4_i32 kMaxInt = 0x7fffffff; + if (first_ == 0) + first_ = kMaxInt; + + Add(first_); // start at a nicely aligned position + Add(kMaxInt); // ... there is no limit on file size +} + +t4_i32 c4_Allocator::Allocate(t4_i32 len_) +{ + // zero arg is ok, it simply returns first allocatable position + for (int i = 2; i < GetSize(); i += 2) + if (GetAt(i+1) >= GetAt(i) + len_) { + t4_i32 pos = GetAt(i); + if ((t4_i32) GetAt(i+1) > pos + len_) + ElementAt(i) += len_; + else + RemoveAt(i, 2); + return pos; + } + + d4_assert(0); + return 0; // not reached +} + +void c4_Allocator::Occupy(t4_i32 pos_, t4_i32 len_) +{ + d4_assert(pos_ > 0); + // note that zero size simply checks if there is any space to extend + + int i = Locate(pos_); + d4_assert(0 < i && i < GetSize()); + + if (i % 2) { // allocation is not at start of free block + d4_assert((t4_i32) GetAt(i-1) < pos_); + + if ((t4_i32) GetAt(i) == pos_ + len_) // allocate from end of free block + SetAt(i, pos_); + else // split free block in two + InsertPair(i, pos_, pos_ + len_); + } + else if ((t4_i32) GetAt(i) == pos_) +/* + This side of the if used to be unconditional, but that was + incorrect if ReduceFrags gets called (which only happens with + severely fragmented files) - there are cases when allocation + leads to an occupy request of which the free space list knows + nothing about because it dropped small segments. The solution + is to silently "allow" such allocations - fixed 29-02-2000 + Thanks to Andrew Kuchling for his help in chasing this bug. +*/ + { // else extend tail of allocated area + if ((t4_i32) GetAt(i+1) > pos_ + len_) + ElementAt(i) += len_; // move start of next free up + else + RemoveAt(i, 2); // remove this slot + } +} + +void c4_Allocator::Release(t4_i32 pos, t4_i32 len) +{ + int i = Locate(pos + len); + d4_assert(0 < i && i < GetSize()); + d4_assert(i % 2 == 0); // don't release inside a free block + + if ((t4_i32) GetAt(i) == pos) // move start of next free down + ElementAt(i) -= len; + else if ((t4_i32) GetAt(i-1) == pos) // move end of previous free up + ElementAt(i-1) += len; + else // insert a new entry + InsertPair(i, pos, pos + len); + + if (GetAt(i-1) == GetAt(i)) // merge if adjacent free + RemoveAt(i-1, 2); +} + +t4_i32 c4_Allocator::AllocationLimit() const +{ + d4_assert(GetSize() >= 2); + + return GetAt(GetSize() - 2); +} + +int c4_Allocator::Locate(t4_i32 pos) const +{ + int lo = 0, hi = GetSize() - 1; + + while (lo < hi) { + int i = (lo + hi) / 2; + if (pos < (t4_i32) GetAt(i)) + hi = i - 1; + else if (pos > (t4_i32) GetAt(i)) + lo = i + 1; + else + return i; + } + + return lo < GetSize() && pos > (t4_i32) GetAt(lo) ? lo + 1 : lo; +} + +void c4_Allocator::InsertPair(int i_, t4_i32 from_, t4_i32 to_) +{ + d4_assert(0 < i_); + d4_assert(i_ < GetSize()); + + d4_assert(from_ < to_); + d4_assert((t4_i32) GetAt(i_-1) < from_); + //!d4_assert(to_ < GetAt(i_)); + + if (to_ >= (t4_i32) GetAt(i_)) + return; // ignore 2nd allocation of used area + + InsertAt(i_, from_, 2); + SetAt(i_+1, to_); + + // it's ok to have arrays up to some 30000 bytes + if (GetSize() > 7500) + ReduceFrags(5000, 12, 6); +} + +t4_i32 c4_Allocator::ReduceFrags(int goal_, int sHi_, int sLo_) +{ + // drastic fail-safe measure: remove small gaps if vec gets too long + // this will cause some lost free space but avoids array overflow + // the lost space will most probably be re-used after the next commit + + int limit = GetSize() - 2; + t4_i32 loss = 0; + + // go through all entries and remove gaps under the given threshold + for (int shift = sHi_; shift >= sLo_; --shift) { + // the threshold is a fraction of the current size of the arena + t4_i32 threshold = AllocationLimit() >> shift; + if (threshold == 0) + continue; + + int n = 2; + for (int i = n; i < limit; i += 2) + if ((t4_i32) GetAt(i+1) - (t4_i32) GetAt(i) > threshold) { + SetAt(n++, GetAt(i)); + SetAt(n++, GetAt(i+1)); + } + else + loss += GetAt(i+1) - GetAt(i); + + limit = n; + + // if (GetSize() < goal_) - suboptimal, fixed 29-02-2000 + if (limit < goal_) + break; // got rid of enough entries, that's enough + } + + int n = GetSize() - 2; + SetAt(limit++, GetAt(n++)); + SetAt(limit++, GetAt(n)); + SetSize(limit); + + return loss; +} + +#if q4_CHECK +#include <stdio.h> + +void c4_Allocator::Dump(const char* str_) +{ + fprintf(stderr, "c4_Allocator::Dump, %d entries <%s>\n", GetSize(), str_); + for (int i = 2; i < GetSize(); i += 2) + fprintf(stderr, " %10ld .. %ld\n", GetAt(i-1), GetAt(i)); + fprintf(stderr, "END\n"); +} + +#else + +void c4_Allocator::Dump(const char* str_) { } + +#endif + +///////////////////////////////////////////////////////////////////////////// + +class c4_Differ +{ +public: + c4_Differ (c4_Storage& storage_); + ~c4_Differ (); + + int NewDiffID(); + void CreateDiff(int id_, c4_Column& col_); + t4_i32 BaseOfDiff(int id_); + void ApplyDiff(int id_, c4_Column& col_) const; + + void GetRoot(c4_Bytes& buffer_); + + c4_Storage _storage; + c4_View _diffs; + c4_View _temp; + +private: + void AddEntry(t4_i32, t4_i32, const c4_Bytes&); + + c4_ViewProp pCols; // column info: + c4_IntProp pOrig; // original position + c4_ViewProp pDiff; // difference chunks: + c4_IntProp pKeep; // offset + c4_IntProp pResize; // length + c4_BytesProp pBytes; // data +}; + +c4_Differ::c4_Differ (c4_Storage& storage_) + : _storage (storage_), pCols ("_C"), pOrig ("_O"), + pDiff ("_D"), pKeep ("_K"), pResize ("_R"), pBytes ("_B") +{ + // weird names, to avoid clashing with existing ones (capitalization!) + _diffs = _storage.GetAs("_C[_O:I,_D[_K:I,_R:I,_B:B]]"); +} + +c4_Differ::~c4_Differ () +{ + _diffs = c4_View (); +} + +void c4_Differ::AddEntry(t4_i32 off_, t4_i32 len_, const c4_Bytes& data_) +{ + int n = _temp.GetSize(); + _temp.SetSize(n + 1); + c4_RowRef r = _temp[n]; + + pKeep (r) = (t4_i32) off_; + pResize (r) = (t4_i32) len_; + pBytes (r).SetData(data_); +} + +int c4_Differ::NewDiffID() +{ + int n = _diffs.GetSize(); + _diffs.SetSize(n + 1); + return n; +} + +void c4_Differ::CreateDiff(int id_, c4_Column& col_) +{ + _temp.SetSize(0); +#if 0 + t4_i32 offset = 0; + t4_i32 savedOff = 0; + t4_i32 savedLen = 0; + + c4_Strategy* strat = col_.Persist() != 0 ? &col_.Strategy() : 0; + + c4_ColIter iter (col_, 0, col_.ColSize()); + while (iter.Next()) { + const t4_byte* p = iter.BufLoad(); + if (strat != 0 && strat->_mapStart != 0 && p >= strat->_mapStart && + p - strat->_mapStart < strat->_dataSize) + { + t4_i32 nextOff = p - strat->_mapStart; + if (savedLen == 0) + savedOff = nextOff; + if (nextOff == savedOff + savedLen) { + savedLen += iter.BufLen(); + continue; + } + + if (savedLen > 0) + AddEntry(savedOff, savedLen, c4_Bytes ()); + + savedOff = nextOff; + savedLen = iter.BufLen(); + } else { + AddEntry(savedOff, savedLen, c4_Bytes (p, iter.BufLen())); + savedLen = 0; + } + + offset += iter.BufLen(); + } + + c4_View diff = pDiff (_diffs[id_]); + if (_temp.GetSize() != diff.GetSize() || _temp != diff) +#else + c4_Bytes t1; + const t4_byte* p = col_.FetchBytes(0, col_.ColSize(), t1, false); + AddEntry(0, 0, c4_Bytes (p, col_.ColSize())); +#endif + pDiff (_diffs[id_]) = _temp; + + pOrig (_diffs[id_]) = col_.Position(); +} + +t4_i32 c4_Differ::BaseOfDiff(int id_) +{ + d4_assert(0 <= id_ && id_ < _diffs.GetSize()); + + return pOrig (_diffs[id_]); +} + +void c4_Differ::ApplyDiff(int id_, c4_Column& col_) const +{ + d4_assert(0 <= id_ && id_ < _diffs.GetSize()); + + c4_View diff = pDiff (_diffs[id_]); + t4_i32 offset = 0; + + for (int n = 0; n < diff.GetSize(); ++n) { + c4_RowRef row (diff[n]); + offset += pKeep (row); + + c4_Bytes data; + pBytes(row).GetData(data); + + // the following code is a lot like c4_MemoRef::Modify + const t4_i32 change = pResize (row); + if (change < 0) + col_.Shrink(offset, -change); + else if (change > 0) + col_.Grow(offset, change); + + col_.StoreBytes(offset, data); + offset += data.Size(); + } + + if (offset > col_.ColSize()) + col_.Shrink(offset, offset - col_.ColSize()); +} + +void c4_Differ::GetRoot(c4_Bytes& buffer_) +{ + int last = _diffs.GetSize() - 1; + if (last >= 0) { + c4_Bytes temp; + c4_View diff = pDiff (_diffs[last]); + if (diff.GetSize() > 0) + pBytes (diff[0]).GetData(buffer_); + } +} + +///////////////////////////////////////////////////////////////////////////// + +c4_SaveContext::c4_SaveContext (c4_Strategy& strategy_, bool fullScan_, + int mode_, c4_Differ* differ_, c4_Allocator* space_) + : _strategy (strategy_), _walk (0), _differ (differ_), _space (space_), + _cleanup (0), _nextSpace (0), _preflight (true), _fullScan (fullScan_), + _mode (mode_), _nextPosIndex (0), _bufPtr (_buffer), _curr (_buffer), + _limit (_buffer) +{ + if (_space == 0) + _space = _cleanup = d4_new c4_Allocator; + + _nextSpace = _mode == 1 ? d4_new c4_Allocator : _space; +} + +c4_SaveContext::~c4_SaveContext () +{ + delete _cleanup; + if (_nextSpace != _space) + delete _nextSpace; +} + +bool c4_SaveContext::IsFlipped() const +{ + return _strategy._bytesFlipped; +} + +bool c4_SaveContext::Serializing() const +{ + return _fullScan; +} + +void c4_SaveContext::AllocDump(const char* str_, bool next_) +{ + c4_Allocator* ap = next_ ? _nextSpace : _space; + if (ap != 0) + ap->Dump(str_); +} + +void c4_SaveContext::FlushBuffer() +{ + int n = _curr - _bufPtr; + if (_walk != 0 && n > 0) { + t4_i32 end = _walk->ColSize(); + _walk->Grow(end, n); + _walk->StoreBytes(end, c4_Bytes (_bufPtr, n)); + } + + _curr = _bufPtr = _buffer; + _limit = _buffer + sizeof _buffer; +} + +c4_Column* c4_SaveContext::SetWalkBuffer(c4_Column* col_) +{ + FlushBuffer(); + + c4_Column* prev = _walk; + _walk = col_; + return prev; +} + +void c4_SaveContext::Write(const void* buf_, int len_) +{ + // use buffering if possible + if (_curr + len_ <= _limit) { + memcpy(_curr, buf_, len_); + _curr += len_; + } else { + FlushBuffer(); + _bufPtr = (t4_byte*) buf_; // also loses const + _curr = _limit = _bufPtr + len_; + FlushBuffer(); + } +} + +void c4_SaveContext::StoreValue(t4_i32 v_) +{ + if (_walk == 0) + return; + + if (_curr + 10 >= _limit) + FlushBuffer(); + + d4_assert(_curr + 10 < _limit); + c4_Column::PushValue(_curr, v_); +} + +void c4_SaveContext::SaveIt(c4_HandlerSeq& root_, c4_Allocator** spacePtr_, + c4_Bytes& rootWalk_) +{ + d4_assert(_space != 0); + + const t4_i32 size = _strategy.FileSize(); + if (_strategy._failure != 0) + return; + + const t4_i32 end = _fullScan ? 0 : size - _strategy._baseOffset; + + if (_differ == 0) { + if (_mode != 1) + _space->Initialize(); + + // don't allocate anything inside the file in extend mode + if (_mode == 2 && end > 0) { + _space->Occupy(1, end - 1); + _nextSpace->Occupy(1, end - 1); + } + + // the header is always reserved + _space->Occupy(1, 7); + _nextSpace->Occupy(1, 7); + + if (end > 0) { + d4_assert(end >= 16); + _space->Occupy(end - 16, 16); + _nextSpace->Occupy(end - 16, 16); + _space->Occupy(end, 8); + _nextSpace->Occupy(end, 8); + } + } + + //AllocDump("a1", false); + //AllocDump("a2", true); + + // first pass allocates columns and constructs shallow walks + c4_Column walk (root_.Persist()); + SetWalkBuffer(&walk); + CommitSequence(root_, true); + SetWalkBuffer(0); + CommitColumn(walk); + + c4_Bytes tempWalk; + walk.FetchBytes(0, walk.ColSize(), tempWalk, true); + + t4_i32 limit = _nextSpace->AllocationLimit(); + d4_assert(limit >= 8 || _differ != 0); + + bool changed = _fullScan || tempWalk != rootWalk_; + + rootWalk_ = c4_Bytes (tempWalk.Contents(), tempWalk.Size(), true); + + _preflight = false; + + // special-case to avoid saving data if file is logically empty + // in that case, the data is 0x80 0x81 0x80 (plus the header) + if (!_fullScan && limit <= 11 && _differ == 0) { + _space->Initialize(); + _nextSpace->Initialize(); + changed = false; + } + + if (!changed) + return; + + //AllocDump("b1", false); + //AllocDump("b2", true); + + if (_differ != 0) { + int n = _differ->NewDiffID(); + _differ->CreateDiff(n, walk); + return; + } + + d4_assert(_mode != 0 || _fullScan); + + // this is the place where writing may start + + // figure out where the new file ends and write a skip tail there + t4_i32 end0 = end; + + // true if the file need not be extended due to internal free space + bool inPlace = end0 == limit - 8; + if (inPlace) { + d4_assert(!_fullScan); + _space->Release(end0, 8); + _nextSpace->Release(end0, 8); + end0 -= 16; // overwrite existing tail markers + } else { + c4_FileMark head (limit + 16 - end, _strategy._bytesFlipped, end > 0); + _strategy.DataWrite(end, &head, sizeof head); + + if (end0 < limit) + end0 = limit; // create a gap + } + + t4_i32 end1 = end0 + 8; + t4_i32 end2 = end1 + 8; + + if (!_fullScan && !inPlace) { + c4_FileMark mark1 (end0, 0); + _strategy.DataWrite(end0, &mark1, sizeof mark1); +#if q4_WIN32 + /* March 8, 2002 + * On at least NT4 with NTFS, extending a file can cause it to be + * rounded up further than expected. To prevent creating a bad + * file (since the file does then not end with a marker), the + * workaround it so simply accept the new end instead and rewrite. + * Note that between these two writes, the file is in a bad state. + */ + t4_i32 realend = _strategy.FileSize() - _strategy._baseOffset; + if (realend > end1) { + end0 = limit = realend - 8; + end1 = realend; + end2 = realend + 8; + c4_FileMark mark1a (end0, 0); + _strategy.DataWrite(end0, &mark1a, sizeof mark1a); + } +#endif + d4_assert(_strategy.FileSize() == _strategy._baseOffset + end1); + } + + _space->Occupy(end0, 16); + _nextSpace->Occupy(end0, 16); + + // strategy.DataCommit(0); // may be needed, need more info on how FS's work + // but this would need more work, since we can't adjust file-mapping here + + // second pass saves the columns and structure to disk + CommitSequence(root_, true); // writes changed columns + CommitColumn(walk); + + //! d4_assert(_curr == 0); + d4_assert(_nextPosIndex == _newPositions.GetSize()); + + if (_fullScan) { + c4_FileMark mark1 (limit, 0); + _strategy.DataWrite(_strategy.FileSize() - _strategy._baseOffset, + &mark1, sizeof mark1); + + c4_FileMark mark2 (limit - walk.ColSize(), walk.ColSize()); + _strategy.DataWrite(_strategy.FileSize() - _strategy._baseOffset, + &mark2, sizeof mark2); + + return; + } + + if (inPlace) + d4_assert(_strategy.FileSize() == _strategy._baseOffset + end2); + else { + // make sure the allocated size hasn't changed + d4_assert(_nextSpace->AllocationLimit() == limit + 16); + d4_assert(end0 >= limit); + d4_assert(_strategy.FileSize() - _strategy._baseOffset == end1); + } + + if (walk.Position() == 0 || _strategy._failure != 0) + return; + + _strategy.DataCommit(0); + + c4_FileMark mark2 (walk.Position(), walk.ColSize()); + _strategy.DataWrite(end1, &mark2, sizeof mark2); + d4_assert(_strategy.FileSize() - _strategy._baseOffset == end2); + + // do not alter the file header in extend mode, unless it is new + if (!_fullScan && (_mode == 1 || end == 0)) { + _strategy.DataCommit(0); + + c4_FileMark head (end2, _strategy._bytesFlipped, false); + d4_assert(head.IsHeader()); + _strategy.DataWrite(0, &head, sizeof head); + + // if the file became smaller, we could shrink it + if (limit + 16 < end0) { +/* + Not yet, this depends on the strategy class being able to truncate, but + there is no way to find out whether it does (the solution is to write tail + markers in such a way that the file won't grow unnecessarily if it doesn't). + + The logic will probably be: + + * write new skip + commit "tails" at limit (no visible effect on file) + * overwrite commit tail at end with a skip to this new one (equivalent) + * replace header with one pointing to that internal new one (equivalent) + * flush (now the file is valid both truncated and not-yet-truncated + + end = limit; +*/ + } + } + + // if using memory mapped files, make sure the map is no longer in use + if (_strategy._mapStart != 0) + root_.UnmappedAll(); + + // commit and tell strategy object what the new file size is, this + // may be smaller now, if old data at the end is no longer referenced + _strategy.DataCommit(end2); + + d4_assert(_strategy.FileSize() - _strategy._baseOffset == end2); + + if (spacePtr_ != 0 && _space != _nextSpace) { + d4_assert(*spacePtr_ == _space); + delete *spacePtr_; + *spacePtr_ = _nextSpace; + _nextSpace = 0; + } +} + +bool c4_SaveContext::CommitColumn(c4_Column& col_) +{ + bool changed = col_.IsDirty() || _fullScan; + + t4_i32 sz = col_.ColSize(); + StoreValue(sz); + if (sz > 0) { + t4_i32 pos = col_.Position(); + + if (_differ) { + if (changed) { + int n = pos < 0 ? ~pos : _differ->NewDiffID(); + _differ->CreateDiff(n, col_); + + d4_assert(n >= 0); + pos = ~n; + } + } else if (_preflight) { + if (changed) + pos = _space->Allocate(sz); + + _nextSpace->Occupy(pos, sz); + _newPositions.Add(pos); + } else { + pos = _newPositions.GetAt(_nextPosIndex++); + + if (changed) + col_.SaveNow(_strategy, pos); + + if (!_fullScan) + col_.SetLocation(pos, sz); + } + + StoreValue(pos); + } + + return changed; +} + +void c4_SaveContext::CommitSequence(c4_HandlerSeq& seq_, bool selfDesc_) +{ + StoreValue(0); // sias prefix + + if (selfDesc_) { + c4_String desc = seq_.Description(); + int k = desc.GetLength(); + StoreValue(k); + Write((const char*) desc, k); + } + + StoreValue(seq_.NumRows()); + if (seq_.NumRows() > 0) + for (int i = 0; i < seq_.NumFields(); ++i) + seq_.NthHandler(i).Commit(*this); +} + +///////////////////////////////////////////////////////////////////////////// + + // used for on-the-fly conversion of old-format datafiles + t4_byte* _oldBuf; + const t4_byte* _oldCurr; + const t4_byte* _oldLimit; + t4_i32 _oldSeek; + + +c4_Persist::c4_Persist (c4_Strategy& strategy_, bool owned_, int mode_) + : _space (0), _strategy (strategy_), _root (0), _differ (0), + _fCommit (0), _mode (mode_), _owned (owned_), _oldBuf (0), + _oldCurr (0), _oldLimit (0), _oldSeek (-1) +{ + if (_mode == 1) + _space = d4_new c4_Allocator; +} + +c4_Persist::~c4_Persist () +{ + delete _differ; + + if (_owned) { + if (_root != 0) + _root->UnmappedAll(); + delete &_strategy; + } + + delete _space; + + if (_oldBuf != 0) + delete [] _oldBuf; +} + +c4_HandlerSeq& c4_Persist::Root() const +{ + d4_assert(_root != 0); + return *_root; +} + +void c4_Persist::SetRoot(c4_HandlerSeq* root_) +{ + d4_assert(_root == 0); + _root = root_; +} + +c4_Strategy& c4_Persist::Strategy() const +{ + return _strategy; +} + +bool c4_Persist::AutoCommit(bool flag_) +{ + bool prev = _fCommit != 0; + if (flag_) + _fCommit = &c4_Persist::Commit; + else + _fCommit = 0; + return prev; +} + +void c4_Persist::DoAutoCommit() +{ + if (_fCommit != 0) + (this->*_fCommit)(false); +} + +bool c4_Persist::SetAside(c4_Storage& aside_) +{ + delete _differ; + _differ = d4_new c4_Differ (aside_); + Rollback(false); + return true; //! true if the generation matches +} + +c4_Storage* c4_Persist::GetAside() const +{ + return _differ != 0 ? &_differ->_storage : 0; +} + +bool c4_Persist::Commit(bool full_) +{ + // 1-Mar-1999, new semantics! return success status of commits + _strategy._failure = 0; + + if (!_strategy.IsValid()) + return false; + + if (_mode == 0 && (_differ == 0 || full_)) // can't commit to r/o file + return false; // note that _strategy._failure is *zero* in this case + + c4_SaveContext ar (_strategy, false, _mode, full_ ? 0 : _differ, _space); + + // get rid of temp properties which still use the datafile + if (_mode == 1) + _root->DetachFromStorage(false); + + // 30-3-2001: moved down, fixes "crash every 2nd call of mkdemo/dbg" + ar.SaveIt(*_root, &_space, _rootWalk); + return _strategy._failure == 0; +} + +bool c4_Persist::Rollback(bool full_) +{ + _root->DetachFromParent(); + _root->DetachFromStorage(true); + _root = 0; + + if (_space != 0) + _space->Initialize(); + + c4_HandlerSeq* seq = d4_new c4_HandlerSeq (this); + seq->DefineRoot(); + SetRoot(seq); + + if (full_) { + delete _differ; + _differ = 0; + } + + LoadAll(); + + return _strategy._failure == 0; +} + +bool c4_Persist::LoadIt(c4_Column& walk_) +{ + t4_i32 limit = _strategy.FileSize(); + if (_strategy._failure != 0) + return false; + + if (_strategy.EndOfData(limit) < 0) { + _strategy.SetBase(limit); + d4_assert(_strategy._failure == 0); // file is ok, but empty + return false; + } + + if (_strategy._rootLen > 0) + walk_.SetLocation(_strategy._rootPos, _strategy._rootLen); + + // if the file size has increased, we must remap + if (_strategy._mapStart != 0 && + _strategy.FileSize() > _strategy._baseOffset + _strategy._dataSize) + _strategy.ResetFileMapping(); + + return true; +} + +void c4_Persist::LoadAll() +{ + c4_Column walk (this); + if (!LoadIt(walk)) + return; + + if (_strategy._rootLen < 0) { + _oldSeek = _strategy._rootPos; + _oldBuf = d4_new t4_byte [512]; + _oldCurr = _oldLimit = _oldBuf; + + t4_i32 n = FetchOldValue(); + d4_assert(n == 0); + n = FetchOldValue(); + d4_assert(n > 0); + + c4_Bytes temp; + t4_byte* buf = temp.SetBuffer(n); + d4_dbgdef(int n2 =) + OldRead(buf, n); + d4_assert(n2 == n); + + c4_String s = "[" + c4_String ((const char*) buf, n) + "]"; + const char* desc = s; + + c4_Field* f = d4_new c4_Field (desc); + d4_assert(!*desc); + + //?_root->DefineRoot(); + _root->Restructure(*f, false); + + _root->OldPrepare(); + + // don't touch data inside while converting the file + if (_strategy.FileSize() >= 0) + OccupySpace(1, _strategy.FileSize()); + } else { + walk.FetchBytes(0, walk.ColSize(), _rootWalk, true); + if (_differ) + _differ->GetRoot(_rootWalk); + + // define and fill the root table + const t4_byte* ptr = _rootWalk.Contents(); + _root->Prepare(&ptr, true); + d4_assert(ptr == _rootWalk.Contents() + _rootWalk.Size()); + } +} + +t4_i32 c4_Persist::FetchOldValue() +{ + d4_assert(_oldSeek >= 0); + + if (_oldCurr == _oldLimit) { + int n = OldRead(_oldBuf, 500); + _oldLimit = _oldCurr + n; + _oldBuf[n] = 0x80; // to force end + } + + const t4_byte* p = _oldCurr; + t4_i32 value = c4_Column::PullValue(p); + + if (p > _oldLimit) { + int k = _oldLimit - _oldCurr; + d4_assert(0 < k && k < 10); + memcpy(_oldBuf, _oldCurr, k); + + int n = OldRead(_oldBuf + k, 500); + _oldCurr = _oldBuf + k; + _oldLimit = _oldCurr + n; + _oldBuf[n+k] = 0x80; // to force end + + p = _oldCurr; + value = c4_Column::PullValue(p); + d4_assert(p <= _oldLimit); + } + + _oldCurr = p; + return value; +} + +void c4_Persist::FetchOldLocation(c4_Column& col_) +{ + d4_assert(_oldSeek >= 0); + + t4_i32 sz = FetchOldValue(); + if (sz > 0) + col_.SetLocation(FetchOldValue(), sz); +} + +int c4_Persist::OldRead(t4_byte* buf_, int len_) +{ + d4_assert(_oldSeek >= 0); + + t4_i32 newSeek = _oldSeek + _oldCurr - _oldLimit; + int n = _strategy.DataRead(newSeek, buf_, len_); + d4_assert(n > 0); + _oldSeek = newSeek + n; + _oldCurr = _oldLimit = _oldBuf; + return n; +} + +c4_HandlerSeq* c4_Persist::Load(c4_Stream* stream_) +{ + d4_assert(stream_ != 0); + + c4_FileMark head; + if (stream_->Read(&head, sizeof head) != sizeof head || !head.IsHeader()) + return 0; // no data in file + + //_oldStyle = head._data[3] == 0x80; + d4_assert(!head.IsOldHeader()); + + t4_i32 limit = head.Offset(); + + c4_StreamStrategy* strat = d4_new c4_StreamStrategy (limit); + strat->_bytesFlipped = head.IsFlipped(); + strat->DataWrite(strat->FileSize() - strat->_baseOffset, &head, sizeof head); + + while (strat->FileSize() - strat->_baseOffset < limit) { + char buffer [4096]; + int n = stream_->Read(buffer, sizeof buffer); + d4_assert(n > 0); + strat->DataWrite(strat->FileSize() - strat->_baseOffset, buffer, n); + } + + c4_Persist* pers = d4_new c4_Persist (*strat, true, 0); + c4_HandlerSeq* seq = d4_new c4_HandlerSeq (pers); + seq->DefineRoot(); + pers->SetRoot(seq); + + c4_Column walk (pers); + if (!pers->LoadIt(walk)) { + seq->IncRef(); + seq->DecRef(); // a funny way to delete + return 0; + } + + c4_Bytes tempWalk; + walk.FetchBytes(0, walk.ColSize(), tempWalk, true); + + const t4_byte* ptr = tempWalk.Contents(); + seq->Prepare(&ptr, true); + d4_assert(ptr == tempWalk.Contents() + tempWalk.Size()); + + return seq; +} + +void c4_Persist::Save(c4_Stream* stream_, c4_HandlerSeq& root_) +{ + d4_assert(stream_ != 0); + + c4_StreamStrategy strat (stream_); + + // 31-01-2002: streaming must adopt byte order of origin datafile + c4_Persist* p = root_.Persist(); + if (p != 0) + strat._bytesFlipped = p->Strategy()._bytesFlipped; + + c4_SaveContext ar (strat, true, 0, 0, 0); + c4_Bytes tempWalk; + ar.SaveIt(root_, 0, tempWalk); +} + +t4_i32 c4_Persist::LookupAside(int id_) +{ + d4_assert(_differ != 0); + + return _differ->BaseOfDiff(id_); +} + +void c4_Persist::ApplyAside(int id_, c4_Column& col_) +{ + d4_assert(_differ != 0); + + _differ->ApplyDiff(id_, col_); +} + +void c4_Persist::OccupySpace(t4_i32 pos_, t4_i32 len_) +{ + d4_assert(_mode != 1 || _space != 0); + + if (_space != 0) + _space->Occupy(pos_, len_); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/persist.h b/akregator/src/mk4storage/metakit/src/persist.h new file mode 100644 index 00000000..61ef261e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/persist.h @@ -0,0 +1,127 @@ +// persist.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Definition of the core file management classes + */ + +#ifndef __PERSIST_H__ +#define __PERSIST_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_SaveContext; // wraps file commits + class c4_Persist; // persistent table storage + + class c4_Allocator; // not defined here + class c4_Column; // not defined here + class c4_Differ; // not defined here + class c4_FileMark; // not defined here + class c4_Strategy; // not defined here + class c4_HandlerSeq; // not defined here + +///////////////////////////////////////////////////////////////////////////// + +class c4_SaveContext +{ + c4_Strategy& _strategy; + c4_Column* _walk; + c4_Differ* _differ; + + c4_Allocator* _space; + c4_Allocator* _cleanup; + c4_Allocator* _nextSpace; + + bool _preflight; + bool _fullScan; + int _mode; + + c4_DWordArray _newPositions; + int _nextPosIndex; + + t4_byte* _bufPtr; + t4_byte* _curr; + t4_byte* _limit; + t4_byte _buffer [512]; + +public: + c4_SaveContext (c4_Strategy& strategy_, bool fullScan_, int mode_, + c4_Differ* differ_, c4_Allocator* space_); + ~c4_SaveContext (); + + void SaveIt(c4_HandlerSeq& root_, c4_Allocator** spacePtr_, + c4_Bytes& rootWalk_); + + void StoreValue(t4_i32 v_); + bool CommitColumn(c4_Column& col_); + void CommitSequence(c4_HandlerSeq& seq_, bool selfDesc_); + + c4_Column* SetWalkBuffer(c4_Column* walk_); + bool IsFlipped() const; + + bool Serializing() const; + void AllocDump(const char*, bool =false); + +private: + void FlushBuffer(); + void Write(const void* buf_, int len_); +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_Persist +{ + c4_Allocator* _space; + c4_Strategy& _strategy; + c4_HandlerSeq* _root; + c4_Differ* _differ; + c4_Bytes _rootWalk; + bool (c4_Persist::*_fCommit) (bool); + int _mode; + bool _owned; + + // used for on-the-fly conversion of old-format datafiles + t4_byte* _oldBuf; + const t4_byte* _oldCurr; + const t4_byte* _oldLimit; + t4_i32 _oldSeek; + + int OldRead(t4_byte* buf_, int len_); + +public: + c4_Persist (c4_Strategy&, bool owned_, int mode_); + ~c4_Persist (); + + c4_HandlerSeq& Root() const; + void SetRoot(c4_HandlerSeq* root_); + c4_Strategy& Strategy() const; + + bool AutoCommit(bool =true); + void DoAutoCommit(); + + bool SetAside(c4_Storage& aside_); + c4_Storage* GetAside() const; + + bool Commit(bool full_); + bool Rollback(bool full_); + + bool LoadIt(c4_Column& walk_); + void LoadAll(); + + t4_i32 LookupAside(int id_); + void ApplyAside(int id_, c4_Column& col_); + + void OccupySpace(t4_i32 pos_, t4_i32 len_); + + t4_i32 FetchOldValue(); + void FetchOldLocation(c4_Column& col_); + + static c4_HandlerSeq* Load(c4_Stream*); + static void Save(c4_Stream*, c4_HandlerSeq& root_); +}; + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/remap.cpp b/akregator/src/mk4storage/metakit/src/remap.cpp new file mode 100644 index 00000000..cc8175df --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/remap.cpp @@ -0,0 +1,1154 @@ +// remap.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file +* Mapping and remapping custom viewers +*/ + +#include "header.h" +#include "remap.h" +#include "handler.h" + +///////////////////////////////////////////////////////////////////////////// + +class c4_ReadOnlyViewer : public c4_CustomViewer +{ + c4_View _base; + +public: + c4_ReadOnlyViewer (c4_Sequence& seq_) : _base (&seq_) { } + virtual ~c4_ReadOnlyViewer () { } + + virtual c4_View GetTemplate() { return _base.Clone(); } + virtual int GetSize() { return _base.GetSize(); } + + virtual int Lookup(c4_Cursor key_, int& count_) + { int pos = 0; count_ = _base.GetSize(); + return _base.RestrictSearch(*key_, pos, count_); } + + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_) + { return _base.GetItem(row_, col_, buf_); } +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_HashViewer : public c4_CustomViewer +{ + c4_View _base; + c4_View _map; + int _numKeys; + + c4_IntProp _pHash; + c4_IntProp _pRow; + + bool KeySame(int row_, c4_Cursor cursor_) const; + t4_i32 CalcHash(c4_Cursor cursor_) const; + int LookDict(t4_i32 hash_, c4_Cursor cursor_) const; + void InsertDict(int row_); + void RemoveDict(int pos_); + bool DictResize(int minused); + + int Row(int i_) const { return _pRow (_map[i_]); } + int Hash(int i_) const { return _pHash (_map[i_]); } + + void SetRow(int i_, int v_) { _pRow (_map[i_]) = v_; } + void SetHash(int i_, int v_) { _pHash (_map[i_]) = v_; } + + bool IsUnused(int) const; + bool IsDummy(int) const; + bool IsActive(int i_) const { return Row(i_) >= 0; } + + int GetPoly() const; + void SetPoly(int v_); + int GetSpare() const; + void SetSpare(int v_); + +public: + c4_HashViewer (c4_Sequence& seq_, int numKeys_, + c4_Sequence* map_=0); + virtual ~c4_HashViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual int Lookup(c4_Cursor key_, int& count_); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + virtual bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +///////////////////////////////////////////////////////////////////////////// +// The following contains code derived froms Python's dictionaries, hence: +// Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, +// The Netherlands. +// Reduced and turned into a fast C++ class by Christian Tismer, hence: +// Copyright 1999 by Christian Tismer. +// Vectorized and reorganized further by Jean-Claude Wippler. +///////////////////////////////////////////////////////////////////////////// + +// Table of irreducible polynomials to efficiently cycle through +// GF(2^n)-{0}, 2<=n<=30. + +static long s_polys[] = { + 4 + 3, + 8 + 3, + 16 + 3, + 32 + 5, + 64 + 3, + 128 + 3, + 256 + 29, + 512 + 17, + 1024 + 9, + 2048 + 5, + 4096 + 83, + 8192 + 27, + 16384 + 43, + 32768 + 3, + 65536 + 45, + 131072 + 9, + 262144 + 39, + 524288 + 39, + 1048576 + 9, + 2097152 + 5, + 4194304 + 3, + 8388608 + 33, + 16777216 + 27, + 33554432 + 9, + 67108864 + 71, + 134217728 + 39, + 268435456 + 9, + 536870912 + 5, + 1073741824 + 83, + 0 +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_HashViewer::c4_HashViewer (c4_Sequence& seq_, int numKeys_, + c4_Sequence* map_) + : _base (&seq_), _map (map_), _numKeys (numKeys_), + _pHash ("_H"), _pRow ("_R") +{ + if (_map.GetSize() == 0) + _map.SetSize(1); + + int poly = GetPoly(); + if (poly == 0 || _map.GetSize() <= _base.GetSize()) + DictResize(_base.GetSize()); +} + +c4_HashViewer::~c4_HashViewer () +{ +} + +bool c4_HashViewer::IsUnused(int row_) const +{ + c4_RowRef r = _map[row_]; + return _pRow (r) < 0 && _pHash (r) == 0; +} + +bool c4_HashViewer::IsDummy(int row_) const +{ + c4_RowRef r = _map[row_]; + return _pRow (r) < 0 && _pHash (r) < 0; +} + +int c4_HashViewer::GetPoly() const +{ + return Hash(_map.GetSize()-1); +} + +void c4_HashViewer::SetPoly(int v_) +{ + SetHash(_map.GetSize()-1, v_); +} + +int c4_HashViewer::GetSpare() const +{ + return Row(_map.GetSize()-1); +} + +void c4_HashViewer::SetSpare(int v_) +{ + SetRow(_map.GetSize()-1, v_); +} + +bool c4_HashViewer::KeySame(int row_, c4_Cursor cursor_) const +{ + for (int i = 0; i < _numKeys; ++i) + { + c4_Bytes buffer; + _base.GetItem(row_, i, buffer); + + c4_Handler& h = cursor_._seq->NthHandler(i); + if (h.Compare(cursor_._index, buffer) != 0) + return false; + } + + return true; +} + +/// Create mapped view which is uses a second view for hashing +t4_i32 c4_HashViewer::CalcHash(c4_Cursor cursor_) const +{ + c4_Bytes buffer, buf2; + const t4_i32 endian = 0x03020100; + t4_i32 hash = 0; + + for (int i = 0; i < _numKeys; ++i) + { + c4_Handler& h = cursor_._seq->NthHandler(i); + cursor_._seq->Get(cursor_._index, h.PropId(), buffer); + + // this code borrows from Python's stringobject.c/string_hash() + int len = buffer.Size(); + if (len > 0) + { + const t4_byte* p = buffer.Contents(); + + // 20030218: careful to avoid endian-ness sensitivity + if (*(const t4_byte*) &endian) // true on big-endian systems + switch (h.Property().Type()) + { + case 'I': case 'L': case 'F': case 'D': + { + t4_byte* q = buf2.SetBuffer(len); + for (int j = 0; j < len; ++j) + q[len-j-1] = p[j]; + p = q; + } + } + + long x = *p << 7; + + // modifications are risky, this code avoid scanning huge blobs + if (len > 200) + len = 100; + + while (--len >= 0) + x = (1000003 * x) ^ *p++; + + if (buffer.Size() > 200) + { + len = 100; + p += buffer.Size() - 200; + while (--len >= 0) + x = (1000003 * x) ^ *p++; + } + + x ^= buffer.Size(); + hash ^= x ^ i; + } + } + + if (hash == 0) + hash = -1; + + return hash; +} + +/* + * Types of slots: + * Unused: row = -1, hash = 0 + * Dummy: row = -1, hash = -1 + * Active: row >= 0 + * There must be at least one Unused slot at all times. + */ + +int c4_HashViewer::LookDict(t4_i32 hash_, c4_Cursor cursor_) const +{ + const unsigned int mask = _map.GetSize() - 2; + /* We must come up with (i, incr) such that 0 <= i < _size + and 0 < incr < _size and both are a function of hash */ + int i = mask & ~hash_; + /* We use ~hash_ instead of hash_, as degenerate hash functions, such + as for ints <sigh>, can have lots of leading zeros. It's not + really a performance risk, but better safe than sorry. */ + if (IsUnused(i) || Hash(i) == hash_ && KeySame(Row(i), cursor_)) + return i; + + int freeslot = IsDummy(i) ? i : -1; + + /* Derive incr from hash_, just to make it more arbitrary. Note that + incr must not be 0, or we will get into an infinite loop.*/ + unsigned incr = (hash_ ^ ((unsigned long) hash_ >> 3)) & mask; + if (!incr) + incr = mask; + + int poly = GetPoly(); + for (;;) + { + i = (i+incr) & mask; + if (IsUnused(i)) + break; + if (Hash(i) == hash_ && KeySame(Row(i), cursor_)) + return i; + if (freeslot == -1 && IsDummy(i)) + freeslot = i; + /* Cycle through GF(2^n)-{0} */ + incr = incr << 1; + if (incr > mask) + incr ^= poly; /* This will implicitely clear the highest bit */ + } + + return freeslot != -1 ? freeslot : i; +} + +void c4_HashViewer::InsertDict(int row_) +{ + c4_Cursor cursor = &_base[row_]; + + t4_i32 hash = CalcHash(cursor); + int i = LookDict(hash, cursor); + + if (IsDummy(i)) + { + int n = GetSpare(); + d4_assert(n > 0); + SetSpare(n - 1); + } + + SetHash(i, hash); + SetRow(i, row_); +} + +void c4_HashViewer::RemoveDict(int pos_) +{ + c4_Cursor key = &_base[pos_]; + t4_i32 hash = CalcHash(key); + int i = LookDict(hash, key); + d4_assert(i >= 0); + + d4_assert(Row(i) == pos_); + + SetHash(i, -1); + SetRow(i, -1); + + SetSpare(GetSpare() + 1); +} + +bool c4_HashViewer::DictResize(int minused) +{ + int i, newsize, newpoly; + for (i = 0, newsize = 4; ; i++, newsize <<= 1) { + if (s_polys[i] == 0) + return false; + else if (newsize > minused) { + newpoly = s_polys[i]; + break; + } + } + + _map.SetSize(0); + + c4_Row empty; + _pRow (empty) = -1; + _map.InsertAt(0, empty, newsize + 1); + + SetPoly(newpoly); + SetSpare(0); + + for (int j = 0; j < _base.GetSize(); ++j) + InsertDict(j); + + return true; +} + +c4_View c4_HashViewer::GetTemplate() +{ + return _base.Clone(); +} + +int c4_HashViewer::GetSize() +{ + return _base.GetSize(); +} + +int c4_HashViewer::Lookup(c4_Cursor key_, int& count_) +{ + // can only use hashing if the properties match the query + // XXX it appears that this loop takes some 300 uS! + c4_View kv = (*key_).Container(); + for (int k = 0; k < _numKeys; ++k) + if (kv.FindProperty(_base.NthProperty(k).GetId()) < 0) + return -1; + + t4_i32 hash = CalcHash(key_); // TODO should combine with above loop + int i = LookDict(hash, key_); + + int row = Row(i); + count_ = row >= 0 && KeySame(row, key_) ? 1 : 0; + return count_ ? row : 0; // don't return -1, we *know* it's not there +} + +bool c4_HashViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + return _base.GetItem(row_, col_, buf_); +} + +bool c4_HashViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + if (col_ < _numKeys) + { + c4_Bytes temp; + _base.GetItem(row_, col_, temp); + if (buf_ == temp) + return true; // this call will have no effect, just ignore it + + RemoveDict(row_); + } + + _base.SetItem(row_, col_, buf_); + + if (col_ < _numKeys) + { + // careful if changing a key to one which is already present: + // in that case, delete the other row to preserve uniqueness + // + // Note: this is a tricky and confusing issue, because now the + // mere act of *setting* a property value can *delete* a row! + // + // The big problem here is that setting the rest of the values + // in a loop can end up *wrong*, if the row has moved down!!! + int n; + int i = Lookup(&_base[row_], n); + if (i >= 0 && n > 0) + { + RemoveRows(i, 1); + if (i < row_) + --row_; + } + + InsertDict(row_); + } + + return true; +} + +bool c4_HashViewer::InsertRows(int pos_, c4_Cursor value_, int count_) +{ + d4_assert(count_ > 0); + + int n; + int i = Lookup(value_, n); + if (i >= 0 && n > 0) + { + _base.SetAt(i, *value_); // replace existing + return true; + } + + // adjust row numbers if the insertion is not at the end + // + // TODO this could be optimized to go through the rows which + // were moved up, and then adjusting the map through a lookup + // (probably better than full scan if pos_ is relatively high) + if (pos_ < _base.GetSize()) + { + for (int r = 0; r < _map.GetSize() - 1; ++r) + { + int n2 = Row(r); + if (n2 >= pos_) + SetRow(r, n2 + 1); + } + } + + _base.InsertAt(pos_, *value_); + InsertDict(pos_); + + int used = _base.GetSize(); + int fill = used + GetSpare(); + if (fill * 3 >= (_map.GetSize() - 1) * 2 && !DictResize(used * 2)) + return false; // bail out + + d4_assert(_base.GetSize() + GetSpare() < _map.GetSize() - 1); + return true; +} + +bool c4_HashViewer::RemoveRows(int pos_, int count_) +{ + while (--count_ >= 0) + { + // since the map persists, be somewhat more aggressive than the + // original code in resizing down when the map is getting empty + if (_base.GetSize() * 3 < _map.GetSize() - 1 && + !DictResize(_base.GetSize())) + return false; // bail out + + RemoveDict(pos_); + + // move rows down for now + // + // optionally: consider replacing with last entry (much faster) + for (int r = 0; r < _map.GetSize() - 1; ++r) + { + int n = Row(r); + if (n > pos_) + SetRow(r, n - 1); + } + + _base.RemoveAt(pos_, 1); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_BlockedViewer : public c4_CustomViewer +{ + enum { kLimit = 1000 }; + + c4_View _base; + + c4_ViewProp _pBlock; + c4_DWordArray _offsets; + + int Slot(int& pos_); + void Split(int block_, int row_); + void Merge(int block_); + void Validate() const; + +public: + c4_BlockedViewer (c4_Sequence& seq_); + virtual ~c4_BlockedViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + virtual bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_CHECK + + // debugging version to verify that the internal data is consistent + void c4_BlockedViewer::Validate() const + { + d4_assert(_base.GetSize() >= 2); + + int n = _base.GetSize() - 1; + d4_assert(_offsets.GetSize() == n); + + int total = 0; + for (int i = 0; i < n; i++) + { + c4_View bv = _pBlock (_base[i]); + d4_assert(bv.GetSize() > 0 || i == 0); + total += bv.GetSize(); + d4_assert((int) _offsets.GetAt(i) == total++); + } + + c4_View be = _pBlock (_base[n]); + d4_assert(be.GetSize() == n - 1); + } + +#else + + // nothing, so inline this thing to avoid even the calling overhead + d4_inline void c4_BlockedViewer::Validate() const + { + } + +#endif + +c4_BlockedViewer::c4_BlockedViewer (c4_Sequence& seq_) + : _base (&seq_), _pBlock ("_B") +{ + if (_base.GetSize() < 2) + _base.SetSize(2); + + int n = _base.GetSize() - 1; + _offsets.SetSize(n); + + int total = 0; + for (int i = 0; i < n; i++) + { + c4_View bv = _pBlock (_base[i]); + total += bv.GetSize(); + _offsets.SetAt(i, total++); + } + Validate(); +} + +c4_BlockedViewer::~c4_BlockedViewer () +{ +} + +int c4_BlockedViewer::Slot(int& pos_) +{ + d4_assert(_offsets.GetSize() > 0); + d4_assert(pos_ <= (int) _offsets.GetAt(_offsets.GetSize() - 1)); + +#if 0 + const int n = _offsets.GetSize(); + + int h; + for (h = 0; h < n; ++h) + if (pos_ <= (t4_i32) _offsets.GetAt(h)) + break; +#else + // switch to binary search, adapted from code by Zhang Dehua, 28-3-2002 + // slows down some 5%, but said to be much better with 5 million rows + int l = 0, h = _offsets.GetSize() - 1; + while (l < h) { + int m = l + (h - l) / 2; + if ((t4_i32) _offsets.GetAt(m) < pos_) + l = m + 1; + else + h = m; + } +#endif + + if (h > 0) + pos_ -= _offsets.GetAt(h-1) + 1; + + return h; +} + +void c4_BlockedViewer::Split(int bno_, int row_) +{ + int z = _base.GetSize() - 1; + d4_assert(bno_ < z); + c4_View bz = _pBlock (_base[z]); + c4_View bv = _pBlock (_base[bno_]); + d4_assert(row_ < bv.GetSize()); + + _offsets.InsertAt(bno_, _offsets.GetAt(bno_) - bv.GetSize() + row_); + + _base.InsertAt(bno_+1, c4_Row ()); + c4_View bn = _pBlock (_base[bno_+1]); + + bv.RelocateRows(row_ + 1, -1, bn, 0); + bv.RelocateRows(row_, 1, bz, bno_); + + Validate(); +} + +void c4_BlockedViewer::Merge(int bno_) +{ + int z = _base.GetSize() - 1; + c4_View bz = _pBlock (_base[z]); + c4_View bv1 = _pBlock (_base[bno_]); + c4_View bv2 = _pBlock (_base[bno_+1]); + + _offsets.RemoveAt(bno_); + + bz.RelocateRows(bno_, 1, bv1, -1); + bv2.RelocateRows(0, -1, bv1, -1); + + _base.RemoveAt(bno_+1); + + Validate(); +} + +c4_View c4_BlockedViewer::GetTemplate() +{ + c4_View bv = _pBlock (_base[0]); + return bv.Clone(); +} + +int c4_BlockedViewer::GetSize() +{ + int n = _offsets.GetAt(_offsets.GetSize() - 1); + d4_assert(n >= 0); + return n; +} + +bool c4_BlockedViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + int orig = row_; + + int i = Slot(row_); + d4_assert(0 <= i && i < _base.GetSize() - 1); + + if ((t4_i32) _offsets.GetAt(i) == orig) + { + row_ = i; + i = _base.GetSize() - 1; + } + + c4_View bv = _pBlock (_base[i]); + return bv.GetItem(row_, col_, buf_); +} + +bool c4_BlockedViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + int orig = row_; + + int i = Slot(row_); + d4_assert(0 <= i && i < _base.GetSize() - 1); + + if ((t4_i32) _offsets.GetAt(i) == orig) + { + row_ = i; + i = _base.GetSize() - 1; + } + + c4_View bv = _pBlock (_base[i]); + bv.SetItem(row_, col_, buf_); + return true; +} + +bool c4_BlockedViewer::InsertRows(int pos_, c4_Cursor value_, int count_) +{ + d4_assert(count_ > 0); + + bool atEnd = pos_ == GetSize(); + + int z = _base.GetSize() - 1; + int i = Slot(pos_); + d4_assert(0 <= i && i < z); + + c4_View bv = _pBlock (_base[i]); + d4_assert(0 <= pos_ && pos_ <= bv.GetSize()); + + bv.InsertAt(pos_, *value_, count_); + for (int j = i; j < z; ++j) + _offsets.SetAt(j, _offsets.GetAt(j) + count_); + + // massive insertions are first split off + while (bv.GetSize() >= 2 * kLimit) + Split(i, bv.GetSize() - kLimit - 2); + + if (bv.GetSize() > kLimit ) + Split(i, atEnd ? kLimit - 1 : bv.GetSize() / 2); // 23-3-2002, from MB + + Validate(); + + return true; +} + +bool c4_BlockedViewer::RemoveRows(int pos_, int count_) +{ + d4_assert(count_ > 0); + d4_assert(pos_ + count_ <= GetSize()); + + int z = _base.GetSize() - 1; + int i = Slot(pos_); + d4_assert(0 <= i && i < z); + + c4_View bv = _pBlock (_base[i]); + d4_assert(0 <= pos_ && pos_ <= bv.GetSize()); + + int todo = count_; + + // optimize if the deletion goes past the end of this block... + int overshoot = pos_ + count_ - bv.GetSize(); + if (overshoot > 0) { + + // first, delete blocks which are going away completely + while (i+1 < _offsets.GetSize()) { + int nextsize = _offsets.GetAt(i+1) - _offsets.GetAt(i); + if (overshoot < nextsize) + break; + todo -= nextsize; + overshoot -= nextsize; + + // drop the block and forget it ever existed + for (int j = i+1; j < z; ++j) + _offsets.SetAt(j, _offsets.GetAt(j) - nextsize); + _offsets.RemoveAt(i+1); + + _base.RemoveAt(i+1); + --z; + c4_View bz = _pBlock (_base[z]); + bz.RemoveAt(i); + + Validate(); + } + + // delete before merging, to avoid useless copying + if (overshoot > 1) { + c4_View bv2 = _pBlock (_base[i+1]); + bv2.RemoveAt(0, overshoot - 1); + todo -= overshoot - 1; + + for (int j = i+1; j < z; ++j) + _offsets.SetAt(j, _offsets.GetAt(j) - (overshoot - 1)); + + // if the next block is filled enough, rotate the separator + // this avoids an expensive and unnecessary merge + split + if (bv2.GetSize() > kLimit / 2) { + c4_View bz = _pBlock (_base[z]); + bz[i] = bv2[0]; + bv2.RemoveAt(0); + --todo; + d4_assert(pos_ + todo <= bv.GetSize()); + d4_assert(i < _offsets.GetSize()); + + for (int j = i+1; j < z; ++j) + _offsets.SetAt(j, _offsets.GetAt(j) - 1); + } + } + + // merge into one block + if (pos_ + todo > bv.GetSize()) { + d4_assert(i < z - 1); + Merge(i); + --z; + } + } + d4_assert(pos_ + todo <= bv.GetSize()); + + // now remove the rows and adjust offsets + if (todo > 0) + bv.RemoveAt(pos_, todo); + + for (int j = i; j < z; ++j) + _offsets.SetAt(j, _offsets.GetAt(j) - todo); + + // if the block underflows, merge it + if (bv.GetSize() < kLimit / 2) { + if (i > 0) // merge with predecessor, preferably + bv = _pBlock (_base[--i]); + if (i >= z - 1) // unless there is no successor to merge with + return true; + Merge(i); + } + + // if the block overflows, split it + if (bv.GetSize() > kLimit ) + Split(i, bv.GetSize() / 2); + + Validate(); + + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_OrderedViewer : public c4_CustomViewer +{ + c4_View _base; + int _numKeys; + + int KeyCompare(int row_, c4_Cursor cursor_) const; + +public: + c4_OrderedViewer (c4_Sequence& seq_, int numKeys_); + virtual ~c4_OrderedViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual int Lookup(c4_Cursor key_, int& count_); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + virtual bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_OrderedViewer::c4_OrderedViewer (c4_Sequence& seq_, int numKeys_) + : _base (&seq_), _numKeys (numKeys_) +{ +} + +c4_OrderedViewer::~c4_OrderedViewer () +{ +} + +int c4_OrderedViewer::KeyCompare(int row_, c4_Cursor cursor_) const +{ + for (int i = 0; i < _numKeys; ++i) + { + c4_Bytes buffer; + _base.GetItem(row_, i, buffer); + + c4_Handler& h = cursor_._seq->NthHandler(i); + int f = h.Compare(cursor_._index, buffer); + if (f != 0) + return f; + } + + return 0; +} + +c4_View c4_OrderedViewer::GetTemplate() +{ + return _base.Clone(); +} + +int c4_OrderedViewer::GetSize() +{ + return _base.GetSize(); +} + +int c4_OrderedViewer::Lookup(c4_Cursor key_, int& count_) +{ + // can only use bsearch if the properties match the query + // XXX with ord1.tcl (dict words), this loop takes 300 uS! + c4_View kv = (*key_).Container(); + for (int k = 0; k < _numKeys; ++k) + if (kv.FindProperty(_base.NthProperty(k).GetId()) < 0) + return -1; + +#if 0 // Locate gets the count wrong, it seems 2000-06-15 + int pos; + count_ = _base.Locate(*key_, &pos); +#else + int pos = _base.Search(*key_); + count_ = pos < _base.GetSize() && KeyCompare(pos, key_) == 0 ? 1 : 0; +#endif + return pos; +} + +bool c4_OrderedViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + return _base.GetItem(row_, col_, buf_); +} + +bool c4_OrderedViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + if (col_ < _numKeys) + { + c4_Bytes temp; + _base.GetItem(row_, col_, temp); + if (buf_ == temp) + return true; // this call will have no effect, just ignore it + } + + _base.SetItem(row_, col_, buf_); + + if (col_ < _numKeys) + { + c4_Row copy = _base[row_]; + // have to remove the row because it messes up searching + // it would be more efficient to search *around* this row + // or perhaps figure out new pos before changing any data + RemoveRows(row_); + InsertRows(0, ©); // position is ignored + } + + return true; +} + +bool c4_OrderedViewer::InsertRows(int, c4_Cursor value_, int count_) +{ + d4_assert(count_ > 0); + + int n; + int i = Lookup(value_, n); + + // XXX if the lookup does not work, then insert as first element (!?) + d4_assert(i >= 0); + if (i < 0) + i = 0; + + if (n == 0) + _base.InsertAt(i, *value_); + else + { + d4_assert(i < _base.GetSize()); + _base.SetAt(i, *value_); // replace existing + } + + return true; +} + +bool c4_OrderedViewer::RemoveRows(int pos_, int count_) +{ + _base.RemoveAt(pos_, count_); + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +class c4_IndexedViewer : public c4_CustomViewer +{ + c4_View _base; + c4_View _map; + c4_View _props; + bool _unique; + c4_IntProp _mapProp; + + int KeyCompare(int row_, c4_Cursor cursor_) const; + +public: + c4_IndexedViewer (c4_Sequence& seq_, c4_Sequence& map_, + const c4_View& props_, bool unique_); + virtual ~c4_IndexedViewer (); + + virtual c4_View GetTemplate(); + virtual int GetSize(); + virtual int Lookup(c4_Cursor key_, int& count_); + virtual bool GetItem(int row_, int col_, c4_Bytes& buf_); + virtual bool SetItem(int row_, int col_, const c4_Bytes& buf_); + virtual bool InsertRows(int pos_, c4_Cursor value_, int count_=1); + virtual bool RemoveRows(int pos_, int count_=1); +}; + +///////////////////////////////////////////////////////////////////////////// + +c4_IndexedViewer::c4_IndexedViewer (c4_Sequence& seq_, c4_Sequence& map_, + const c4_View& props_, bool unique_) + : _base (&seq_), _map (&map_), _props (props_), _unique (unique_), + _mapProp ((const c4_IntProp&) _map.NthProperty(0)) +{ + int n = _base.GetSize(); + if (_map.GetSize() != n) + { + c4_View sorted = _base.SortOn(_props); + + _map.SetSize(n); + for (int i = 0; i < n; ++i) + _mapProp (_map[i]) = _base.GetIndexOf(sorted[i]); + } +} + +c4_IndexedViewer::~c4_IndexedViewer () +{ +} + +int c4_IndexedViewer::KeyCompare(int row_, c4_Cursor cursor_) const +{ + int n = _props.NumProperties(); + for (int i = 0; i < n; ++i) + { + c4_Bytes buffer; + _base.GetItem(row_, i, buffer); + + c4_Handler& h = cursor_._seq->NthHandler(i); + int f = h.Compare(cursor_._index, buffer); + if (f != 0) + return f; + } + + return 0; +} + +c4_View c4_IndexedViewer::GetTemplate() +{ + return _base.Clone(); +} + +int c4_IndexedViewer::GetSize() +{ + return _base.GetSize(); +} + +int c4_IndexedViewer::Lookup(c4_Cursor key_, int& count_) +{ + // can only use bsearch if the properties match the query + // XXX with ord1.tcl (dict words), this loop takes 300 uS! + c4_View kv = (*key_).Container(); + int n = _props.NumProperties(); + for (int k = 0; k < n; ++k) + if (kv.FindProperty(_props.NthProperty(k).GetId()) < 0) + return -1; + +#if 0 // Locate gets the count wrong, it seems 2000-06-15 + int pos; + count_ = _base.Locate(*key_, &pos); +#else + int pos = _base.Search(*key_); + count_ = pos < _base.GetSize() && KeyCompare(pos, key_) == 0 ? 1 : 0; +#endif + return pos; +} + +bool c4_IndexedViewer::GetItem(int row_, int col_, c4_Bytes& buf_) +{ + return _base.GetItem(row_, col_, buf_); +} + +bool c4_IndexedViewer::SetItem(int row_, int col_, const c4_Bytes& buf_) +{ + const int id = _base.NthProperty(col_).GetId(); + const bool keyMod = _props.FindProperty(id) >= 0; + + if (keyMod) + { + c4_Bytes temp; + _base.GetItem(row_, col_, temp); + if (buf_ == temp) + return true; // this call will have no effect, just ignore it + } + + _base.SetItem(row_, col_, buf_); + + if (keyMod) + { + // TODO adjust index + } + + return true; +} + +bool c4_IndexedViewer::InsertRows(int, c4_Cursor value_, int count_) +{ + d4_assert(count_ > 0); + + if (_unique) + count_ = 1; + + int n; + int i = Lookup(value_, n); + + // XXX if the lookup does not work, then insert as first element (!?) + d4_assert(i >= 0); + if (i < 0) + i = 0; + + if (n == 0) + _base.InsertAt(i, *value_); + else + { + d4_assert(i < _base.GetSize()); + _base.SetAt(i, *value_); // replace existing + } + + return true; +} + +bool c4_IndexedViewer::RemoveRows(int pos_, int count_) +{ + _base.RemoveAt(pos_, count_); + + int n = _map.GetSize(); + while (--n >= 0) + { + int v = _mapProp (_map[n]); + if (v >= pos_) + if (v < pos_ + count_) + _map.RemoveAt(n); + else + _mapProp (_map[n]) = v - count_; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_CustomViewer* f4_CreateReadOnly(c4_Sequence& seq_) +{ + return d4_new c4_ReadOnlyViewer (seq_); +} + +c4_CustomViewer* f4_CreateHash(c4_Sequence& seq_, int nk_, c4_Sequence* map_) +{ + return d4_new c4_HashViewer (seq_, nk_, map_); +} + +c4_CustomViewer* f4_CreateBlocked(c4_Sequence& seq_) +{ + return d4_new c4_BlockedViewer (seq_); +} + +c4_CustomViewer* f4_CreateOrdered(c4_Sequence& seq_, int nk_) +{ + return d4_new c4_OrderedViewer (seq_, nk_); +} + +c4_CustomViewer* f4_CreateIndexed(c4_Sequence& seq_, c4_Sequence& map_, + const c4_View& props_, bool unique_) +{ + return d4_new c4_IndexedViewer (seq_, map_, props_, unique_); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/remap.h b/akregator/src/mk4storage/metakit/src/remap.h new file mode 100644 index 00000000..a435f853 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/remap.h @@ -0,0 +1,26 @@ +// remap.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Encapsulation of the (re)mapping viewers + */ + +#ifndef __REMAP_H__ +#define __REMAP_H__ + +///////////////////////////////////////////////////////////////////////////// +// Declarations in this file + + class c4_Sequence; // not defined here + + extern c4_CustomViewer* f4_CreateReadOnly(c4_Sequence&); + extern c4_CustomViewer* f4_CreateHash(c4_Sequence&, int, c4_Sequence* =0); + extern c4_CustomViewer* f4_CreateBlocked(c4_Sequence&); + extern c4_CustomViewer* f4_CreateOrdered(c4_Sequence&, int); + extern c4_CustomViewer* f4_CreateIndexed(c4_Sequence&, c4_Sequence&, + const c4_View&, bool =false); + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/std.cpp b/akregator/src/mk4storage/metakit/src/std.cpp new file mode 100644 index 00000000..135fe776 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/std.cpp @@ -0,0 +1,84 @@ +// std.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Implementation of STL-based strings and containers + */ + +#include "header.h" + +#if q4_STD // until end of source +///////////////////////////////////////////////////////////////////////////// + +#include "column.h" // c4_ColCache + +#if !q4_INLINE +static char _mk4stdInl[] = "mk4str.inl"; +#include "mk4str.inl" +#endif + +#if !q4_NO_NS +using namespace std; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Implemented in this file + + class c4_String; + +///////////////////////////////////////////////////////////////////////////// + +#if !q4_MSVC && !q4_WATC + + // MS C/C++ has this handy stricmp: a case-insensitive version of strcmp + // This version only works with 7-bit ASCII characters 0x00 through 0x7F + + static int stricmp(const char* p1, const char* p2) + { + int c1, c2; + +#ifdef d4_USE_UNOPTIMIZED_CODE + do + { + c1 = tolower(*p1++); + c2 = tolower(*p2++); + } while (c1 != 0 && c1 == c2); +#else + do + { + c1 = *p1++; + c2 = *p2++; + } while (c1 != 0 && (c1 == c2 || tolower(c1) == tolower(c2))); + + c1 = tolower(c1); + c2 = tolower(c2); +#endif + + return c1 - c2; + } + +#endif + +///////////////////////////////////////////////////////////////////////////// +// c4_String + +c4_String c4_String::Mid(int nFirst_, int nCount_) const +{ + int n = length(); + if (nFirst_ > n) + nFirst_ = n; + if (nFirst_ + nCount_ > n) + nCount_ = n - nFirst_; + + return substr(nFirst_, nCount_); +} + +int c4_String::CompareNoCase(const char* str_) const +{ + // this is not very "standard library-ish" ... + return *(const string*) this == str_ ? 0 : stricmp(c_str(), str_); +} + +///////////////////////////////////////////////////////////////////////////// +#endif // q4_STD diff --git a/akregator/src/mk4storage/metakit/src/std.h b/akregator/src/mk4storage/metakit/src/std.h new file mode 100644 index 00000000..1d1937be --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/std.h @@ -0,0 +1,63 @@ +// std.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Configuration header for STL-based builds + */ + +#define q4_STD 1 + +#include "mk4str.h" + +#include <vector> + +///////////////////////////////////////////////////////////////////////////// + +template<class T> +class c4_ArrayT +{ +#ifdef _MSC_VER + d4_std::vector< T, d4_std::allocator<T> > _vector; +#else + d4_std::vector< T, d4_std::alloc > _vector; +#endif + +public: + c4_ArrayT () { } + ~c4_ArrayT () { } + + int GetSize() const { return _vector.size(); } + void SetSize(int nNewSize, int =-1) { _vector.resize(nNewSize); } + + T GetAt(int nIndex) const { return _vector[nIndex]; } + T& ElementAt(int nIndex) { return _vector[nIndex]; } + + void SetAt(int nIndex, const T& newElement) + { + _vector[nIndex] = newElement; + } + + int Add(const T& newElement) + { + int n = _vector.size(); + _vector.push_back(newElement); + return n; + } + + void InsertAt(int nIndex, const T& newElement, int nCount =1) + { + _vector.insert(&_vector[nIndex], nCount, newElement); + } + + void RemoveAt(int nIndex, int nCount =1) + { + _vector.erase(&_vector[nIndex], &_vector[nIndex+nCount]); + } +}; + +typedef c4_ArrayT<t4_i32> c4_DWordArray; +typedef c4_ArrayT<void*> c4_PtrArray; +typedef c4_ArrayT<c4_String> c4_StringArray; + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/store.cpp b/akregator/src/mk4storage/metakit/src/store.cpp new file mode 100644 index 00000000..a32de665 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/store.cpp @@ -0,0 +1,587 @@ +// store.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Storage management and several other loose ends + */ + +#include "header.h" +#include "handler.h" // 19990906 +#include "store.h" +#include "field.h" +#include "persist.h" +#include "format.h" // 19990906 + +#include "mk4io.h" // 19991104 + +#if !q4_INLINE +#include "store.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +c4_Dependencies::c4_Dependencies () +{ + _refs.SetSize(0, 3); // a little optimization +} + +c4_Dependencies::~c4_Dependencies () +{ +} + +void c4_Dependencies::Add(c4_Sequence* seq_) +{ + for (int i = 0; i < _refs.GetSize(); ++i) + d4_assert(_refs.GetAt(i) != seq_); + + _refs.Add(seq_); +} + +bool c4_Dependencies::Remove(c4_Sequence* seq_) +{ + int n = _refs.GetSize() - 1; + d4_assert(n >= 0); + + for (int i = 0; i <= n; ++i) + if (_refs.GetAt(i) == seq_) { + _refs.SetAt(i, _refs.GetAt(n)); + _refs.SetSize(n); + return n > 0; + } + + d4_assert(0); // dependency not found + return true; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Notifier::~c4_Notifier () +{ + if (_type > kNone && _origin->GetDependencies()) { + c4_PtrArray& refs = _origin->GetDependencies()->_refs; + + for (int i = 0; i < refs.GetSize(); ++i) { + c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i); + d4_assert(seq != 0); + + seq->PostChange(*this); + + if (_chain && _chain->_origin == seq) { + c4_Notifier* next = _chain->_next; + _chain->_next = 0; + + delete _chain; + + _chain = next; + } + } + } + + d4_assert(!_chain); + d4_assert(!_next); +} + +void c4_Notifier::StartSetAt(int index_, c4_Cursor& cursor_) +{ + _type = kSetAt; + _index = index_; + _cursor = &cursor_; + + Notify(); +} + +void c4_Notifier::StartInsertAt(int i_, c4_Cursor& cursor_, int n_) +{ + _type = kInsertAt; + _index = i_; + _cursor = &cursor_; + _count = n_; + + Notify(); +} + +void c4_Notifier::StartRemoveAt(int index_, int count_) +{ + _type = kRemoveAt; + _index = index_; + _count = count_; + + Notify(); +} + +void c4_Notifier::StartMove(int from_, int to_) +{ + _type = kMove; + _index = from_; + _count = to_; + + Notify(); +} + +void c4_Notifier::StartSet(int i_, int propId_, const c4_Bytes& buf_) +{ + _type = kSet; + _index = i_; + _propId = propId_; + _bytes = &buf_; + + Notify(); +} + +void c4_Notifier::Notify() +{ + d4_assert(_origin->GetDependencies() != 0); + c4_PtrArray& refs = _origin->GetDependencies()->_refs; + + int n = refs.GetSize(); + d4_assert(n > 0); + + c4_Notifier** rover = &_chain; + + for (int i = 0; i < n; ++i) { + c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i); + d4_assert(seq != 0); + + c4_Notifier* ptr = seq->PreChange(*this); + if (ptr) { + d4_assert(ptr->_origin == seq); + + d4_assert(!*rover); + *rover = ptr; + rover = &ptr->_next; + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_Storage + * + * Manager for persistent storage of view structures. + * + * The storage class uses a view, with additional functionality to be able + * to store and reload the data it contains (including nested subviews). + * + * By default, data is loaded on demand, i.e. whenever data which has + * not yet been referenced is used for the first time. Loading is limited + * to the lifetime of this storage object, since the storage object carries + * the file descriptor with it that is needed to access the data file. + * + * To save changes, call the Commit member. This is the only time + * that data is written to file - when using a read-only file simply avoid + * calling Commit. + * + * The LoadFromStream and SaveToStream members can be used to + * serialize the contents of this storage row using only sequential I/O + * (no seeking, only read or write calls). + * + * The data storage mechanism implementation provides fail-safe operation: + * if anything prevents Commit from completing its task, the last + * succesfully committed version of the saved data will be recovered on + * the next open. This also includes changes made to the table structure. + * + * The following code creates a view with 1 row and stores it on file: + * @code + * c4_StringProp pName ("Name"); + * c4_IntProp pAge ("Age"); + * + * c4_Storage storage ("myfile.dat", true); + * c4_View myView = storage.GetAs("Musicians[Name:S,Age:I]"); + * + * myView.Add(pName ["John Williams"] + pAge [43]); + * + * storage.Commit(); + * @endcode + */ + +c4_Storage::c4_Storage () +{ + // changed to r/o, now that commits don't crash on it anymore + Initialize(*d4_new c4_Strategy, true, 0); +} + +c4_Storage::c4_Storage (c4_Strategy& strategy_, bool owned_, int mode_) +{ + Initialize(strategy_, owned_, mode_); + Persist()->LoadAll(); +} + +c4_Storage::c4_Storage (const char* fname_, int mode_) +{ + c4_FileStrategy* strat = d4_new c4_FileStrategy; + strat->DataOpen(fname_, mode_); + + Initialize(*strat, true, mode_); + if (strat->IsValid()) + Persist()->LoadAll(); +} + +c4_Storage::c4_Storage (const c4_View& root_) +{ + if (root_.Persist() != 0) // only restore if view was indeed persistent + *(c4_View*) this = root_; + else // if this was not possible, start with a fresh empty storage + Initialize(*d4_new c4_Strategy, true, 0); +} + +c4_Storage::~c4_Storage () +{ + // cannot unmap here, because there may still be an autocommit pending + //((c4_HandlerSeq*) _seq)->UnmapAll(); +} + +void c4_Storage::Initialize(c4_Strategy& strategy_, bool owned_, int mode_) +{ + c4_Persist* pers = d4_new c4_Persist (strategy_, owned_, mode_); + c4_HandlerSeq* seq = d4_new c4_HandlerSeq (pers); + seq->DefineRoot(); + *(c4_View*) this = seq; + pers->SetRoot(seq); +} + + /// Get or set a named view in this storage object +c4_ViewRef c4_Storage::View(const char* name_) +{ + /* + The easy solution would seem to be: + + c4_ViewProp prop (name_); + return prop (Contents()); + + But this does not work, because the return value would point to + an object allocated on the stack. + + Instead, make sure the view *has* such a property, and use the + one inside the c4_Handler for it (since this will stay around). + */ + +// int n = _root->PropIndex(c4_ViewProp (name_)); + + c4_ViewProp prop (name_); + int n = AddProperty(prop); + d4_assert(n >= 0); + + // the following is an expression of the form "property (rowref)" + return NthProperty(n) (GetAt(0)); +} + + /// Get a named view, redefining it to match the given structure +c4_View c4_Storage::GetAs(const char* description_) +{ + d4_assert(description_ != 0); + + // Dec 2001: now that GetAs is being used so much more frequently, + // add a quick check to see whether restructuring is needed at all + const char* q = strchr(description_, '['); + if (q != 0) { + c4_String vname (description_, q - description_); + const char* d = Description(vname); + if (d != 0) { + c4_String desc (d); + if (("[" + desc + "]").CompareNoCase(q) == 0) + return View(vname); + } + } + + c4_Field* field = d4_new c4_Field (description_); + d4_assert(field != 0); + + d4_assert(!*description_); + + c4_String name = field->Name(); + d4_assert(!name.IsEmpty()); + + c4_Field& curr = Persist()->Root().Definition(); + + c4_String newField = "," + field->Description(); + bool keep = newField.Find('[') >= 0; + + c4_String newDef; + + // go through all subfields + for (int i = 0; i < curr.NumSubFields(); ++i) { + c4_Field& of = curr.SubField(i); + if (of.Name().CompareNoCase(name) == 0) { + if (field->IsRepeating()) + newDef += newField; + // else new is not a repeating entry, so drop this entire field + + newField.Empty(); // don't append it later on + continue; + } + + newDef += "," + of.Description(); // keep original field + } + + if (keep) // added 19990824 ignore if deletion + newDef += newField; // appends new definition if not found earlier + + delete field; + + const char* p = newDef; + SetStructure(*p ? ++p : p); // skip the leading comma + + if (!keep) // 19990916: avoid adding an empty view again + return c4_View (); + + return View(name); +} + + /// Define the complete view structure of the storage +void c4_Storage::SetStructure(const char* description_) +{ + d4_assert(description_ != 0); + + if (description_ != Description()) { + c4_String s = "[" + c4_String (description_) + "]"; + description_ = s; + + c4_Field* field = d4_new c4_Field (description_); + d4_assert(!*description_); + + d4_assert(field != 0); + Persist()->Root().Restructure(*field, false); + } +} + + /// Return the strategy object associated with this storage +c4_Strategy& c4_Storage::Strategy() const +{ + return Persist()->Strategy(); +} + + /// Return a description of the view structure (default is all) +const char* c4_Storage::Description(const char* name_) +{ + if (name_ == 0 || *name_ == 0) + return c4_View::Description(); + + c4_View v = View(name_); + return v.Description(); +} + + /// Define the storage to use for differential commits +bool c4_Storage::SetAside(c4_Storage& aside_) +{ + c4_Persist* pers = Persist(); + bool f = pers->SetAside(aside_); + // adjust our copy when the root view has been replaced + *(c4_View*) this = &pers->Root(); + return f; +} + + /// Return storage used for differential commits, or null +c4_Storage* c4_Storage::GetAside() const +{ + return Persist()->GetAside(); +} + + /// Flush pending changes to file right now +bool c4_Storage::Commit(bool full_) +{ + return Strategy().IsValid() && Persist()->Commit(full_); +} + +/** (Re)initialize for on-demand loading + * + * Calling Rollback will cancel all uncommitted changes. + */ +bool c4_Storage::Rollback(bool full_) +{ + c4_Persist* pers = Persist(); + bool f = Strategy().IsValid() && pers->Rollback(full_); + // adjust our copy when the root view has been replaced + *(c4_View*) this = &pers->Root(); + return f; +} + + /// Set storage up to always call Commit in the destructor +bool c4_Storage::AutoCommit(bool flag_) +{ + return Persist()->AutoCommit(flag_); +} + + /// Load contents from the specified input stream +bool c4_Storage::LoadFrom(c4_Stream& stream_) +{ + c4_HandlerSeq* newRoot = c4_Persist::Load(&stream_); + if (newRoot == 0) + return false; + + // fix commit-after-load bug, by using a full view copy + // this is inefficient, but avoids mapping/strategy problems + c4_View temp (newRoot); + + SetSize(0); + SetStructure(temp.Description()); + InsertAt(0, temp); + + return true; +} + + /// Save contents to the specified output stream +void c4_Storage::SaveTo(c4_Stream& stream_) +{ + c4_Persist::Save(&stream_, Persist()->Root()); +} + +///////////////////////////////////////////////////////////////////////////// + +c4_DerivedSeq::c4_DerivedSeq (c4_Sequence& seq_) + : _seq (seq_) +{ + _seq.Attach(this); +} + +c4_DerivedSeq::~c4_DerivedSeq () +{ + _seq.Detach(this); +} + +int c4_DerivedSeq::RemapIndex(int index_, const c4_Sequence* seq_) const +{ + return seq_ == this ? index_ : _seq.RemapIndex(index_, seq_); +} + +int c4_DerivedSeq::NumRows() const +{ + return _seq.NumRows(); +} + +int c4_DerivedSeq::NumHandlers() const +{ + return _seq.NumHandlers(); +} + +c4_Handler& c4_DerivedSeq::NthHandler(int colNum_) const +{ + return _seq.NthHandler(colNum_); +} + +const c4_Sequence* c4_DerivedSeq::HandlerContext(int colNum_) const +{ + return _seq.HandlerContext(colNum_); +} + +int c4_DerivedSeq::AddHandler(c4_Handler* handler_) +{ + return _seq.AddHandler(handler_); +} + +c4_Handler* c4_DerivedSeq::CreateHandler(const c4_Property& prop_) +{ + return _seq.CreateHandler(prop_); +} + +void c4_DerivedSeq::SetNumRows(int size_) +{ + _seq.SetNumRows(size_); +} + +c4_Notifier* c4_DerivedSeq::PreChange(c4_Notifier& nf_) +{ + if (!GetDependencies()) + return 0; + + c4_Notifier* chg = d4_new c4_Notifier (this); + + switch (nf_._type) + { + case c4_Notifier::kSetAt: + chg->StartSetAt(nf_._index, *nf_._cursor); + break; + + case c4_Notifier::kSet: + chg->StartSet(nf_._index, nf_._propId, *nf_._bytes); + break; + + case c4_Notifier::kInsertAt: + chg->StartInsertAt(nf_._index, *nf_._cursor, nf_._count); + break; + + case c4_Notifier::kRemoveAt: + chg->StartRemoveAt(nf_._index, nf_._count); + break; + + case c4_Notifier::kMove: + chg->StartMove(nf_._index, nf_._count); + break; + } + + return chg; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_StreamStrategy::c4_StreamStrategy (t4_i32 buflen_) + : _stream (0), _buffer (d4_new t4_byte [buflen_]), _buflen (buflen_), _position (0) +{ + _mapStart = _buffer; + _dataSize = buflen_; +} + +c4_StreamStrategy::c4_StreamStrategy (c4_Stream* stream_) + : _stream (stream_), _buffer (0), _buflen (0), _position (0) +{ +} + +c4_StreamStrategy::~c4_StreamStrategy () +{ + _mapStart = 0; + _dataSize = 0; + + if (_buffer != 0) + delete [] _buffer; +} + +bool c4_StreamStrategy::IsValid() const +{ + return true; +} + +int c4_StreamStrategy::DataRead(t4_i32 pos_, void* buffer_, int length_) +{ + if (_buffer != 0) { + d4_assert(pos_ <= _buflen); + _position = pos_ + _baseOffset; + + if (length_ > _buflen - _position) + length_ = _buflen - _position; + if (length_ > 0) + memcpy(buffer_, _buffer + _position, length_); + } else { + d4_assert(_position == pos_ + _baseOffset); + length_ = _stream != 0 ? _stream->Read(buffer_, length_) : 0; + } + + _position += length_; + return length_; +} + +void c4_StreamStrategy::DataWrite(t4_i32 pos_, const void* buffer_, int length_) +{ + if (_buffer != 0) { + d4_assert(pos_ <= _buflen); + _position = pos_ + _baseOffset; + + int n = length_; + if (n > _buflen - _position) + n = _buflen - _position; + if (n > 0) + memcpy(_buffer + _position, buffer_, n); + } else { + d4_assert(_position == pos_ + _baseOffset); + if (_stream != 0 && !_stream->Write(buffer_, length_)) + ++_failure; + } + + _position += length_; +} + +t4_i32 c4_StreamStrategy::FileSize() +{ + return _position; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/store.h b/akregator/src/mk4storage/metakit/src/store.h new file mode 100644 index 00000000..c45d258e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/store.h @@ -0,0 +1,114 @@ +// store.h -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Definition of auxiliary storage management classes + */ + +#ifndef __STORE_H__ +#define __STORE_H__ + +///////////////////////////////////////////////////////////////////////////// + +class c4_Dependencies +{ + c4_PtrArray _refs; + +public: + c4_Dependencies (); + ~c4_Dependencies (); + + void Add(c4_Sequence* seq_); + bool Remove(c4_Sequence* seq_); + + friend class c4_Notifier; +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_Notifier +{ + c4_Sequence* _origin; + c4_Notifier* _chain; + c4_Notifier* _next; + +public: + enum { kNone, kSetAt, kInsertAt, kRemoveAt, kMove, kSet, kLimit }; + + c4_Notifier (c4_Sequence* origin_); + ~c4_Notifier (); + + bool HasDependents() const; + + void StartSetAt(int index_, c4_Cursor& cursor_); + void StartInsertAt(int index_, c4_Cursor& cursor_, int count_); + void StartRemoveAt(int index_, int count_); + void StartMove(int from_, int to_); + void StartSet(int index_, int propId_, const c4_Bytes& buf_); + + int _type; + int _index; + int _propId; + int _count; + c4_Cursor* _cursor; + const c4_Bytes* _bytes; + +private: + void Notify(); +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_DerivedSeq : public c4_Sequence +{ +protected: + c4_Sequence& _seq; + +protected: + c4_DerivedSeq (c4_Sequence& seq_); + virtual ~c4_DerivedSeq (); + +public: + virtual int RemapIndex(int, const c4_Sequence*) const; + + virtual int NumRows() const; + virtual void SetNumRows(int size_); + + virtual int NumHandlers() const; + virtual c4_Handler& NthHandler(int) const; + virtual const c4_Sequence* HandlerContext(int) const; + virtual int AddHandler(c4_Handler*); + virtual c4_Handler* CreateHandler(const c4_Property&); + + virtual c4_Notifier* PreChange(c4_Notifier& nf_); +}; + +///////////////////////////////////////////////////////////////////////////// + +class c4_StreamStrategy : public c4_Strategy +{ + c4_Stream* _stream; + t4_byte* _buffer; + t4_i32 _buflen; + t4_i32 _position; +public: + c4_StreamStrategy (t4_i32 buflen_); + c4_StreamStrategy (c4_Stream* stream_); + virtual ~c4_StreamStrategy (); + + virtual bool IsValid() const; + virtual int DataRead(t4_i32 pos_, void* buffer_, int length_); + virtual void DataWrite(t4_i32 pos_, const void* buffer_, int length_); + virtual t4_i32 FileSize(); +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_INLINE +#include "store.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/akregator/src/mk4storage/metakit/src/store.inl b/akregator/src/mk4storage/metakit/src/store.inl new file mode 100644 index 00000000..191ee7de --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/store.inl @@ -0,0 +1,20 @@ +// store.inl -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Inlined members of the storage management classes + */ + +///////////////////////////////////////////////////////////////////////////// +// c4_Notifier + +d4_inline c4_Notifier::c4_Notifier (c4_Sequence* origin_) + : _origin (origin_), _chain (0), _next (0), + _type (kNone), _index (0), _propId (0), _count (0), + _cursor (0), _bytes (0) +{ + d4_assert(_origin != 0); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/string.cpp b/akregator/src/mk4storage/metakit/src/string.cpp new file mode 100644 index 00000000..33c8836c --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/string.cpp @@ -0,0 +1,304 @@ +// string.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * yet another string implementation + */ + +#include "header.h" + +/* these definitions could be used instead of header.h ... + #define q4_UNIV 1 + #define d4_inline + #include "mk4str.h" + #define d4_reentrant + #define d4_assert(x) +*/ + +#if q4_UNIV // until end of source +///////////////////////////////////////////////////////////////////////////// + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#ifdef _AIX +#include <strings.h> +#endif + +#if !q4_INLINE +#include "mk4str.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#if q4_MSVC || q4_WATC || q4_BORC || (q4_MWCW && __MWERKS__ < 0x3000) +#define strcasecmp stricmp +#elif q4_WINCE + + // MS C/C++ has this handy stricmp: a case-insensitive version of strcmp + // This version only works with 7-bit ASCII characters 0x00 through 0x7F + + static int stricmp(const char* p1, const char* p2) + { + int c1, c2; + +#ifdef d4_USE_UNOPTIMIZED_CODE + do + { + c1 = tolower(*p1++); + c2 = tolower(*p2++); + } while (c1 != 0 && c1 == c2); +#else + do + { + c1 = *p1++; + c2 = *p2++; + } while (c1 != 0 && (c1 == c2 || tolower(c1) == tolower(c2))); + + c1 = tolower(c1); + c2 = tolower(c2); +#endif + + return c1 - c2; + } + +#endif + +#if q4_WINCE + const char* strrchr(const char* p, char ch) + { + const char* q = 0; + while (*p) + if (*p++ == ch) + q = p; + return q; + } +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// This string class implement functionality which is very similar to that +// provided by the CString class of the Microsoft Framework Classes (MFC). +// +// There are also several major differences: +// +// 1) This class uses reference counting to avoid massive copying. +// Consequently, function return as well as assignment is very fast. +// 2) Strings of up to 255 bytes can contain any data, even null bytes. +// Longer strings can not contain any null bytes past position 255. +// 3) This class can produce a "const char*" without overhead, but it +// can also cast to the byte-counted "const unsigned char*" used +// everywhere in Macintosh applications (as StringPtr, Str255, etc). +// 4) This source code is not derived from Microsoft's code in any way. +// +// A good way to use this class, is to always use c4_String for function +// return values and "const [unsigned] char*" for all parameters. Together, +// these two choices will remove the need for nearly any messy casts. +// +// Note: MFC 4.0 has now adopted refcounts, and is a good alternative to +// this code (but a bit bulkier, it also has Unicode support). + + // 2001-11-27, stop releasing nullvec, to allow MT use + d4_reentrant static unsigned char* nullVec = 0; + +static int fInc(unsigned char* p) +{ + ++*p; + if (*p) + return 1; + + --*p; + return 0; +} + +inline static void fDec(unsigned char* p) +{ + --*p; + if (!*p && p != nullVec) + delete [] p; +} + +c4_String::c4_String (char ch, int n /* =1 */) +{ + if (n < 0) + n = 0; + + _value = new unsigned char [n + 3]; + + _value[0] = 1; // see Init() member + memset(_value + 2, ch, n); + _value[1] = (unsigned char) (n <= 255 ? n : 255); + _value[n+2] = 0; +} + +c4_String::c4_String (const char* p) +{ + Init(p, p != 0 ? strlen(p) : 0); +} + +c4_String::c4_String (const c4_String& s) +{ + if (fInc(s._value)) + _value = s._value; + else + Init(s.Data(), s.GetLength()); +} + +c4_String::~c4_String () +{ + fDec(_value); +} + +const c4_String& c4_String::operator= (const c4_String& s) +{ + unsigned char* oldVal = _value; + if (fInc(s._value)) + _value = s._value; + else + Init(s.Data(), s.GetLength()); + fDec(oldVal); + + return *this; +} + +c4_String operator+ (const c4_String& a, const c4_String& b) +{ + const int aCnt = a.GetLength(); + int sum = aCnt + b.GetLength(); + + c4_String result ('\0', sum); // set up correct size, then fix contents + memcpy(result._value + 2, a.Data(), aCnt); + memcpy(result._value + 2 + aCnt, b.Data(), sum - aCnt); + + return result; +} + +void c4_String::Init(const void* p, int n) +{ + if (p == NULL || n <= 0) + { + // Optimization to significantly speed-up init of empty strings: + // share a common entry, which avoids *LOTS* of tiny mem allocs. + // + // Especially "new [...] c4_String" will benefit a lot, as well as: + // + // c4_String s; // this would have caused a new allocation + // s = ... // then immediately drops the count back + // + // 2001/11/27: changed to never release this empty vector, for MT use + // the new logic is to completely ignore its ref count + + if (!nullVec) + { + // obtain a valid new empty string buffer to keep around + unsigned char* nv = new unsigned char [3]; + nv[0] = nv[1] = nv[2] = 0; + // only set static value after item is fully inited (avoid MT race) + nullVec = nv; + } + + _value = nullVec; // use this buffer as our empty string + return; // done... that was quick, wasn't it? + } + + _value = new unsigned char [n + 3]; + + _value[0] = 1; // many assumptions here: set the reference count to 1 + + if (n > 0) + memcpy(_value + 2, p, n); + _value[1] = (unsigned char) (n <= 255 ? n : 255); + _value[n+2] = 0; +} + +int c4_String::FullLength() const +{ + int n = _value[1]; + return n < 255 ? n : n + strlen((const char*) _value + 2 + 255); +} + +c4_String c4_String::Mid(int nFirst, int nCount) const +{ + if (nFirst >= GetLength()) + return c4_String (); + + if (nFirst + nCount > GetLength()) + nCount = GetLength() - nFirst; + + if (nFirst == 0 && nCount == GetLength()) + return *this; + + return c4_String (Data() + nFirst, nCount); +} + +c4_String c4_String::Left(int nCount) const +{ + if (nCount >= GetLength()) + return *this; + + return c4_String (Data(), nCount); +} + +c4_String c4_String::Right(int nCount) const +{ + if (nCount >= GetLength()) + return *this; + + return c4_String (Data() + GetLength() - nCount, nCount); +} + +bool operator== (const c4_String& a, const c4_String& b) +{ + return a._value == b._value || a.GetLength() == b.GetLength() && + memcmp(a.Data(), b.Data(), a.GetLength()) == 0; +} + +int c4_String::Compare(const char* str) const +{ + return Data() == str ? 0 : strcmp(Data(), str); +} + +int c4_String::CompareNoCase(const char* str) const +{ + return Data() == str ? 0 : strcasecmp(Data(), str); +} + +int c4_String::Find(char ch) const +{ + const char* p = strchr(Data(), ch); + return p != 0 ? p - Data() : -1; +} + +int c4_String::ReverseFind(char ch) const +{ + const char* p = strrchr(Data(), ch); + return p != 0 ? p - Data() : -1; +} + +int c4_String::FindOneOf(const char* set) const +{ + const char* p = strpbrk(Data(), set); + return p != 0 ? p - Data() : -1; +} + +int c4_String::Find(const char* sub) const +{ + const char* p = strstr(Data(), sub); + return p != 0 ? p - Data() : -1; +} + +c4_String c4_String::SpanIncluding(const char* set) const +{ + return Left(strspn(Data(), set)); +} + +c4_String c4_String::SpanExcluding(const char* set) const +{ + return Left(strcspn(Data(), set)); +} + +///////////////////////////////////////////////////////////////////////////// +#endif // q4_UNIV diff --git a/akregator/src/mk4storage/metakit/src/table.cpp b/akregator/src/mk4storage/metakit/src/table.cpp new file mode 100644 index 00000000..a857cee0 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/table.cpp @@ -0,0 +1,160 @@ +// table.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Loose ends, these should be moved + */ + +#include "header.h" +#include "handler.h" +#include "store.h" +#include "field.h" +#include "format.h" +#include "persist.h" + +///////////////////////////////////////////////////////////////////////////// +// Implemented in this file + + class c4_Bytes; + class c4_HandlerSeq; + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_Bytes + * + * Generic data buffer, with optional automatic clean up. + * + * These objects are used to pass around untyped data without concern about + * clean-up. They know whether the bytes need to be de-allocated when these + * objects go out of scope. Small amounts of data are stored in the object. + * + * Objects of this class are used a lot within Metakit to manipulate its own + * data items generically. The c4_BytesProp class allows storing binary + * data explicitly in a file. If such data files must be portable, then the + * application itself must define a generic format to deal with byte order. + * + * How to store an object in binary form in a row (this is not portable): + * @code + * struct MyStruct { ... }; + * MyStruct something; + * + * c4_BytesProp pData ("Data"); + * c4_Row row; + * + * pData (row) = c4_Bytes (&something, sizeof something); + * @endcode + */ + + /// Construct an object with contents, optionally as a copy +c4_Bytes::c4_Bytes (const void* buf_, int len_, bool copy_) + : _size (len_), _copy (copy_) +{ + _contents = (t4_byte*) buf_; // moved out of intializers for DEC CXX 5.7 + if (_copy) + _MakeCopy(); +} + + /// Copy constructor +c4_Bytes::c4_Bytes (const c4_Bytes& src_) + : _size (src_._size), _copy (src_._copy) +{ + _contents = src_._contents; // moved out of intializers for DEC CXX 5.7 + if (_copy || _contents == src_._buffer) + _MakeCopy(); +} + + /// Assignment, this may make a private copy of contents +c4_Bytes& c4_Bytes::operator= (const c4_Bytes& src_) +{ + if (&src_ != this) + { + _LoseCopy(); + + _contents = src_._contents; + _size = src_._size; + _copy = src_._copy; + + if (_copy || _contents == src_._buffer) + _MakeCopy(); + } + + return *this; +} + + /// Swap the contents and ownership of two byte objects +void c4_Bytes::Swap(c4_Bytes& bytes_) +{ + t4_byte* p = _contents; + int s = _size; + bool c = _copy; + + _contents = bytes_._contents; + _size = bytes_._size; + _copy = bytes_._copy; + + bytes_._contents = p; + bytes_._size = s; + bytes_._copy = c; + + // if either one is using its local buffer, swap those too + if (_contents == bytes_._buffer || p == _buffer) + { + t4_byte t [sizeof _buffer]; + + memcpy(t, _buffer, sizeof _buffer); + memcpy(_buffer, bytes_._buffer, sizeof _buffer); + memcpy(bytes_._buffer, t, sizeof _buffer); + + if (_contents == bytes_._buffer) + _contents = _buffer; + + if (bytes_._contents == _buffer) + bytes_._contents = bytes_._buffer; + } +} + +/// Define contents as a freshly allocated buffer of given size +t4_byte* c4_Bytes::SetBuffer(int length_) +{ +/* No substantial improvement measured: + Perhaps keep a correctly sized c4_Bytes object in each property? + It means c4_...Ref objects would need to store a pointer, not an id. + + if (length_ == _size) + return _contents; // no work needed, get out fast +*/ + _LoseCopy(); + + _size = length_; + _copy = _size > (int) sizeof _buffer; + + return _contents = _copy ? d4_new t4_byte [_size] : _buffer; +} + +/// Allocate a buffer and fills its contents with zero bytes +t4_byte* c4_Bytes::SetBufferClear(int length_) +{ + return (t4_byte*) memset(SetBuffer(length_), 0, length_); +} + +void c4_Bytes::_MakeCopy() +{ + d4_assert(_contents != 0); + + _copy = _size > (int) sizeof _buffer; + + if (_size > 0) + _contents = (t4_byte*) memcpy(_copy ? d4_new t4_byte [_size] + : _buffer, _contents, _size); +} + +/// Return true if the contents of both objects are equal +bool operator== (const c4_Bytes& a_, const c4_Bytes& b_) +{ + return a_._contents == b_._contents || + (a_._size == b_._size && + memcmp(a_._contents, b_._contents, a_._size) == 0); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/univ.cpp b/akregator/src/mk4storage/metakit/src/univ.cpp new file mode 100644 index 00000000..81a73015 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/univ.cpp @@ -0,0 +1,227 @@ +// univ.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * A simple implementation of dynamic arrays + */ + +#include "header.h" + +#if q4_UNIV // until end of source +///////////////////////////////////////////////////////////////////////////// + +#include <stdlib.h> // malloc + +#if !q4_INLINE +#include "univ.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + +#if q4_UNIX || __MINGW32__ +#define _strdup strdup +#elif !q4_BORC && !q4_MSVC && !q4_WATC && !(q4_MWCW && defined(_WIN32)) && \ + !(q4_MWCW && __MWERKS__ >= 0x3000) + + static char* _strdup(const char* p) + { + if (!p) + return 0; + + char* s = (char*) malloc(strlen(p) + 1); + return strcpy(s, p); + } + +#endif + + // The Borland C++ RTL does not want file handle objects to cross + // DLL boundaries, so we add special fopen/fclose hooks to this DLL. + +#if q4_BORC + #include <stdio.h> + +#if q4_WIN32 + __declspec(dllexport) FILE* +#else + FILE* __export +#endif + f4_FileOpenInDLL(const char* name_, const char* mode_) + { + return fopen(name_, mode_); + } + +#if q4_WIN32 + __declspec(dllexport) +#else + int __export +#endif + f4_FileCloseInDLL(FILE* file_) + { + return fclose(file_); + } + +#endif + +///////////////////////////////////////////////////////////////////////////// +// c4_BaseArray + +c4_BaseArray::c4_BaseArray () + : _data (0), _size (0) +{ +} + +c4_BaseArray::~c4_BaseArray () +{ + SetLength(0); +} + +void c4_BaseArray::SetLength(int nNewSize) +{ + // 2001-11-25: use more granular allocation, as optimization + const int bits = 6; + + if (((_size - 1) ^ (nNewSize - 1)) >> bits) { + const int n = (nNewSize + (1<<bits) - 1) & -(1<<bits); + _data = _data == 0 ? n == 0 ? (char*) 0 + : (char*) malloc(n) + : n == 0 ? (free(_data), (char*) 0) + : (char*) realloc(_data, n); + } + + d4_assert(_data != 0 || nNewSize == 0); + + int n = _size; + _size = nNewSize; + + if (nNewSize > n) + memset(GetData(n), 0, nNewSize - n); +} + +void c4_BaseArray::Grow(int nIndex) +{ + if (nIndex > _size) + SetLength(nIndex); +} + +void c4_BaseArray::InsertAt(int nIndex, int nCount) +{ + SetLength(_size + nCount); + + int to = nIndex + nCount; + if (_size > to) + d4_memmove(GetData(to), GetData(nIndex), _size - to); +} + +void c4_BaseArray::RemoveAt(int nIndex, int nCount) +{ + int from = nIndex + nCount; + if (_size > from) + d4_memmove(GetData(nIndex), GetData(from), _size - from); + + SetLength(_size - nCount); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_DWordArray + +int c4_DWordArray::Add(t4_i32 newElement) +{ + int n = GetSize(); + _vector.Grow(Off(n + 1)); + SetAt(n, newElement); + return n; +} + +void c4_DWordArray::InsertAt(int nIndex, t4_i32 newElement, int nCount) +{ + _vector.InsertAt(Off(nIndex), nCount * sizeof (t4_i32)); + + while (--nCount >= 0) + SetAt(nIndex++, newElement); +} + +void c4_DWordArray::RemoveAt(int nIndex, int nCount) +{ + _vector.RemoveAt(Off(nIndex), nCount * sizeof (t4_i32)); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_PtrArray + +int c4_PtrArray::Add(void* newElement) +{ + int n = GetSize(); + _vector.Grow(Off(n + 1)); + SetAt(n, newElement); + return n; +} + +void c4_PtrArray::InsertAt(int nIndex, void* newElement, int nCount) +{ + _vector.InsertAt(Off(nIndex), nCount * sizeof (void*)); + + while (--nCount >= 0) + SetAt(nIndex++, newElement); +} + +void c4_PtrArray::RemoveAt(int nIndex, int nCount) +{ + _vector.RemoveAt(Off(nIndex), nCount * sizeof (void*)); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_StringArray + +c4_StringArray::~c4_StringArray() +{ + SetSize(0); +} + +void c4_StringArray::SetSize(int nNewSize, int) +{ + int i = nNewSize; + + while (i < GetSize()) + SetAt(i++, 0); + + _ptrs.SetSize(nNewSize); + + while (i < GetSize()) + _ptrs.SetAt(i++, ""); +} + +void c4_StringArray::SetAt(int nIndex, const char* newElement) +{ + char* s = (char*) _ptrs.GetAt(nIndex); + if (s && *s) + free(s); + + _ptrs.SetAt(nIndex, newElement && *newElement? _strdup(newElement) : ""); +} + +int c4_StringArray::Add(const char* newElement) +{ + int n = _ptrs.Add(0); + SetAt(n, newElement); + return n; +} + +void c4_StringArray::InsertAt(int nIndex, const char* newElement, int nCount) +{ + _ptrs.InsertAt(nIndex, 0, nCount); + + while (--nCount >= 0) + SetAt(nIndex++, newElement); +} + +void c4_StringArray::RemoveAt(int nIndex, int nCount) +{ + for (int i = 0; i < nCount; ++i) + SetAt(nIndex + i, 0); + + _ptrs.RemoveAt(nIndex, nCount); +} + +///////////////////////////////////////////////////////////////////////////// +#endif // q4_UNIV diff --git a/akregator/src/mk4storage/metakit/src/univ.h b/akregator/src/mk4storage/metakit/src/univ.h new file mode 100644 index 00000000..5558e8a4 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/univ.h @@ -0,0 +1,115 @@ +// univ.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Definition of the container classes + */ + +#define q4_UNIV 1 + +#include "mk4str.h" + +///////////////////////////////////////////////////////////////////////////// + +class c4_BaseArray +{ +public: + c4_BaseArray (); + ~c4_BaseArray (); + + int GetLength() const; + void SetLength(int nNewSize); + + const void* GetData(int nIndex) const; + void* GetData(int nIndex); + + void Grow(int nIndex); + + void InsertAt(int nIndex, int nCount); + void RemoveAt(int nIndex, int nCount); + +private: + char* _data; + int _size; +// char _buffer[4]; +}; + +class c4_PtrArray +{ +public: + c4_PtrArray (); + ~c4_PtrArray (); + + int GetSize() const; + void SetSize(int nNewSize, int nGrowBy = -1); + + void* GetAt(int nIndex) const; + void SetAt(int nIndex, const void* newElement); + void*& ElementAt(int nIndex); + + int Add(void* newElement); + + void InsertAt(int nIndex, void* newElement, int nCount = 1); + void RemoveAt(int nIndex, int nCount = 1); + +private: + static int Off(int n_); + + c4_BaseArray _vector; +}; + +class c4_DWordArray +{ +public: + c4_DWordArray (); + ~c4_DWordArray (); + + int GetSize() const; + void SetSize(int nNewSize, int nGrowBy = -1); + + t4_i32 GetAt(int nIndex) const; + void SetAt(int nIndex, t4_i32 newElement); + t4_i32& ElementAt(int nIndex); + + int Add(t4_i32 newElement); + + void InsertAt(int nIndex, t4_i32 newElement, int nCount = 1); + void RemoveAt(int nIndex, int nCount = 1); + +private: + static int Off(int n_); + + c4_BaseArray _vector; +}; + +class c4_StringArray +{ +public: + c4_StringArray (); + ~c4_StringArray (); + + int GetSize() const; + void SetSize(int nNewSize, int nGrowBy = -1); + + const char* GetAt(int nIndex) const; + void SetAt(int nIndex, const char* newElement); +// c4_String& ElementAt(int nIndex); + + int Add(const char* newElement); + + void InsertAt(int nIndex, const char* newElement, int nCount = 1); + void RemoveAt(int nIndex, int nCount = 1); + +private: + c4_PtrArray _ptrs; +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_INLINE + #include "univ.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// + diff --git a/akregator/src/mk4storage/metakit/src/univ.inl b/akregator/src/mk4storage/metakit/src/univ.inl new file mode 100644 index 00000000..45b086fb --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/univ.inl @@ -0,0 +1,126 @@ +// univ.inl -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Inlined members of the container classes + */ + +///////////////////////////////////////////////////////////////////////////// +// c4_BaseArray + +d4_inline int c4_BaseArray::GetLength() const +{ + return _size; +} + +d4_inline const void* c4_BaseArray::GetData(int nIndex) const +{ + return _data + nIndex; +} + +d4_inline void* c4_BaseArray::GetData(int nIndex) +{ + return _data + nIndex; +} + +///////////////////////////////////////////////////////////////////////////// +// c4_PtrArray + +d4_inline c4_PtrArray::c4_PtrArray () +{ +} + +d4_inline c4_PtrArray::~c4_PtrArray () +{ +} + +d4_inline int c4_PtrArray::Off(int n_) +{ + return n_ * sizeof (void*); +} + +d4_inline int c4_PtrArray::GetSize() const +{ + return _vector.GetLength() / sizeof (void*); +} + +d4_inline void c4_PtrArray::SetSize(int nNewSize, int) +{ + _vector.SetLength(Off(nNewSize)); +} + +d4_inline void* c4_PtrArray::GetAt(int nIndex) const +{ + return *(void* const*) _vector.GetData(Off(nIndex)); +} + +d4_inline void c4_PtrArray::SetAt(int nIndex, const void* newElement) +{ + *(const void**) _vector.GetData(Off(nIndex)) = newElement; +} + +d4_inline void*& c4_PtrArray::ElementAt(int nIndex) +{ + return *(void**) _vector.GetData(Off(nIndex)); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_DWordArray + +d4_inline c4_DWordArray::c4_DWordArray () +{ +} + +d4_inline c4_DWordArray::~c4_DWordArray () +{ +} + +d4_inline int c4_DWordArray::Off(int n_) +{ + return n_ * sizeof (t4_i32); +} + +d4_inline int c4_DWordArray::GetSize() const +{ + return _vector.GetLength() / sizeof (t4_i32); +} + +d4_inline void c4_DWordArray::SetSize(int nNewSize, int) +{ + _vector.SetLength(Off(nNewSize)); +} + +d4_inline t4_i32 c4_DWordArray::GetAt(int nIndex) const +{ + return *(const t4_i32*) _vector.GetData(Off(nIndex)); +} + +d4_inline void c4_DWordArray::SetAt(int nIndex, t4_i32 newElement) +{ + *(t4_i32*) _vector.GetData(Off(nIndex)) = newElement; +} + +d4_inline t4_i32& c4_DWordArray::ElementAt(int nIndex) +{ + return *(t4_i32*) _vector.GetData(Off(nIndex)); +} + +///////////////////////////////////////////////////////////////////////////// +// c4_StringArray + +d4_inline c4_StringArray::c4_StringArray () +{ +} + +d4_inline int c4_StringArray::GetSize() const +{ + return _ptrs.GetSize(); +} + +d4_inline const char* c4_StringArray::GetAt(int nIndex) const +{ + return (const char*) _ptrs.GetAt(nIndex); +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/view.cpp b/akregator/src/mk4storage/metakit/src/view.cpp new file mode 100644 index 00000000..af2fc9fa --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/view.cpp @@ -0,0 +1,1271 @@ +// view.cpp -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Implementation of main classes not involved in persistence + */ + +#include "header.h" +#include "derived.h" +#include "custom.h" +#include "store.h" // for RelocateRows +#include "field.h" // for RelocateRows +#include "persist.h" +#include "remap.h" + +#if !q4_INLINE +#include "mk4.inl" +#endif + +///////////////////////////////////////////////////////////////////////////// +// c4_ThreadLock + +class c4_ThreadLock +{ +public: + c4_ThreadLock (); + + class Hold + { + public: + Hold (); + ~Hold (); + }; +}; + +///////////////////////////////////////////////////////////////////////////// + +#if q4_MULTI + +#if q4_WIN32 + +/* + * On Win32, use a critical section to protect the global symbol table. + * Also uses special thread-safe calls to inc/dec all reference counts. + * + * This implementation replaces the previous use of TLS, which cannot + * be used without special tricks in dynamically loaded DLL's, as is + * required for OCX/ActiveX use (which uses LoadLibrary). + * + * Note: Could have used MFC's CCriticalSection and CSingleLock classes, + * but the code below is so trivial that it hardly matters. + */ + +#if q4_MSVC && !q4_STRICT +#pragma warning(disable: 4201) // nonstandard extension used : ... +#endif +#include <windows.h> + + static CRITICAL_SECTION gCritSect; + + c4_ThreadLock::c4_ThreadLock () + { + InitializeCriticalSection(&gCritSect); + } + + c4_ThreadLock::Hold::Hold () + { + EnterCriticalSection(&gCritSect); + } + + c4_ThreadLock::Hold::~Hold () + { + LeaveCriticalSection(&gCritSect); + } + +#else /* q4_WIN32 */ + +#include <pthread.h> + + static pthread_mutex_t gMutex; + + d4_inline c4_ThreadLock::c4_ThreadLock () + { + pthread_mutex_init(&gMutex, 0); + } + + d4_inline c4_ThreadLock::Hold::Hold () + { + d4_dbgdef(int r =) + pthread_mutex_lock(&gMutex); + d4_assert(r == 0); + } + + d4_inline c4_ThreadLock::Hold::~Hold () + { + d4_dbgdef(int r =) + pthread_mutex_unlock(&gMutex); + d4_assert(r == 0); + } + +#endif /* q4_WIN32 */ + +#else /* q4_MULTI */ + +// All other implementations revert to the simple "thread-less" case. + + d4_inline c4_ThreadLock::c4_ThreadLock () + { + } + + d4_inline c4_ThreadLock::Hold::Hold () + { + } + + d4_inline c4_ThreadLock::Hold::~Hold () + { + } + +#endif + +///////////////////////////////////////////////////////////////////////////// + +#if q4_LOGPROPMODS + + static FILE* sPropModsFile = 0; + static int sPropModsProp = -1; + +FILE* f4_LogPropMods(FILE* fp_, int propId_) +{ + FILE* prevfp = sPropModsFile; + sPropModsFile = fp_; + sPropModsProp = propId_; + return prevfp; +} + +void f4_DoLogProp(const c4_Handler* hp_, int id_, const char* fmt_, int arg_) +{ + if (sPropModsFile != 0 && (sPropModsProp < 0 || sPropModsProp == id_)) { + fprintf(sPropModsFile, "handler 0x%x id %d: ", hp_, id_); + fprintf(sPropModsFile, fmt_, arg_); + } +} + +#endif + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_View + * + * A collection of data rows. This is the central public data structure of + * Metakit (often called "table", "array", or "relation" in other systems). + * + * Views are smart pointers to the actual collections, setting a view to a new + * value does not alter the collection to which this view pointed previously. + * + * The elements of views can be referred to by their 0-based index, which + * produces a row-reference of type c4_RowRef. These row references can + * be copied, used to get or set properties, or dereferenced (in which case + * an object of class c4_Row is returned). Taking the address of a row + * reference produces a c4_Cursor, which acts very much like a pointer. + * + * The following code creates a view with 1 row and 2 properties: + * @code + * c4_StringProp pName ("name"); + * c4_IntProp pAge ("age"); + * + * c4_Row data; + * pName (data) = "John Williams"; + * pAge (data) = 43; + * + * c4_View myView; + * myView.Add(row); + * @endcode + */ + +/// Construct a view based on a sequence +c4_View::c4_View (c4_Sequence* seq_) + : _seq (seq_) +{ + if (!_seq) + _seq = d4_new c4_HandlerSeq (0); + + _IncSeqRef(); +} + +/// Construct a view based on a custom viewer +c4_View::c4_View (c4_CustomViewer* viewer_) + : _seq (0) +{ + d4_assert(viewer_); + + _seq = d4_new c4_CustomSeq (viewer_); + + _IncSeqRef(); +} + +/// Construct a view based on an input stream +c4_View::c4_View (c4_Stream* stream_) + : _seq (c4_Persist::Load(stream_)) +{ + if (_seq == 0) + _seq = d4_new c4_HandlerSeq (0); + + _IncSeqRef(); +} + +/// Construct an empty view with one property +c4_View::c4_View (const c4_Property& prop_) + : _seq (d4_new c4_HandlerSeq (0)) +{ + _IncSeqRef(); + + _seq->PropIndex(prop_); +} + +/// Copy constructor +c4_View::c4_View (const c4_View& view_) + : _seq (view_._seq) +{ + _IncSeqRef(); +} + +/// Makes this view the same as another one. +c4_View& c4_View::operator= (const c4_View& view_) +{ + if (_seq != view_._seq) { + _DecSeqRef(); + _seq = view_._seq; + _IncSeqRef(); + } + return *this; +} + +/** Get a single data item in a generic way + * + * This can be used to access view data in a generalized way. + * Useful for c4_CustomViewers which are based on other views. + * @return true if the item is non-empty + */ +bool c4_View::GetItem(int row_, int col_, c4_Bytes& buf_) const +{ + const c4_Property& prop = NthProperty(col_); + return prop (GetAt(row_)).GetData(buf_); +} + +/// Set a single data item in a generic way +void c4_View::SetItem(int row_, int col_, const c4_Bytes& buf_) const +{ + const c4_Property& prop = NthProperty(col_); + prop (GetAt(row_)).SetData(buf_); +} + +/// Set an entry, growing the view if needed +void c4_View::SetAtGrow(int index_, const c4_RowRef& newElem_) +{ + if (index_ >= GetSize()) + SetSize(index_ + 1); + + _seq->SetAt(index_, &newElem_); +} + +/** Add a new entry, same as "SetAtGrow(GetSize(), ...)" + * @return the index of the newly added row + */ +int c4_View::Add(const c4_RowRef& newElem_) +{ + int i = GetSize(); + InsertAt(i, newElem_); + return i; +} + +/** Construct a new view with a copy of the data + * + * The copy is a deep copy, because subviews are always copied in full. + */ +c4_View c4_View::Duplicate() const +{ + // insert all rows, sharing any subviews as needed + c4_View result = Clone(); + result.InsertAt(0, _seq); + return result; +} + +/** Constructs a new view with the same structure but no data + * + * Structural information can only be maintain for the top level, + * subviews will be included but without any properties themselves. + */ +c4_View c4_View::Clone() const +{ + c4_View view; + + for (int i = 0; i < NumProperties(); ++i) + view._seq->PropIndex(NthProperty(i)); + + return view; +} + +/** Adds a property column to a view if not already present + * @return 0-based column position of the property + */ +int c4_View::AddProperty(const c4_Property& prop_) +{ + return _seq->PropIndex(prop_); +} + +/** Returns the N-th property (using zero-based indexing) + * @return reference to the specified property + */ +const c4_Property& c4_View::NthProperty( + int index_ ///< the zero-based property index + ) const +{ + return _seq->NthHandler(index_).Property(); +} + +/** Find the index of a property, given its name + * @return 0-based column index + * @retval -1 property not present in this view + */ +int c4_View::FindPropIndexByName( + const char* name_ ///< property name (case insensitive) + ) const +{ + // use a slow linear scan to find the untyped property by name + for (int i = 0; i < NumProperties(); ++i) { + c4_String s = NthProperty(i).Name(); + if (s.CompareNoCase(name_) == 0) + return i; + } + + return -1; +} + +/** Defines a column for a property. + * + * The following code defines an empty view with three properties: + * @code + * c4_IntProp p1, p2, p3; + * c4_View myView = (p1, p2, p3); + * @endcode + * @return the new view object (without any data rows) + * @sa c4_Property + */ +c4_View c4_View::operator, (const c4_Property& prop_) const +{ + c4_View view = Clone(); + view.AddProperty(prop_); + return view; +} + +/// Insert copies of all rows of the specified view +void c4_View::InsertAt(int index_, const c4_View& view_) +{ + int n = view_.GetSize(); + if (n > 0) { + c4_Row empty; + + InsertAt(index_, empty, n); + + for (int i = 0; i < n; ++i) + SetAt(index_ + i, view_[i]); + } +} + +bool c4_View::IsCompatibleWith(const c4_View& dest_) const +{ + // can't determine table without handlers (and can't be a table) + if (NumProperties() == 0 || dest_.NumProperties() == 0) + return false; + + c4_Sequence* s1 = _seq; + c4_Sequence* s2 = dest_._seq; + c4_HandlerSeq* h1 = (c4_HandlerSeq*) s1->HandlerContext(0); + c4_HandlerSeq* h2 = (c4_HandlerSeq*) s2->HandlerContext(0); + + // both must be real handler views, not derived ones + if (h1 != s1 || h2 != s2) + return false; + + // both must not contain any temporary handlers + if (s1->NumHandlers() != h1->NumFields() || + s2->NumHandlers() != h2->NumFields()) + return false; + + // both must be in the same storage + if (h1->Persist() == 0 || h1->Persist() != h2->Persist()) + return false; + + // both must have the same structure (is this expensive?) + c4_String d1 = h1->Definition().Description(true); + c4_String d2 = h1->Definition().Description(true); + return d1 == d2; // ignores all names +} + +/** Move attached rows to somewhere else in same storage + * + * There is a lot of trickery going on here. The whole point of this + * code is that moving rows between (compatible!) subviews should not + * use copying when potentially large memo's and subviews are involved. + * In that case, the best solution is really to move pointers, not data. + */ +void c4_View::RelocateRows(int from_, int count_, c4_View& dest_, int pos_) +{ + if (count_ < 0) + count_ = GetSize() - from_; + if (pos_ < 0) + pos_ = dest_.GetSize(); + + d4_assert(0 <= from_ && from_ <= GetSize()); + d4_assert(0 <= count_ && from_ + count_ <= GetSize()); + d4_assert(0 <= pos_ && pos_ <= dest_.GetSize()); + + if (count_ > 0) { + // the destination must not be inside the source rows + d4_assert(&dest_ != this || from_ > pos_ || pos_ >= from_ + count_); + + // this test is slow, so do it only as a debug check + d4_assert(IsCompatibleWith(dest_)); + + // make space, swap rows, drop originals + c4_Row empty; + dest_.InsertAt(pos_, empty, count_); + + // careful if insert moves origin + if (&dest_ == this && pos_ <= from_) + from_ += count_; + + for (int i = 0; i < count_; ++i) + ((c4_HandlerSeq*) _seq)->ExchangeEntries(from_ + i, + *(c4_HandlerSeq*) dest_._seq, pos_ + i); + + RemoveAt(from_, count_); + } +} + +/** Create view with all rows in natural (property-wise) order + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but unfortunately there are some major + * limitations with this scheme - one of them being that deriving another + * view from this sorted one will not properly track changes. + */ +c4_View c4_View::Sort() const +{ + return f4_CreateSort(*_seq); +} + +/** Create view sorted according to the specified properties + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but unfortunately there are some major + * limitations with this scheme - one of them being that deriving another + * view from this sorted one will not properly track changes. + */ +c4_View c4_View::SortOn(const c4_View& up_) const +{ + c4_Sequence* seq = f4_CreateProject(*_seq, *up_._seq, true); + + return f4_CreateSort(*seq); +} + +/** Create sorted view, with some properties sorted in reverse + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but unfortunately there are some major + * limitations with this scheme - one of them being that deriving another + * view from this sorted one will not properly track changes. + */ +c4_View c4_View::SortOnReverse( + const c4_View& up_, ///< the view which defines the sort order + const c4_View& down_ ///< subset of up_, defines reverse order + ) const +{ + c4_Sequence* seq = f4_CreateProject(*_seq, *up_._seq, true); + + return f4_CreateSort(*seq, down_._seq); +} + +/** Create view with rows matching the specified value + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but this only works when based on views + * which properly generate change notifications (.e. raw views, other + * selections, and projections). + */ +c4_View c4_View::Select(const c4_RowRef& crit_) const +{ + return f4_CreateFilter(*_seq, &crit_, &crit_); +} + +/** Create view with row values within the specified range + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but this only works when based on views + * which properly generate change notifications (.e. raw views, other + * selections, and projections). + */ +c4_View c4_View::SelectRange( + const c4_RowRef& low_, ///< values of the lower bounds (inclusive) + const c4_RowRef& high_ ///< values of the upper bounds (inclusive) + ) const +{ + return f4_CreateFilter(*_seq, &low_, &high_); +} + +/** Create view with the specified property arrangement + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but this only works when based on views + * which properly generate change notifications (.e. raw views, selections, + * and other projections). + */ +c4_View c4_View::Project(const c4_View& in_) const +{ + return f4_CreateProject(*_seq, *in_._seq, false); +} + +/** Create derived view with some properties omitted + * + * The result is virtual, it merely maintains a permutation to access the + * underlying view. This "derived" view uses change notification to track + * changes to the underlying view, but this only works when based on views + * which properly generate change notifications (.e. raw views, selections, + * and other projections). + */ +c4_View c4_View::ProjectWithout(const c4_View& out_) const +{ + return f4_CreateProject(*_seq, *_seq, false, out_._seq); +} + +/** Create view which is a segment/slice (default is up to end) + * + * Returns a view which is a subset, either a contiguous range, or + * a "slice" with element taken from every step_ entries. If the + * step is negative, the same entries are returned, but in reverse + * order (start_ is still lower index, it'll then be returned last). + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::Slice(int first_, int limit_, int step_) const +{ + return f4_CustSlice(*_seq, first_, limit_, step_); +} + +/** Create view which is the cartesian product with given view + * + * The cartesian product is defined as every combination of rows + * in both views. The number of entries is the product of the + * number of entries in the two views, properties which are present + * in both views will use the values defined in this view. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Product(const c4_View& view_) const +{ + return f4_CustProduct(*_seq, view_); +} + +/** Create view which remaps another given view + * + * Remapping constructs a view with the rows indicated by another + * view. The first property in the order_ view must be an int + * property with index values referring to this one. The size of + * the resulting view is determined by the order_ view and can + * differ, for example to act as a subset selection (if smaller). + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::RemapWith(const c4_View& view_) const +{ + return f4_CustRemapWith(*_seq, view_); +} + +/** Create view which pairs each row with corresponding row + * + * This is like a row-by-row concatenation. Both views must have + * the same number of rows, the result has all properties from + * this view plus any other properties from the other view. + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::Pair(const c4_View& view_) const +{ + return f4_CustPair(*_seq, view_); +} + +/** Create view with rows from another view appended + * + * Constructs a view which has all rows of this view, and all rows + * of the second view appended. The structure of the second view + * is assumed to be identical to this one. This operation is a bit + * similar to appending all rows from the second view, but it does + * not actually store the result anywhere, it just looks like it. + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::Concat(const c4_View& view_) const +{ + return f4_CustConcat(*_seq, view_); +} + +/** Create view with one property renamed (must be of same type) + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::Rename(const c4_Property& old_, const c4_Property& new_) const +{ + return f4_CustRename(*_seq, old_, new_); +} + +/** Create view with a subview, grouped by the specified properties + * + * This operation is similar to the SQL 'GROUP BY', but it takes + * advantage of the fact that Metakit supports nested views. The + * view returned from this member has one row per distinct group, + * with an extra view property holding the remaining properties. + * If there are N rows in the original view matching key X, then + * the result is a row for key X, with a subview of N rows. The + * properties of the subview are all the properties not in the key. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::GroupBy( + const c4_View& keys_, ///< properties in this view determine grouping + const c4_ViewProp& result_ ///< name of new subview defined in result + ) const +{ + return f4_CustGroupBy(*_seq, keys_, result_); +} + +/** Create view with count of duplicates, when grouped by key + * + * This is similar to c4_View::GroupBy, but it determines only the + * number of rows in each group and does not create a nested view. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Counts( + const c4_View& keys_, ///< properties in this view determine grouping + const c4_IntProp& result_ ///< new count property defined in result + ) const +{ + return f4_CustGroupBy(*_seq, keys_, result_); // third arg is c4_IntProp +} + +/** Create view with all duplicate rows omitted + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Unique() const +{ + c4_IntProp count ("#N#"); + return Counts(Clone(), count).ProjectWithout(count); +} + +/** Create view which is the set union (assumes no duplicate rows) + * + * Calculates the set union. This will only work if both input + * views are sets, i.e. they have no duplicate rows in them. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Union(const c4_View& view_) const +{ + return Concat(view_).Unique(); +} + +/** Create view with all rows also in the given view (no dups) + * + * Calculates the set intersection. This will only work if both + * input views are sets, i.e. they have no duplicate rows in them. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Intersect(const c4_View& view_) const +{ + c4_View v = Concat(view_); + + // assume neither view has any duplicates + c4_IntProp count ("#N#"); + return v.Counts(Clone(), count).Select(count [2]).ProjectWithout(count); +} + +/** Create view with all rows not in both views (no dups) + * + * Calculates the "XOR" of two sets. This will only work if both + * input views are sets, i.e. they have no duplicate rows in them. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Different(const c4_View& view_) const +{ + c4_View v = Concat(view_); + + // assume neither view has any duplicates + c4_IntProp count ("#N#"); + return v.Counts(Clone(), count).Select(count [1]).ProjectWithout(count); +} + +/** Create view with all rows not in the given view (no dups) + * + * Calculates set-difference of this view minus arg view. Result + * is a subset, unlike c4_View::Different. Will only work if both + * input views are sets, i.e. they have no duplicate rows in them. + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Minus( + const c4_View& view_ ///< the second view + ) const +{ + // inefficient: calculate difference, then keep only those in self + return Intersect(Different(view_)); +} + +/** Create view with a specific subview expanded, like a join + * + * This operation is the inverse of c4_View::GroupBy, expanding + * all rows in specified subview and returning a view which looks + * as if the rows in each subview were "expanded in place". + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::JoinProp( + const c4_ViewProp& sub_, ///< name of the subview to expand + bool outer_ ///< true: keep rows with empty subviews + ) const +{ + return f4_CustJoinProp(*_seq, sub_, outer_); +} + +/** Create view which is the relational join on the given keys + * + * This view operation is based on a read-only custom viewer. + */ +c4_View c4_View::Join( + const c4_View& keys_, ///< properties in this view determine the join + const c4_View& view_, ///< second view participating in the join + bool outer_ ///< true: keep rows with no match in second view + ) const +{ + // inefficient: calculate difference, then keep only those in self + return f4_CustJoin(*_seq, keys_, view_, outer_); +} + +/** Create an identity view which only allows reading + * + * This view operation is based on a custom viewer. + */ +c4_View c4_View::ReadOnly() const +{ + return f4_CreateReadOnly(*_seq); +} + +/** Create mapped view which adds a hash lookup layer + * + * This view creates and manages a special hash map view, to implement a + * fast find on the key. The key is defined to consist of the first + * numKeys_ properties of the underlying view. + * + * The map_ view must be empty the first time this hash view is used, so + * that Metakit can fill it based on whatever rows are already present in + * the underlying view. After that, neither the underlying view nor the + * map view may be modified other than through this hash mapping layer. + * The defined structure of the map view must be "_H:I,_R:I". + * + * This view is modifiable. Insertions and changes to key field properties + * can cause rows to be repositioned to maintain hash uniqueness. Careful: + * when a row is changed in such a way that its key is the same as in another + * row, that other row will be deleted from the view. + * + * Example of use: + * @code + * c4_View data = storage.GetAs("people[name:S,age:I]"); + * c4_View datah = storage.GetAs("people_H[_H:I,_R:I]"); + * c4_View hash = raw.Hash(datah, 1); + * ... hash.GetSize() ... + * hash.Add(...) + * @endcode + */ +c4_View c4_View::Hash(const c4_View& map_, int numKeys_) const +{ + return f4_CreateHash(*_seq, numKeys_, map_._seq); +} + +/** Create mapped view which blocks its rows in two levels + * + * This view acts like a large flat view, even though the actual rows are + * stored in blocks, which are rebalanced automatically to maintain a good + * trade-off between block size and number of blocks. + * + * The underlying view must be defined with a single view property, with + * the structure of the subview being as needed. An example of a blocked + * view definition which will act like a single one containing 2 properties: + * @code + * c4_View raw = storage.GetAs("people[_B[name:S,age:I]]"); + * c4_View flat = raw.Blocked(); + * ... flat.GetSize() ... + * flat.InsertAt(...) + * @endcode + * + * This view operation is based on a custom viewer and is modifiable. + */ +c4_View c4_View::Blocked() const +{ + return f4_CreateBlocked(*_seq); +} + +/** Create mapped view which keeps its rows ordered + * + * This is an identity view, which has as only use to inform Metakit that + * the underlying view can be considered to be sorted on its first numKeys_ + * properties. The effect is that c4_View::Find will try to use binary + * search when the search includes key properties (results will be identical + * to unordered views, the find will just be more efficient). + * + * This view is modifiable. Insertions and changes to key field properties + * can cause rows to be repositioned to maintain the sort order. Careful: + * when a row is changed in such a way that its key is the same as in another + * row, that other row will be deleted from the view. + * + * This view can be combined with c4_View::Blocked, to create a 2-level + * btree structure. + */ +c4_View c4_View::Ordered(int numKeys_) const +{ + return f4_CreateOrdered(*_seq, numKeys_); +} + +/** Create mapped view which maintains an index permutation + * + * This is an identity view which somewhat resembles the ordered view, it + * maintains a secondary "map" view to contain the permutation to act as + * an index. The indexed view presents the same order of rows as the + * underlying view, but the index map is set up in such a way that binary + * search is possible on the keys specified. When the "unique" parameter + * is true, insertions which would create a duplicate key are ignored. + * + * This view is modifiable. Careful: when a row is changed in such a way + * that its key is the same as in another row, that other row will be + * deleted from the view. + */ +c4_View c4_View::Indexed(const c4_View& map_, const c4_View& props_, + bool unique_) const +{ + return f4_CreateIndexed(*_seq, *map_._seq, props_, unique_); +} + +/** Return the index of the specified row in this view (or -1) + * + * This function can be used to "unmap" an index of a derived view back + * to the original underlying view. + */ +int c4_View::GetIndexOf(const c4_RowRef& row_) const +{ + c4_Cursor cursor = &row_; + + return cursor._seq->RemapIndex(cursor._index, _seq); +} + +/// Restrict the search range for rows +int c4_View::RestrictSearch(const c4_RowRef& c_, int& pos_, int& count_) +{ + return _seq->RestrictSearch(&c_, pos_, count_) ? 0 : ~0; +} + +/** Find index of the the next entry matching the specified key. + * + * Defaults to linear search, but hash- and ordered-views will use a better + * algorithm if possible. Only the properties present in the search key + * are used to determine whether a row matches the key. + * @return position where match occurred + * @retval -1 if not found + */ +int c4_View::Find( + const c4_RowRef& crit_, ///< the value to look for + int start_ ///< the index to start with + ) const +{ + d4_assert(start_ >= 0); + + c4_Row copy = crit_; // the lazy (and slow) solution: make a copy + + int count = GetSize() - start_; + if (_seq->RestrictSearch(©, start_, count)) { + c4_View refView = copy.Container(); + c4_Sequence* refSeq = refView._seq; + d4_assert(refSeq != 0); + + c4_Bytes data; + + for (int j = 0; j < count; ++j) { + int i; + + for (i = 0; i < refSeq->NumHandlers(); ++i) { + c4_Handler& h = refSeq->NthHandler(i); // no context issues + + if (!_seq->Get(start_ + j, h.PropId(), data)) + h.ClearBytes(data); + + if (h.Compare(0, data) != 0) // always row 0 + break; + } + + if (i == refSeq->NumHandlers()) + return start_ + j; + } + } + + return -1; +} + +/** Search for a key, using the native sort order of the view + * @return position where found, or where it may be inserted, + * this position can also be just past the last row + */ +int c4_View::Search(const c4_RowRef& crit_) const +{ + int l = -1, u = GetSize(); + while (l + 1 != u) { + const int m = (l + u) >> 1; + if (_seq->Compare(m, &crit_) < 0) + //if (crit_ > (*this)[m]) // Dec 2001: see comments below + l = m; + else + u = m; + } + + return u; +} + +/// Return number of matching keys, and pos of first one as arg +int c4_View::Locate(const c4_RowRef& crit_, int* pos_) const +{ + // Dec 2001: fixed a problem with searching of partial rows. + // + // There is an *extremely* tricky issue in here, in that the + // comparison operator for rows is not symmetric. So in the + // general case, "a == b" is not euivalent to "b == a". This + // is without doubt a design mistake (and should have at least + // been named differently). + // + // The reason is that the number of properties in both rowrefs + // need not be the same. Only the properties of the leftmost + // rowref are compared against the other one. This also applies + // to the other comparisons, i.e. !=, <, >, <=, and >=. + // + // All Compare calls below have been changed to use comparisons + // in the proper order and now use "rowref <op> rowref" syntax. + + c4_Cursor curr (*(c4_Sequence*) _seq, 0); // loses const + + int l = -1, u = GetSize(); + while (l + 1 != u) { + curr._index = (l + u) >> 1; + if (crit_ > *curr) + l = curr._index; + else + u = curr._index; + } + + if (pos_ != 0) + *pos_ = u; + + // only look for more if the search hit an exact match + curr._index = u; + if (u == GetSize() || crit_ != *curr) + return 0; + + // as Jon Bentley wrote in DDJ Apr 2000, setting l2 to -1 is better than u + int l2 = -1, u2 = GetSize(); + while (l2 + 1 != u2) { + curr._index = (l2 + u2) >> 1; + if (crit_ >= *curr) + l2 = curr._index; + else + u2 = curr._index; + } + + return u2 - u; +} + +/// Compare two views lexicographically (rows 0..N-1). +int c4_View::Compare(const c4_View& view_) const +{ + if (_seq == view_._seq) + return 0; + + int na = GetSize(); + int nb = view_.GetSize(); + int i; + + for (i = 0; i < na && i < nb; ++i) + if (GetAt(i) != view_.GetAt(i)) + return GetAt(i) < view_.GetAt(i) ? -1 : +1; + + return na == nb ? 0 : i < na ? +1 : -1; +} + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_Cursor + * + * An iterator for collections of rows (views). + * + * Cursor objects can be used to point to specific entries in a view. + * A cursor acts very much like a pointer to a row in a view, and is + * returned when taking the address of a c4_RowRef. Dereferencing + * a cursor leads to the original row reference again. You can construct a + * cursor for a c4_Row, but since such rows are not part of a collection, + * incrementing or decrementing these cursors is meaningless (and wrong). + * + * The usual range of pointer operations can be applied to these objects: + * pre/post-increment and decrement, adding or subtracting integer offsets, + * as well as the full range of comparison operators. If two cursors + * point to entries in the same view, their difference can be calculated. + * + * As with regular pointers, care must be taken to avoid running off of + * either end of a view (the debug build includes assertions to check this). + */ + +/** @class c4_RowRef + * + * Reference to a data row, can be used on either side of an assignment. + * + * Row references are created when dereferencing a c4_Cursor or when + * indexing an element of a c4_View. Assignment will change the + * corresponding item. Rows (objects of type c4_Row) are a special + * case of row references, consisting of a view with exactly one item. + * + * Internally, row references are very similar to cursors, in fact they are + * little more than a wrapper around them. The essential difference is one + * of semantics: comparing row references compares contents, copying row + * references copies the contents, whereas cursor comparison and copying + * deals with the pointer to the row, not its contents. + */ + +///////////////////////////////////////////////////////////////////////////// +// c4_Row + +c4_Row::c4_Row () + : c4_RowRef (* Allocate()) +{ +} + +c4_Row::c4_Row (const c4_Row& row_) + : c4_RowRef (* Allocate()) +{ + operator= (row_); +} + +c4_Row::c4_Row (const c4_RowRef& rowRef_) + : c4_RowRef (* Allocate()) +{ + operator= (rowRef_); +} + +c4_Row::~c4_Row () +{ + Release(_cursor); +} + +c4_Row& c4_Row::operator= (const c4_Row& row_) +{ + return operator= ((const c4_RowRef&) row_); +} + +/// Assignment from a reference to a row. +c4_Row& c4_Row::operator= (const c4_RowRef& rowRef_) +{ + d4_assert(_cursor._seq != 0); + + if (_cursor != &rowRef_) { + d4_assert(_cursor._index == 0); + _cursor._seq->SetAt(0, &rowRef_); + } + + return *this; +} + +/// Adds all properties and values into this row. +void c4_Row::ConcatRow(const c4_RowRef& rowRef_) +{ + d4_assert(_cursor._seq != 0); + + c4_Cursor cursor = &rowRef_; // trick to access private rowRef_._cursor + d4_assert(cursor._seq != 0); + + c4_Sequence& rhSeq = * cursor._seq; + + c4_Bytes data; + + for (int i = 0; i < rhSeq.NumHandlers(); ++i) { + c4_Handler& h = rhSeq.NthHandler(i); + + h.GetBytes(cursor._index, data); + _cursor._seq->Set(_cursor._index, h.Property(), data); + } +} + +c4_Row operator+ (const c4_RowRef& a_, const c4_RowRef& b_) +{ + c4_Row row = a_; + row.ConcatRow(b_); + return row; +} + +c4_Cursor c4_Row::Allocate() +{ + c4_Sequence* seq = d4_new c4_HandlerSeq (0); + seq->IncRef(); + + seq->Resize(1); + + return c4_Cursor (*seq, 0); +} + +void c4_Row::Release(c4_Cursor row_) +{ + d4_assert(row_._seq != 0); + d4_assert(row_._index == 0); + + row_._seq->DecRef(); +} + +///////////////////////////////////////////////////////////////////////////// + +/** @class c4_Property + * + * Base class for the basic data types. + * + * Property objects exist independently of view, row, and storage objects. + * They have a name and type, and can appear in any number of views. + * You will normally only use derived classes, to maintain strong typing. + */ + + // This is a workaround for the fact that the initialization order of + // static objects is not always adequate (implementation dependent). + // Extremely messy solution, to allow statically declared properties. + // + // These are the only static variables in the entire Metakit core lib. + + static c4_ThreadLock* sThreadLock = 0; + static c4_StringArray* sPropNames = 0; + static c4_DWordArray* sPropCounts = 0; + + /// Call this to get rid of some internal datastructues (on exit) + void c4_Property::CleanupInternalData() + { + delete sPropNames; + sPropNames = 0; // race + + delete sPropCounts; + sPropCounts = 0; // race + + delete sThreadLock; + sThreadLock = 0; // race + } + +c4_Property::c4_Property (char type_, const char* name_) + : _type (type_) +{ + if (sThreadLock == 0) + sThreadLock = d4_new c4_ThreadLock; + + c4_ThreadLock::Hold lock; // grabs the lock until end of scope + + if (sPropNames == 0) + sPropNames = d4_new c4_StringArray; + + if (sPropCounts == 0) + sPropCounts = d4_new c4_DWordArray; + + c4_String temp = name_; + + _id = sPropNames->GetSize(); + while (-- _id >= 0) { + const char* p = sPropNames->GetAt(_id); + // optimize for first char case-insensitive match + if (((*p ^ *name_) & ~0x20) == 0 && temp.CompareNoCase(p) == 0) + break; + } + + if (_id < 0) { + int size = sPropCounts->GetSize(); + + for (_id = 0; _id < size; ++_id) + if (sPropCounts->GetAt(_id) == 0) + break; + + if (_id >= size) { + sPropCounts->SetSize(_id + 1); + sPropNames->SetSize(_id + 1); + } + + sPropCounts->SetAt(_id, 0); + sPropNames->SetAt(_id, name_); + } + + Refs(+1); +} + +c4_Property::c4_Property (const c4_Property& prop_) + : _id (prop_.GetId()), _type (prop_.Type()) +{ + c4_ThreadLock::Hold lock; + + d4_assert(sPropCounts != 0); + d4_assert(sPropCounts->GetAt(_id) > 0); + + Refs(+1); +} + +c4_Property::~c4_Property () +{ + c4_ThreadLock::Hold lock; + + Refs(-1); +} + +void c4_Property::operator= (const c4_Property& prop_) +{ + c4_ThreadLock::Hold lock; + + prop_.Refs(+1); + Refs(-1); + + _id = prop_.GetId(); + _type = prop_.Type(); +} + + /// Return the name of this property +const char* c4_Property::Name() const +{ + c4_ThreadLock::Hold lock; + + d4_assert(sPropNames != 0); + return sPropNames->GetAt(_id); +} + +/** Adjust the reference count + * + * This is part of the implementation and shouldn't normally be called. + * This code is only called with the lock held, and always thread-safe. + */ +void c4_Property::Refs(int diff_) const +{ + d4_assert(diff_ == -1 || diff_ == +1); + + d4_assert(sPropCounts != 0); + sPropCounts->ElementAt(_id) += diff_; + +#if q4_CHECK + // get rid of the cache when the last property goes away + static t4_i32 sPropTotals; + + sPropTotals += diff_; + if (sPropTotals == 0) + CleanupInternalData(); +#endif +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/viewx.cpp b/akregator/src/mk4storage/metakit/src/viewx.cpp new file mode 100644 index 00000000..db30d14e --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/viewx.cpp @@ -0,0 +1,857 @@ +// viewx.cpp -- +// $Id$ +// This is part of Metakit, see http://www.equi4.com/metakit/ + +/** @file + * Implements c4_Sequence, c4_Reference, and c4_...Ref + */ + +#include "header.h" +#include "handler.h" +#include "store.h" +#include "column.h" + +///////////////////////////////////////////////////////////////////////////// + +c4_Sequence::c4_Sequence () + : _refCount (0), _dependencies (0), _propertyLimit (0), _tempBuf (0) +{ +} + +c4_Sequence::~c4_Sequence () +{ + d4_assert(_refCount == 0); + + d4_assert(!_dependencies); // there can be no dependencies left + + ClearCache(); + + delete _tempBuf; +} + +c4_Persist* c4_Sequence::Persist() const +{ + return 0; +} + + /// Increment the reference count of this sequence +void c4_Sequence::IncRef() +{ + ++_refCount; + + d4_assert(_refCount != 0); +} + + /// Decrement the reference count, delete objects when last +void c4_Sequence::DecRef() +{ + d4_assert(_refCount != 0); + + if (--_refCount == 0) + delete this; +} + + /// Return the current reference count +int c4_Sequence::NumRefs() const +{ + return _refCount; +} + + /// Compare the specified row with another one +int c4_Sequence::Compare(int index_, c4_Cursor cursor_) const +{ + d4_assert(cursor_._seq != 0); + + c4_Bytes data; + + for (int colNum = 0; colNum < NumHandlers(); ++colNum) + { + c4_Handler& h = NthHandler(colNum); + + const c4_Sequence* hc = HandlerContext(colNum); + int i = RemapIndex(index_, hc); + + if (!cursor_._seq->Get(cursor_._index, h.PropId(), data)) + h.ClearBytes(data); + + int f = h.Compare(i, data); + if (f != 0) + return f; + } + + return 0; +} + + /// Restrict the search range for rows +bool c4_Sequence::RestrictSearch(c4_Cursor, int&, int&) +{ + return true; +} + + /// Replace the contents of a specified row +void c4_Sequence::SetAt(int index_, c4_Cursor newElem_) +{ + d4_assert(newElem_._seq != 0); + + c4_Bytes data; + + c4_Notifier change (this); + if (GetDependencies()) + change.StartSetAt(index_, newElem_); + + for (int i = 0; i < newElem_._seq->NumHandlers(); ++i) + { + c4_Handler& h = newElem_._seq->NthHandler(i); + + // added 06-12-1999 to do index remapping for derived seq's + const c4_Sequence* hc = newElem_._seq->HandlerContext(i); + int ri = newElem_._seq->RemapIndex(newElem_._index, hc); + + h.GetBytes(ri, data); + +// Set(index_, cursor._seq->NthProperty(i), data); + int colNum = PropIndex(h.Property()); + d4_assert(colNum >= 0); + + NthHandler(colNum).Set(index_, data); + } + + // if number of props in dest is larger after adding, clear the rest + // this way, new props get copied and undefined props get cleared + if (newElem_._seq->NumHandlers() < NumHandlers()) + { + for (int j = 0; j < NumHandlers(); ++j) + { + c4_Handler& h = NthHandler(j); + + // if the property does not appear in the source + if (newElem_._seq->PropIndex(h.PropId()) < 0) + { + h.ClearBytes(data); + h.Set(index_, data); + } + } + } +} + + /// Remap the index to an underlying view +int c4_Sequence::RemapIndex(int index_, const c4_Sequence* seq_) const +{ + return seq_ == this ? index_ : -1; +} + + /// Gives access to a general purpose temporary buffer +c4_Bytes& c4_Sequence::Buffer() +{ + if (_tempBuf == 0) + _tempBuf = d4_new c4_Bytes; + return *_tempBuf; +} + + // 1.8.5: extra buffer to hold returned description strings +const char* c4_Sequence::UseTempBuffer(const char* str_) +{ + return strcpy((char*) Buffer().SetBuffer(strlen(str_) + 1), str_); +} + + /// Change number of rows, either by inserting or removing them +void c4_Sequence::Resize(int newSize_, int) +{ + if (NumHandlers() > 0) + { + int diff = newSize_ - NumRows(); + + if (diff > 0) + { + c4_Row empty; // make sure this doesn't recurse, see below + InsertAt(NumRows(), &empty, diff); + } + else if (diff < 0) + RemoveAt(newSize_, - diff); + } + else // need special case to avoid recursion for c4_Row allocations + SetNumRows(newSize_); +} + + /// Insert one or more rows into this sequence +void c4_Sequence::InsertAt(int index_, c4_Cursor newElem_, int count_) +{ + d4_assert(newElem_._seq != 0); + + c4_Notifier change (this); + if (GetDependencies()) + change.StartInsertAt(index_, newElem_, count_); + + SetNumRows(NumRows() + count_); + + c4_Bytes data; + + for (int i = 0; i < newElem_._seq->NumHandlers(); ++i) + { + c4_Handler& h = newElem_._seq->NthHandler(i); + + // added 06-12-1999 to do index remapping for derived seq's + const c4_Sequence* hc = newElem_._seq->HandlerContext(i); + int ri = newElem_._seq->RemapIndex(newElem_._index, hc); + + h.GetBytes(ri, data); + + int colNum = PropIndex(h.Property()); + d4_assert(colNum >= 0); + + if (h.Property().Type() == 'V') + { + // special treatment for subviews, insert empty, then overwrite + // changed 19990904 - probably fixes a long-standing limitation + c4_Bytes temp; + h.ClearBytes(temp); + + c4_Handler& h2 = NthHandler(colNum); + h2.Insert(index_, temp, count_); + for (int j = 0; j < count_; ++j) + h2.Set(index_ + j, data); + } + else + NthHandler(colNum).Insert(index_, data, count_); + } + + // if number of props in dest is larger after adding, clear the rest + // this way, new props get copied and undefined props get cleared + if (newElem_._seq->NumHandlers() < NumHandlers()) + { + for (int j = 0; j < NumHandlers(); ++j) + { + c4_Handler& h = NthHandler(j); + + // if the property does not appear in the source + if (newElem_._seq->PropIndex(h.PropId()) < 0) + { + h.ClearBytes(data); + h.Insert(index_, data, count_); + } + } + } +} + + /// Remove one or more rows from this sequence +void c4_Sequence::RemoveAt(int index_, int count_) +{ + c4_Notifier change (this); + if (GetDependencies()) + change.StartRemoveAt(index_, count_); + + SetNumRows(NumRows() - count_); + + //! careful, this does no index remapping, wrong for derived seq's + for (int i = 0; i < NumHandlers(); ++i) + NthHandler(i).Remove(index_, count_); +} + + /// Move a row to another position +void c4_Sequence::Move(int from_, int to_) +{ + c4_Notifier change (this); + if (GetDependencies()) + change.StartMove(from_, to_); + + //! careful, this does no index remapping, wrong for derived seq's + for (int i = 0; i < NumHandlers(); ++i) + NthHandler(i).Move(from_, to_); +} + + /// Return the id of the N-th property +int c4_Sequence::NthPropId(int index_) const +{ + return NthHandler(index_).PropId(); +} + +void c4_Sequence::ClearCache() +{ + if (_propertyLimit > 0) + { + delete [] _propertyMap; // property indexes may change + _propertyLimit = 0; + } +} + + /// Find the index of a property by its id +int c4_Sequence::PropIndex(int propId_) +{ +//! CACHING NOTE: derived views will fail if underlying view is restructured +// still, this cache is kept, since sort will fail anyway... +// The only safe change in these cases is adding new properties at the end. + + // use the map for the fastest result once known + if (propId_ < _propertyLimit && _propertyMap[propId_] >= 0) + return _propertyMap[propId_]; + + // locate the property using a linear search, return if not present + int n = NumHandlers(); + do + if (--n < 0) + return -1; + while (NthPropId(n) != propId_); + + // if the map is too small, resize it (with a little slack) + if (propId_ >= _propertyLimit) + { + int round = (propId_ + 8) & ~0x07; + short* vec = d4_new short [round]; + + for (int i = 0; i < round; ++i) + vec[i] = i < _propertyLimit ? _propertyMap[i] : -1; + + if (_propertyLimit > 0) + delete [] _propertyMap; + + _propertyMap = vec; + _propertyLimit = round; + } + + // we have a map, adjust the entry and return + return _propertyMap[propId_] = n; +} + + /// Find the index of a property, or create a new entry +int c4_Sequence::PropIndex(const c4_Property& prop_) +{ + int pos = PropIndex(prop_.GetId()); + if (pos >= 0) + { + d4_assert(NthHandler(pos).Property().Type() == prop_.Type()); + return pos; + } + + c4_Handler* h = CreateHandler(prop_); + d4_assert(h != 0); + + int i = AddHandler(h); + if (i >= 0 && NumRows() > 0) + { + c4_Bytes data; + h->ClearBytes(data); + h->Insert(0, data, NumRows()); + } + + return i; +} + +const char* c4_Sequence::Description() +{ + return 0; +} + +int c4_Sequence::ItemSize(int index_, int propId_) +{ + int colNum = PropIndex(propId_); + return colNum >= 0 ? NthHandler(colNum).ItemSize(index_) : -1; +} + +bool c4_Sequence::Get(int index_, int propId_, c4_Bytes& buf_) +{ + int colNum = PropIndex(propId_); + if (colNum < 0) + return false; + + NthHandler(colNum).GetBytes(index_, buf_); + return true; +} + +void c4_Sequence::Set(int index_, const c4_Property& prop_, const c4_Bytes& buf_) +{ + int colNum = PropIndex(prop_); + d4_assert(colNum >= 0); + + c4_Handler& h = NthHandler(colNum); + + c4_Notifier change (this); + if (GetDependencies()) + change.StartSet(index_, prop_.GetId(), buf_); + + if (buf_.Size()) + h.Set(index_, buf_); + else + { + c4_Bytes empty; + h.ClearBytes(empty); + h.Set(index_, empty); + } +} + + /// Register a sequence to receive change notifications +void c4_Sequence::Attach(c4_Sequence* child_) +{ + IncRef(); + + if (!_dependencies) + _dependencies = d4_new c4_Dependencies; + + _dependencies->Add(child_); +} + + /// Unregister a sequence which received change notifications +void c4_Sequence::Detach(c4_Sequence* child_) +{ + d4_assert(_dependencies != 0); + + if (!_dependencies->Remove(child_)) + { + delete _dependencies; + _dependencies = 0; + } + + DecRef(); +} + + /// Called just before a change is made to the sequence +c4_Notifier* c4_Sequence::PreChange(c4_Notifier&) +{ + d4_assert(0); // should not be called, because it should not attach + return 0; +} + + /// Called after changes have been made to the sequence +void c4_Sequence::PostChange(c4_Notifier&) +{ +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Reference& c4_Reference::operator= (const c4_Reference& value_) +{ + c4_Bytes result; + value_.GetData(result); + SetData(result); + + return *this; +} + +bool operator== (const c4_Reference& a_, const c4_Reference& b_) +{ + c4_Bytes buf1; + bool f1 = a_.GetData(buf1); + + c4_Bytes buf2; + bool f2 = b_.GetData(buf2); + + // if absent, fill either with zero bytes to match length + if (!f1) + buf1.SetBufferClear(buf2.Size()); + if (!f2) + buf2.SetBufferClear(buf1.Size()); + + return buf1 == buf2; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_IntRef::operator t4_i32 () const +{ + c4_Bytes result; + if (!GetData(result)) + return 0; + + d4_assert(result.Size() == sizeof (t4_i32)); + return *(const t4_i32*) result.Contents(); +} + +c4_IntRef& c4_IntRef::operator= (t4_i32 value_) +{ + SetData(c4_Bytes (&value_, sizeof value_)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// +#if !q4_TINY +///////////////////////////////////////////////////////////////////////////// + +c4_LongRef::operator t4_i64 () const +{ + c4_Bytes result; + if (!GetData(result)) + { + static t4_i64 zero; + return zero; + } + + d4_assert(result.Size() == sizeof (t4_i64)); + return *(const t4_i64*) result.Contents(); +} + +c4_LongRef& c4_LongRef::operator= (t4_i64 value_) +{ + SetData(c4_Bytes (&value_, sizeof value_)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_FloatRef::operator double () const +{ + c4_Bytes result; + if (!GetData(result)) + return 0; + + d4_assert(result.Size() == sizeof (float)); + return *(const float*) result.Contents(); +} + +c4_FloatRef& c4_FloatRef::operator= (double value_) +{ + float v = (float) value_; // loses precision + SetData(c4_Bytes (&v, sizeof v)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_DoubleRef::operator double () const +{ + c4_Bytes result; + if (!GetData(result)) + return 0; + + d4_assert(result.Size() == sizeof (double)); + return *(const double*) result.Contents(); +} + +c4_DoubleRef& c4_DoubleRef::operator= (double value_) +{ + SetData(c4_Bytes (&value_, sizeof value_)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// +#endif // !q4_TINY +///////////////////////////////////////////////////////////////////////////// + +c4_BytesRef::operator c4_Bytes () const +{ + c4_Bytes result; + GetData(result); + + // the result must immediately be used, its lifetime may be limited + return result; +} + +c4_BytesRef& c4_BytesRef::operator= (const c4_Bytes& value_) +{ + SetData(value_); + return *this; +} + +c4_Bytes c4_BytesRef::Access(t4_i32 off_, int len_) const +{ + c4_Bytes& buffer = _cursor._seq->Buffer(); + + int colNum = _cursor._seq->PropIndex(_property.GetId()); + if (colNum >= 0) + { + c4_Handler& h = _cursor._seq->NthHandler(colNum); + int sz = h.ItemSize(_cursor._index); + if (len_ == 0 || off_ + len_ > sz) + len_ = sz - off_; + + c4_Column* col = h.GetNthMemoCol(_cursor._index, true); + if (col != 0) + { + + if (len_ > 0) { + col->FetchBytes(off_, len_, buffer, true); + return buffer; + } + } + else // do it the hard way for custom/mapped views (2002-03-13) + { + c4_Bytes result; + GetData(result); + d4_assert(off_ + len_ <= result.Size()); + return c4_Bytes (result.Contents() + off_, len_, true); + } + } + + return c4_Bytes (); +} + +bool c4_BytesRef::Modify(const c4_Bytes& buf_, t4_i32 off_, int diff_) const +{ + int colNum = _cursor._seq->PropIndex(_property.GetId()); + if (colNum >= 0) + { + c4_Handler& h = _cursor._seq->NthHandler(colNum); + const int n = buf_.Size(); + const t4_i32 limit = off_ + n; // past changed bytes + const t4_i32 overshoot = limit - h.ItemSize(_cursor._index); + + if (diff_ < overshoot) + diff_ = overshoot; + + c4_Column* col = h.GetNthMemoCol(_cursor._index, true); + if (col != 0) + { + if (diff_ < 0) + col->Shrink(limit, - diff_); + else if (diff_ > 0) + // insert bytes in the highest possible spot + // if a gap is created, it will contain garbage + col->Grow(overshoot > 0 ? col->ColSize() : + diff_ > n ? off_ : limit - diff_, diff_); + + col->StoreBytes(off_, buf_); + } + else // do it the hard way for custom/mapped views (2002-03-13) + { + c4_Bytes orig; + GetData(orig); + + c4_Bytes result; + t4_byte* ptr = result.SetBuffer(orig.Size() + diff_); + + memcpy(ptr, orig.Contents(), off_); + memcpy(ptr + off_, buf_.Contents(), n); + memcpy(ptr + off_ + n, orig.Contents() + off_, orig.Size() - off_); + + SetData(result); + } + return true; + } + + return false; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_StringRef::operator const char* () const +{ + c4_Bytes result; + GetData(result); + + return result.Size() > 0 ? (const char*) result.Contents() : ""; +} + +c4_StringRef& c4_StringRef::operator= (const char* value_) +{ + SetData(c4_Bytes (value_, strlen(value_) + 1)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_ViewRef::operator c4_View () const +{ + c4_Bytes result; + if (!GetData(result)) + return (c4_Sequence*) 0; // resolve ambiguity + + d4_assert(result.Size() == sizeof (c4_Sequence*)); + return *(c4_Sequence* const*) result.Contents(); +} + +c4_ViewRef& c4_ViewRef::operator= (const c4_View& value_) +{ + SetData(c4_Bytes (&value_._seq, sizeof value_._seq)); + return *this; +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Stream::~c4_Stream () +{ +} + +///////////////////////////////////////////////////////////////////////////// + +c4_Strategy::c4_Strategy () + : _bytesFlipped (false), _failure (0), + _mapStart (0), _dataSize (0), _baseOffset (0), _rootPos (-1), _rootLen (-1) +{ +} + +c4_Strategy::~c4_Strategy () +{ + d4_assert(_mapStart == 0); +} + + /// Read a number of bytes +int c4_Strategy::DataRead(t4_i32, void*, int) +{ +/* + if (_mapStart != 0 && pos_ + length_ <= _dataSize) + { + memcpy(buffer_, _mapStart + pos_, length_); + return length_; + } +*/ + ++_failure; + return -1; +} + + /// Write a number of bytes, return true if successful +void c4_Strategy::DataWrite(t4_i32, const void*, int) +{ + ++_failure; +} + + /// Flush and truncate file +void c4_Strategy::DataCommit(t4_i32) +{ +} + + /// Override to support memory-mapped files +void c4_Strategy::ResetFileMapping() +{ +} + + /// Report total size of the datafile +t4_i32 c4_Strategy::FileSize() +{ + return _dataSize; +} + + /// Return a value to use as fresh generation counter +t4_i32 c4_Strategy::FreshGeneration() +{ + return 1; +} + + /// Define the base offset where data is stored +void c4_Strategy::SetBase(t4_i32 base_) +{ + t4_i32 off = base_ - _baseOffset; + _baseOffset = base_; + _dataSize -= off; + if (_mapStart != 0) + _mapStart += off; +} + +/* + end_ is file position to start from (0 defaults to FileSize()) + + result is the logical end of the datafile (or -1 if no data) + + This code uses a tiny state machine so all the code to read and decode + file marks is in one place within the loop. +*/ + + /// Scan datafile head/tail markers, return logical end of data +t4_i32 c4_Strategy::EndOfData(t4_i32 end_) +{ + enum { kStateAtEnd, kStateCommit, kStateHead, kStateOld, kStateDone }; + + t4_i32 pos = (end_ >= 0 ? end_ : FileSize()) - _baseOffset; + t4_i32 last = pos; + t4_i32 rootPos = 0; + t4_i32 rootLen = -1; // impossible value, flags old-style header + t4_byte mark [8]; + + for (int state = kStateAtEnd; state != kStateDone; ) + { + pos -= 8; + if (pos + _baseOffset < 0 && state != kStateOld) + { + // bad offset, try old-style header from start of file + pos = - _baseOffset; + state = kStateOld; + } + + if (DataRead(pos, &mark, sizeof mark) != sizeof mark) + return -1; + + t4_i32 count = 0; + for (int i = 1; i < 4; ++i) + count = (count << 8) + mark[i]; + + t4_i32 offset = 0; + for (int j = 4; j < 8; ++j) + offset = (offset << 8) + mark[j]; + + const bool isSkipTail = mark[0] == 0x80 && count == 0 && offset > 0; + const bool isCommitTail = mark[0] == 0x80 && count > 0 && offset > 0; + const bool isHeader = (mark[0] == 'J' || mark[0] == 'L') && + (mark[0] ^ mark[1]) == ('J' ^ 'L') && + mark[2] == 0x1A; + switch (state) + { + case kStateAtEnd: // no commit tail found yet + + if (isSkipTail) + { + pos -= offset; + last = pos; + } + else if (isCommitTail) + { + rootPos = offset; + rootLen = count; + state = kStateCommit; + } + else + { + pos = 8; + state = kStateOld; + } + break; + + case kStateCommit: // commit tail must be preceded by skip tail + + if (!isSkipTail) + return -1; + pos -= offset - 8; + state = kStateHead; + break; + + case kStateHead: // fetch the header + + if (!isHeader) + { + pos = 8; + state = kStateOld; + } + else + { + state = kStateDone; + } + break; + + case kStateOld: // old format, look for header in first 4 Kb + + if (isHeader && mark[3] == 0x80) + { + d4_assert(rootPos == 0); + for (int k = 8; --k >= 4; ) // old header is little-endian + rootPos = (rootPos << 8) + mark[k]; + state = kStateDone; + } + else + { + pos += 16; + if (pos > 4096) + return -1; + } + break; + } + } + + last += _baseOffset; // all seeks were relative to current offset + + if (end_ >= 0) // if end was specified, then adjust this strategy object + { + _baseOffset += pos; + d4_assert(_baseOffset >= 0); + if (_mapStart != 0) + { + _mapStart += pos; + _dataSize -= pos; + } + + _rootPos = rootPos; + _rootLen = rootLen; + } + + d4_assert(mark[0] == 'J' || mark[1] == 'J'); + _bytesFlipped = (char) *(const short*) mark != 'J'; + + return last; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/akregator/src/mk4storage/metakit/src/win.h b/akregator/src/mk4storage/metakit/src/win.h new file mode 100644 index 00000000..d27eb244 --- /dev/null +++ b/akregator/src/mk4storage/metakit/src/win.h @@ -0,0 +1,33 @@ +// win.h -- +// $Id$ +// This is part of Metakit, the homepage is http://www.equi4.com/metakit/ + +/** @file + * Configuration header for Windows builds + */ + +#if defined (_MSDOS) +#define q4_DOS 1 +#endif + +#if defined (_WINDOWS) +#define q4_WIN 1 +#endif + +#if defined (_WIN32) +#define q4_WIN32 1 +#endif + +#if defined (_WIN32_WCE) // check for Win CE +#define q4_WINCE 1 +#define q4_WIN32 1 +#endif + +#if q4_WIN32 // WIN32 implies WIN +#undef q4_WIN +#define q4_WIN 1 +#endif + +#if q4_WIN // WIN implies not DOS, even for Win3 +#undef q4_DOS +#endif |