diff --git a/plugins/mk4storage/feedstoragemk4impl.cpp b/plugins/mk4storage/feedstoragemk4impl.cpp index 5a4fc6d1..2d821d36 100644 --- a/plugins/mk4storage/feedstoragemk4impl.cpp +++ b/plugins/mk4storage/feedstoragemk4impl.cpp @@ -1,549 +1,549 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "feedstoragemk4impl.h" #include "storagemk4impl.h" #include #include #include #include #include #include #include #include #include #include namespace { static uint calcHash(const QString &str) { if (str.isNull()) { // handle null string as "", prevents crash return calcHash(QLatin1String("")); } const char *s = str.toLatin1().constData(); uint hash = 5381; int c; while ((c = *s++)) { hash = ((hash << 5) + hash) + c; // hash*33 + c } return hash; } } namespace Akregator { namespace Backend { class FeedStorageMK4Impl::FeedStorageMK4ImplPrivate { public: FeedStorageMK4ImplPrivate() : modified(false) , pguid("guid") , ptitle("title") , pdescription("description") , pcontent("content") , plink("link") , pcommentsLink("commentsLink") , ptag("tag") , pEnclosureType("enclosureType") , pEnclosureUrl("enclosureUrl") , pcatTerm("catTerm") , pcatScheme("catScheme") , pcatName("catName") , pauthorName("authorName") , pauthorUri("authorUri") , pauthorEMail("authorEMail") , phash("hash") , pguidIsHash("guidIsHash") , pguidIsPermaLink("guidIsPermaLink") , pcomments("comments") , pstatus("status") , ppubDate("pubDate") , pHasEnclosure("hasEnclosure") , pEnclosureLength("enclosureLength") { } QString url; c4_Storage *storage; StorageMK4Impl *mainStorage; c4_View archiveView; bool autoCommit; bool modified; c4_StringProp pguid, ptitle, pdescription, pcontent, plink, pcommentsLink, ptag, pEnclosureType, pEnclosureUrl, pcatTerm, pcatScheme, pcatName, pauthorName, pauthorUri, pauthorEMail; c4_IntProp phash, pguidIsHash, pguidIsPermaLink, pcomments, pstatus, ppubDate, pHasEnclosure, pEnclosureLength; }; FeedStorageMK4Impl::FeedStorageMK4Impl(const QString &url, StorageMK4Impl *main) { d = new FeedStorageMK4ImplPrivate; d->autoCommit = main->autoCommit(); d->url = url; d->mainStorage = main; QString url2 = url; if (url.length() > 255) { url2 = url.left(200) + QString::number(::calcHash(url), 16); } qDebug() << url2; QString t = url2; QString t2 = url2; QString filePath = main->archivePath() + QLatin1Char('/') + t.replace(QLatin1Char('/'), QLatin1Char('_')).replace(QLatin1Char(':'), QLatin1Char('_')); d->storage = new c4_Storage(QString(filePath + QLatin1String(".mk4")).toLocal8Bit().constData(), true); d->archiveView = d->storage->GetAs( "articles[guid:S,title:S,hash:I,guidIsHash:I,guidIsPermaLink:I,description:S,link:S,comments:I,commentsLink:S,status:I,pubDate:I,tags[tag:S],hasEnclosure:I,enclosureUrl:S,enclosureType:S,enclosureLength:I,categories[catTerm:S,catScheme:S,catName:S],authorName:S,content:S,authorUri:S,authorEMail:S]"); c4_View hash = d->storage->GetAs("archiveHash[_H:I,_R:I]"); d->archiveView = d->archiveView.Hash(hash, 1); // hash on guid } FeedStorageMK4Impl::~FeedStorageMK4Impl() { delete d->storage; delete d; - d = 0; + d = nullptr; } void FeedStorageMK4Impl::markDirty() { if (!d->modified) { d->modified = true; // Tell this to mainStorage d->mainStorage->markDirty(); } } void FeedStorageMK4Impl::commit() { if (d->modified) { d->storage->Commit(); } d->modified = false; } void FeedStorageMK4Impl::rollback() { d->storage->Rollback(); } void FeedStorageMK4Impl::close() { if (d->autoCommit) { commit(); } } int FeedStorageMK4Impl::unread() const { return d->mainStorage->unreadFor(d->url); } void FeedStorageMK4Impl::setUnread(int unread) { d->mainStorage->setUnreadFor(d->url, unread); } int FeedStorageMK4Impl::totalCount() const { return d->mainStorage->totalCountFor(d->url); } void FeedStorageMK4Impl::setTotalCount(int total) { d->mainStorage->setTotalCountFor(d->url, total); } QDateTime FeedStorageMK4Impl::lastFetch() const { return d->mainStorage->lastFetchFor(d->url); } void FeedStorageMK4Impl::setLastFetch(const QDateTime &lastFetch) { d->mainStorage->setLastFetchFor(d->url, lastFetch); } QStringList FeedStorageMK4Impl::articles() const { QStringList list; int size = d->archiveView.GetSize(); list.reserve(size); for (int i = 0; i < size; ++i) { // fill with guids list += QString::fromLatin1(d->pguid(d->archiveView.GetAt(i))); } return list; } void FeedStorageMK4Impl::addEntry(const QString &guid) { c4_Row row; d->pguid(row) = guid.toLatin1().constData(); if (!contains(guid)) { d->archiveView.Add(row); markDirty(); setTotalCount(totalCount() + 1); } } bool FeedStorageMK4Impl::contains(const QString &guid) const { return findArticle(guid) != -1; } int FeedStorageMK4Impl::findArticle(const QString &guid) const { c4_Row findrow; d->pguid(findrow) = guid.toLatin1().constData(); return d->archiveView.Find(findrow); } void FeedStorageMK4Impl::deleteArticle(const QString &guid) { int findidx = findArticle(guid); if (findidx != -1) { setTotalCount(totalCount() - 1); d->archiveView.RemoveAt(findidx); markDirty(); } } bool FeedStorageMK4Impl::guidIsHash(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? d->pguidIsHash(d->archiveView.GetAt(findidx)) : false; } bool FeedStorageMK4Impl::guidIsPermaLink(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? d->pguidIsPermaLink(d->archiveView.GetAt(findidx)) : false; } uint FeedStorageMK4Impl::hash(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? d->phash(d->archiveView.GetAt(findidx)) : 0; } void FeedStorageMK4Impl::setDeleted(const QString &guid) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pdescription(row) = ""; d->pcontent(row) = ""; d->ptitle(row) = ""; d->plink(row) = ""; d->pauthorName(row) = ""; d->pauthorUri(row) = ""; d->pauthorEMail(row) = ""; d->pcommentsLink(row) = ""; d->archiveView.SetAt(findidx, row); markDirty(); } QString FeedStorageMK4Impl::link(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromLatin1(d->plink(d->archiveView.GetAt(findidx))) : QLatin1String(""); } QDateTime FeedStorageMK4Impl::pubDate(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QDateTime::fromTime_t(d->ppubDate(d->archiveView.GetAt(findidx))) : QDateTime(); } int FeedStorageMK4Impl::status(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? d->pstatus(d->archiveView.GetAt(findidx)) : 0; } void FeedStorageMK4Impl::setStatus(const QString &guid, int status) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pstatus(row) = status; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::article(const QString &guid, uint &hash, QString &title, int &status, QDateTime &pubDate) const { int idx = findArticle(guid); if (idx != -1) { auto view = d->archiveView.GetAt(idx); hash = d->phash(view); title = QString::fromUtf8(d->ptitle(view)); status = d->pstatus(view); pubDate = QDateTime::fromTime_t(d->ppubDate(view)); } } QString FeedStorageMK4Impl::title(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->ptitle(d->archiveView.GetAt(findidx))) : QLatin1String(""); } QString FeedStorageMK4Impl::description(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->pdescription(d->archiveView.GetAt(findidx))) : QLatin1String(""); } QString FeedStorageMK4Impl::content(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->pcontent(d->archiveView.GetAt(findidx))) : QLatin1String(""); } void FeedStorageMK4Impl::setPubDate(const QString &guid, const QDateTime &pubdate) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->ppubDate(row) = pubdate.toTime_t(); d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setGuidIsHash(const QString &guid, bool isHash) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pguidIsHash(row) = isHash; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setLink(const QString &guid, const QString &link) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->plink(row) = !link.isEmpty() ? link.toLatin1().constData() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setHash(const QString &guid, uint hash) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->phash(row) = hash; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setTitle(const QString &guid, const QString &title) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->ptitle(row) = !title.isEmpty() ? title.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setDescription(const QString &guid, const QString &description) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pdescription(row) = !description.isEmpty() ? description.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setContent(const QString &guid, const QString &content) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pcontent(row) = !content.isEmpty() ? content.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setAuthorName(const QString &guid, const QString &author) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pauthorName(row) = !author.isEmpty() ? author.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setAuthorUri(const QString &guid, const QString &author) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pauthorUri(row) = !author.isEmpty() ? author.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setAuthorEMail(const QString &guid, const QString &author) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pauthorEMail(row) = !author.isEmpty() ? author.toUtf8().data() : ""; d->archiveView.SetAt(findidx, row); markDirty(); } QString FeedStorageMK4Impl::authorName(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->pauthorName(d->archiveView.GetAt(findidx))) : QString(); } QString FeedStorageMK4Impl::authorUri(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->pauthorUri(d->archiveView.GetAt(findidx))) : QString(); } QString FeedStorageMK4Impl::authorEMail(const QString &guid) const { int findidx = findArticle(guid); return findidx != -1 ? QString::fromUtf8(d->pauthorEMail(d->archiveView.GetAt(findidx))) : QString(); } void FeedStorageMK4Impl::setGuidIsPermaLink(const QString &guid, bool isPermaLink) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pguidIsPermaLink(row) = isPermaLink; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::setEnclosure(const QString &guid, const QString &url, const QString &type, int length) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pHasEnclosure(row) = true; d->pEnclosureUrl(row) = !url.isEmpty() ? url.toUtf8().data() : ""; d->pEnclosureType(row) = !type.isEmpty() ? type.toUtf8().data() : ""; d->pEnclosureLength(row) = length; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::removeEnclosure(const QString &guid) { int findidx = findArticle(guid); if (findidx == -1) { return; } c4_Row row; row = d->archiveView.GetAt(findidx); d->pHasEnclosure(row) = false; d->pEnclosureUrl(row) = ""; d->pEnclosureType(row) = ""; d->pEnclosureLength(row) = -1; d->archiveView.SetAt(findidx, row); markDirty(); } void FeedStorageMK4Impl::enclosure(const QString &guid, bool &hasEnclosure, QString &url, QString &type, int &length) const { int findidx = findArticle(guid); if (findidx == -1) { hasEnclosure = false; url.clear(); type.clear(); length = -1; return; } c4_Row row = d->archiveView.GetAt(findidx); hasEnclosure = d->pHasEnclosure(row); url = QLatin1String(d->pEnclosureUrl(row)); type = QLatin1String(d->pEnclosureType(row)); length = d->pEnclosureLength(row); } } // namespace Backend } // namespace Akregator diff --git a/plugins/mk4storage/metakit/include/mk4.h b/plugins/mk4storage/metakit/include/mk4.h index e0361e1f..d158d156 100644 --- a/plugins/mk4storage/metakit/include/mk4.h +++ b/plugins/mk4storage/metakit/include/mk4.h @@ -1,1091 +1,1091 @@ // mk4.h -- // This is part of Metakit, see http://www.equi4.com/metakit/ /** @file * Main Metakit library include file */ #ifndef __MK4_H__ #define __MK4_H__ //--------------------------------------------------------------------------- // // TITLE // // The Metakit Library, by Jean-Claude Wippler, Equi4 Software, NL. // // DESCRIPTION // // Structured data storage with commit / rollback and on-demand loading. // // ACKNOWLEDGEMENTS // // To Liesbeth and Myra, for making this possible. // //--------------------------------------------------------------------------- // // NAMING CONVENTIONS PREFIX REMARKS // // Compile time options q4_ Always defined as 1 or 0, capitalized // Preprocessor defines d4_ Use with "#ifdef" or "#if defined()" // Classes c4_ Classes, listed at start of headers // Typedefs t4_ Type definitions, if outside classes // Global functions f4_ Internal, these are rarely defined // // Member functions Start in uppercase // Instance variables _ And start in lowercase // Static members _ And start in uppercase // // Local variable names Start in lowercase // Formal parameter names Start lowercase, end with underscore // //--------------------------------------------------------------------------- /// Current release = 100 * major + 10 * minor + maintenance #define d4_MetakitLibraryVersion 249 // 2.4.9.5 release, Nov 22, 2005 #define d4_MetaKitLibraryVersion d4_MetakitLibraryVersion // compat, yuck //--------------------------------------------------------------------------- // Declarations in this file class c4_View; // a view on underlying data class c4_Cursor; // an index into a view class c4_RowRef; // a reference to a row class c4_Row; // one row in a view class c4_Bytes; // used to pass around generic data class c4_Storage; // manages view persistence class c4_CustomViewer; // used for customizable views class c4_Stream; // abstract stream class class c4_Strategy; // system and file interface class c4_Property; // for access inside rows class c4_IntProp; class c4_LongProp; class c4_FloatProp; class c4_DoubleProp; class c4_StringProp; class c4_BytesProp; class c4_ViewProp; // Everything below is part of the implementation, not for public use class c4_Sequence; // a collection of rows class c4_Reference; // refers to the actual data values class c4_IntRef; class c4_LongRef; class c4_FloatRef; class c4_DoubleRef; class c4_BytesRef; class c4_StringRef; class c4_ViewRef; class c4_Dependencies; // not defined here class c4_Handler; // not defined here class c4_Notifier; // not defined here class c4_Persist; // not defined here //--------------------------------------------------------------------------- // determine whether we need to include "mk4dll.h" to link as DLL #if defined (MKDLL_EXPORTS) && !defined (q4_KITDLL) #define q4_KITDLL 1 #endif // omit floats and doubles in small model 16-bit Intel builds #if defined (_DOS) && defined (_M_I86SM) && !defined (q4_TINY) #define q4_TINY 1 #endif // and here's the other end of the scale... #if !defined (_WIN32) && !defined (q4_LONG64) #if (defined (_PA_RISC2_0) && defined(__hpux)) || defined (__powerpc64__) || defined(__sparcv9) \ || defined (__x86_64__) || defined (__s390x__) || defined (__alpha) || defined (__mips64) \ || (defined (__ia64) && (!defined (__HP_aCC) || defined(__LP64__))) #define q4_LONG64 1 #endif #endif // default to inlining for maximum performance #if !defined (q4_INLINE) #define q4_INLINE 1 #endif //--------------------------------------------------------------------------- // Borland C++ and C++ Builder #if defined (__BORLANDC__) // by default, if runtime is linked as a DLL, then so is Metakit #if defined (_RTLDLL) && !defined (q4_KITDLL) #define q4_KITDLL 1 #endif // Borland 5.0 supports the bool datatype #if __BORLANDC__ >= 0x500 #define q4_BOOL 1 #endif #endif // __BORLANDC__ // IRIX supports the bool datatype // define before gcc to cover both the gcc and MipsPRO compiler #if defined (sgi) #define q4_BOOL 1 #undef bool #undef true #undef false #endif #if defined (__SUNPRO_CC) #define q4_BOOL 1 #ifdef bool #undef bool #undef true #undef false #endif #endif // GNU gcc/egcs #if defined (__GNUC__) #if !defined(q4_BOOL) #define q4_BOOL 1 #endif #if !defined(HAVE_LONG_LONG) #define HAVE_LONG_LONG 1 #endif #endif // HP aCC #if defined (__HP_aCC) #if !defined(HAVE_LONG_LONG) #define HAVE_LONG_LONG 1 #endif #endif // Metrowerks CodeWarrior #if defined (__MWERKS__) #if __option(bool) #define q4_BOOL 1 // bool datatype is optionally supported // undef, these conflict with c4_Storage::c4_Storage overloading #undef bool #undef true #undef false #endif #endif // Microsoft Visual C++ #if defined (_MSC_VER) // MSVC 5.0 supports the bool datatype, MSVC 4.x has no namespaces #if _MSC_VER >= 1100 #define q4_BOOL 1 #define LONG_LONG __int64 #else #define q4_NO_NS 1 #endif // a kludge to avoid having to use ugly DLL exprt defs in this header #pragma warning(disable: 4273) // inconsistent dll linkage #endif // _MSC_VER //--------------------------------------------------------------------------- // Other definitions needed by the public Metakit library header files #if !defined(q4_BOOL) && !defined(q4_STD) // define a bool datatype #define false 0 #define true 1 #define bool int #endif #if defined(q4_KITDLL) // add declaration specifiers #include "mk4dll.h" #endif #if defined(q4_INLINE) // enable inline expansion #define d4_inline inline #else #define d4_inline #endif typedef unsigned char t4_byte; // create typedefs for t4_byte, etc. #if defined(q4_LONG64) typedef int t4_i32; // if longs are 64b, then int must be 32b #else typedef long t4_i32; // if longs aren't 64b, then they are 32b #endif #if defined(q4_LONG64) // choose a way to represent 64b integers typedef long t4_i64; #elif defined (LONG_LONG) typedef LONG_LONG t4_i64; #elif defined(HAVE_LONG_LONG) typedef long long t4_i64; #else struct t4_i64 { long l1; long l2; }; bool operator==(const t4_i64 a_, const t4_i64 b_); bool operator<(const t4_i64 a_, const t4_i64 b_); #endif //--------------------------------------------------------------------------- class c4_View { protected: c4_Sequence *_seq; public: /* Construction / destruction / assignment */ - c4_View(c4_Sequence * = 0); + c4_View(c4_Sequence * = nullptr); c4_View(c4_CustomViewer *); c4_View(c4_Stream *); c4_View(const c4_Property &property_); c4_View(const c4_View &); ~c4_View(); c4_View &operator=(const c4_View &); c4_Persist *Persist() const; // added 16-11-2000 to simplify c4_Storage /* Getting / setting the number of rows */ int GetSize() const; void SetSize(int, int = -1); void RemoveAll(); /*: Getting / setting individual elements */ c4_RowRef GetAt(int) const; c4_RowRef operator[](int) const; void SetAt(int, const c4_RowRef &); c4_RowRef ElementAt(int); bool GetItem(int, int, c4_Bytes &) const; void SetItem(int, int, const c4_Bytes &) const; /* These can increase the number of rows */ void SetAtGrow(int, const c4_RowRef &); int Add(const c4_RowRef &); /* Insertion / deletion of rows */ void InsertAt(int, const c4_RowRef &, int = 1); void RemoveAt(int, int = 1); void InsertAt(int, const c4_View &); bool IsCompatibleWith(const c4_View &) const; void RelocateRows(int, int, c4_View &, int); /* Dealing with the properties of this view */ int NumProperties() const; const c4_Property &NthProperty(int) const; int FindProperty(int); int FindPropIndexByName(const char *) const; c4_View Duplicate() const; c4_View Clone() const; int AddProperty(const c4_Property &); c4_View operator,(const c4_Property &) const; const char *Description() const; /* Derived views */ c4_View Sort() const; c4_View SortOn(const c4_View &) const; c4_View SortOnReverse(const c4_View &, const c4_View &) const; c4_View Select(const c4_RowRef &) const; c4_View SelectRange(const c4_RowRef &, const c4_RowRef &) const; c4_View Project(const c4_View &) const; c4_View ProjectWithout(const c4_View &) const; int GetIndexOf(const c4_RowRef &) const; int RestrictSearch(const c4_RowRef &, int &, int &); /* Custom views */ c4_View Slice(int, int = -1, int = 1) const; c4_View Product(const c4_View &) const; c4_View RemapWith(const c4_View &) const; c4_View Pair(const c4_View &) const; c4_View Concat(const c4_View &) const; c4_View Rename(const c4_Property &, const c4_Property &) const; c4_View GroupBy(const c4_View &, const c4_ViewProp &) const; c4_View Counts(const c4_View &, const c4_IntProp &) const; c4_View Unique() const; c4_View Union(const c4_View &) const; c4_View Intersect(const c4_View &) const; c4_View Different(const c4_View &) const; c4_View Minus(const c4_View &) const; c4_View JoinProp(const c4_ViewProp &, bool = false) const; c4_View Join(const c4_View &, const c4_View &, bool = false) const; c4_View ReadOnly() const; c4_View Hash(const c4_View &, int = 1) const; c4_View Blocked() const; c4_View Ordered(int = 1) const; c4_View Indexed(const c4_View &, const c4_View &, bool = false) const; /* Searching */ int Find(const c4_RowRef &, int = 0) const; int Search(const c4_RowRef &) const; - int Locate(const c4_RowRef &, int * = 0) const; + int Locate(const c4_RowRef &, int * = nullptr) const; /* Comparing view contents */ int Compare(const c4_View &) const; friend bool operator==(const c4_View &, const c4_View &); friend bool operator!=(const c4_View &, const c4_View &); friend bool operator<(const c4_View &, const c4_View &); friend bool operator>(const c4_View &, const c4_View &); friend bool operator<=(const c4_View &, const c4_View &); friend bool operator>=(const c4_View &, const c4_View &); protected: void _IncSeqRef(); void _DecSeqRef(); /// View references are allowed to peek inside view objects friend class c4_ViewRef; // DROPPED: Structure() const; // DROPPED: Description(const c4_View& view_); }; //--------------------------------------------------------------------------- #if defined(os_aix) && defined(compiler_ibmcxx) && (compiler_ibmcxx > 500) bool operator==(const c4_RowRef &a_, const c4_RowRef &b_); bool operator!=(const c4_RowRef &a_, const c4_RowRef &b_); bool operator<=(const c4_RowRef &a_, const c4_RowRef &b_); bool operator>=(const c4_RowRef &a_, const c4_RowRef &b_); bool operator>(const c4_RowRef &a_, const c4_RowRef &b_); bool operator<(const c4_RowRef &a_, const c4_RowRef &b_); #endif class c4_Cursor { public: /// Pointer to the sequence c4_Sequence *_seq; /// Current index into the sequence int _index; /* Construction / destruction / dereferencing */ /// Construct a new cursor c4_Cursor(c4_Sequence &, int); /// Dereference this cursor to "almost" a row c4_RowRef operator*() const; /// This is the same as *(cursor + offset) c4_RowRef operator[](int) const; /* Stepping the iterator forwards / backwards */ /// Pre-increment the cursor c4_Cursor &operator++(); /// Post-increment the cursor c4_Cursor operator++(int); /// Pre-decrement the cursor c4_Cursor &operator--(); /// Post-decrement the cursor c4_Cursor operator--(int); /// Advance by a given offset c4_Cursor &operator+=(int); /// Back up by a given offset c4_Cursor &operator-=(int); /// Subtract a specified offset c4_Cursor operator-(int) const; /// Return the distance between two cursors int operator-(c4_Cursor) const; /// Add specified offset friend c4_Cursor operator+(c4_Cursor, int); /// Add specified offset to cursor friend c4_Cursor operator+(int, c4_Cursor); /* Comparing row positions */ /// Return true if both cursors are equal friend bool operator==(c4_Cursor, c4_Cursor); /// Return true if both cursors are not equal friend bool operator!=(c4_Cursor, c4_Cursor); /// True if first cursor is less than second cursor friend bool operator<(c4_Cursor, c4_Cursor); /// True if first cursor is greater than second cursor friend bool operator>(c4_Cursor, c4_Cursor); /// True if first cursor is less or equal to second cursor friend bool operator<=(c4_Cursor, c4_Cursor); /// True if first cursor is greater or equal to second cursor friend bool operator>=(c4_Cursor, c4_Cursor); /* Comparing row contents */ /// Return true if the contents of both rows are equal friend bool operator==(const c4_RowRef &, const c4_RowRef &); /// Return true if the contents of both rows are not equal friend bool operator!=(const c4_RowRef &, const c4_RowRef &); /// True if first row is less than second row friend bool operator<(const c4_RowRef &, const c4_RowRef &); /// True if first row is greater than second row friend bool operator>(const c4_RowRef &, const c4_RowRef &); /// True if first row is less or equal to second row friend bool operator<=(const c4_RowRef &, const c4_RowRef &); /// True if first row is greater or equal to second row friend bool operator>=(const c4_RowRef &, const c4_RowRef &); }; //--------------------------------------------------------------------------- class c4_RowRef { /// A row reference is a cursor in disguise c4_Cursor _cursor; public: /* General operations */ /// Assign the value of another row to this one c4_RowRef operator=(const c4_RowRef &); /// Return the cursor associated to this row c4_Cursor operator&() const; /// Return the underlying container view c4_View Container() const; protected: /// Constructor, not for general use c4_RowRef(c4_Cursor); friend class c4_Cursor; friend class c4_Row; }; //--------------------------------------------------------------------------- /// An entry in a collection with copy semantics. // // Rows can exist by themselves and as contents of views. Row assignment // implies that a copy of the contents of the originating row is made. // // A row is implemented as an unattached view with exactly one element. class c4_Row : public c4_RowRef { public: /// Construct a row with no properties c4_Row(); /// Construct a row from another one c4_Row(const c4_Row &); /// Construct a row copy from a row reference c4_Row(const c4_RowRef &); /// Destructor ~c4_Row(); /// Assign a copy of another row to this one c4_Row &operator=(const c4_Row &); /// Copy another row to this one c4_Row &operator=(const c4_RowRef &); /// Add all properties and values into this row void ConcatRow(const c4_RowRef &); /// Return a new row which is the concatenation of two others friend c4_Row operator+(const c4_RowRef &, const c4_RowRef &); private: static c4_Cursor Allocate(); static void Release(c4_Cursor); }; //--------------------------------------------------------------------------- class c4_Bytes { union { t4_byte _buffer [16]; double _aligner; // on a Sparc, the int below wasn't enough... }; t4_byte *_contents; int _size; bool _copy; public: c4_Bytes(); c4_Bytes(const void *, int); c4_Bytes(const void *, int, bool); c4_Bytes(const c4_Bytes &); ~c4_Bytes(); c4_Bytes &operator=(const c4_Bytes &); void Swap(c4_Bytes &); int Size() const; const t4_byte *Contents() const; t4_byte *SetBuffer(int); t4_byte *SetBufferClear(int); friend bool operator==(const c4_Bytes &, const c4_Bytes &); friend bool operator!=(const c4_Bytes &, const c4_Bytes &); private: void _MakeCopy(); void _LoseCopy(); }; //--------------------------------------------------------------------------- class c4_Storage : public c4_View { public: /// Construct streaming-only storage object c4_Storage(); /// Construct a storage using the specified strategy handler c4_Storage(c4_Strategy &, bool = false, int = 1); /// Construct a storage object, keeping the current structure c4_Storage(const char *, int); /// Reconstruct a storage object from a suitable view c4_Storage(const c4_View &); /// Destructor, usually closes file, but does not commit by default ~c4_Storage(); void SetStructure(const char *); bool AutoCommit(bool = true); c4_Strategy &Strategy() const; - const char *Description(const char * = 0); + const char *Description(const char * = nullptr); bool SetAside(c4_Storage &); c4_Storage *GetAside() const; bool Commit(bool = false); bool Rollback(bool = false); c4_ViewRef View(const char *); c4_View GetAs(const char *); bool LoadFrom(c4_Stream &); void SaveTo(c4_Stream &); - t4_i32 FreeSpace(t4_i32 *bytes_ = 0); + t4_i32 FreeSpace(t4_i32 *bytes_ = nullptr); //DROPPED: c4_Storage (const char* filename_, const char* description_); //DROPPED: c4_View Store(const char* name_, const c4_View& view_); //DROPPED: c4_HandlerSeq& RootTable() const; //DROPPED: c4_RowRef xContents() const; private: void Initialize(c4_Strategy &, bool, int); }; //--------------------------------------------------------------------------- class c4_Property { short _id; char _type; public: /// Construct a new property with the give type and id c4_Property(char, int); /// Construct a new property with the give type and name c4_Property(char, const char *); ~c4_Property(); c4_Property(const c4_Property &); void operator=(const c4_Property &); const char *Name() const; char Type() const; int GetId() const; c4_Reference operator()(const c4_RowRef &) const; void Refs(int) const; c4_View operator,(const c4_Property &) const; static void CleanupInternalData(); }; /// Integer properties. class c4_IntProp : public c4_Property { public: /// Construct a new property c4_IntProp(const char *); /// Destructor ~c4_IntProp(); /// Get or set an integer property in a row c4_IntRef operator()(const c4_RowRef &) const; /// Get an integer property in a row t4_i32 Get(const c4_RowRef &) const; /// Set an integer property in a row void Set(const c4_RowRef &, t4_i32) const; /// Creates a row with one integer, shorthand for AsRow. c4_Row operator[](t4_i32) const; /// Creates a row with one integer. c4_Row AsRow(t4_i32) const; }; #if !defined(q4_TINY) /// Long int properties. class c4_LongProp : public c4_Property { public: /// Construct a new property c4_LongProp(const char *); /// Destructor ~c4_LongProp(); /// Get or set a long int property in a row c4_LongRef operator()(const c4_RowRef &) const; /// Get a long int property in a row t4_i64 Get(const c4_RowRef &) const; /// Set a long int property in a row void Set(const c4_RowRef &, t4_i64) const; /// Creates a row with one long int, shorthand for AsRow. c4_Row operator[](t4_i64) const; /// Creates a row with one long int. c4_Row AsRow(t4_i64) const; }; /// Floating point properties. class c4_FloatProp : public c4_Property { public: /// Construct a new property c4_FloatProp(const char *); /// Destructor ~c4_FloatProp(); /// Get or set a floating point property in a row c4_FloatRef operator()(const c4_RowRef &) const; /// Get a floating point property in a row double Get(const c4_RowRef &) const; /// Set a floating point property in a row void Set(const c4_RowRef &, double) const; /// Create a row with one floating point value, shorthand for AsRow c4_Row operator[](double) const; /// Create a row with one floating point value c4_Row AsRow(double) const; }; /// Double precision properties. class c4_DoubleProp : public c4_Property { public: /// Construct a new property. c4_DoubleProp(const char *); /// Destructor ~c4_DoubleProp(); /// Get or set a double precision property in a row c4_DoubleRef operator()(const c4_RowRef &) const; /// Get a double precision property in a row double Get(const c4_RowRef &) const; /// Set a double precision property in a row void Set(const c4_RowRef &, double) const; /// Create a row with one double precision value, shorthand for AsRow c4_Row operator[](double) const; /// Create a row with one double precision value c4_Row AsRow(double) const; }; #endif // !q4_TINY /// String properties. class c4_StringProp : public c4_Property { public: /// Construct a new property c4_StringProp(const char *); /// Destructor ~c4_StringProp(); /// Get or set a string property in a row c4_StringRef operator()(const c4_RowRef &) const; /// Get a string property in a row const char *Get(const c4_RowRef &) const; /// Set a string property in a row void Set(const c4_RowRef &, const char *) const; /// Create a row with one string, shorthand for AsRow c4_Row operator[](const char *) const; /// Create a row with one string c4_Row AsRow(const char *) const; }; /// Binary properties. class c4_BytesProp : public c4_Property { public: /// Construct a new property c4_BytesProp(const char *); /// Destructor ~c4_BytesProp(); /// Get or set a bytes property in a row c4_BytesRef operator()(const c4_RowRef &) const; /// Get a bytes property in a row c4_Bytes Get(const c4_RowRef &) const; /// Set a bytes property in a row void Set(const c4_RowRef &, const c4_Bytes &) const; /// Create a row with one bytes object, shorthand for AsRow c4_Row operator[](const c4_Bytes &) const; /// Create a row with one bytes object c4_Row AsRow(const c4_Bytes &) const; }; /// View properties. class c4_ViewProp : public c4_Property { public: /// Construct a new property c4_ViewProp(const char *); /// Destructor ~c4_ViewProp(); /// Get or set a view property in a row c4_ViewRef operator()(const c4_RowRef &) const; /// Get a view property in a row c4_View Get(const c4_RowRef &) const; /// Set a view property in a row void Set(const c4_RowRef &, const c4_View &) const; /// Create a row with one view, shorthand for AsRow c4_Row operator[](const c4_View &) const; /// Create a row with one view c4_Row AsRow(const c4_View &) const; }; //--------------------------------------------------------------------------- class c4_CustomViewer { protected: /// Constructor, must be overridden in derived class c4_CustomViewer(); public: /// Destructor virtual ~c4_CustomViewer(); /// Return the structure of this view (initialization, called once) virtual c4_View GetTemplate() = 0; /// Return the number of rows in this view virtual int GetSize() = 0; int Lookup(const c4_RowRef &, int &); virtual int Lookup(c4_Cursor, int &); /// Fetch one data item, return it as a generic data value virtual bool GetItem(int, int, c4_Bytes &) = 0; virtual bool SetItem(int, int, const c4_Bytes &); bool InsertRows(int, const c4_RowRef &, int = 1); virtual bool InsertRows(int, c4_Cursor, int = 1); virtual bool RemoveRows(int, int = 1); }; //--------------------------------------------------------------------------- /// A stream is a virtual helper class to serialize in binary form. class c4_Stream { public: virtual ~c4_Stream(); /// Fetch some bytes sequentially virtual int Read(void *, int) = 0; /// Store some bytes sequentially virtual bool Write(const void *, int) = 0; }; //--------------------------------------------------------------------------- /// A strategy encapsulates code dealing with the I/O system interface. class c4_Strategy { public: c4_Strategy(); virtual ~c4_Strategy(); virtual bool IsValid() const; virtual int DataRead(t4_i32, void *, int); virtual void DataWrite(t4_i32, const void *, int); virtual void DataCommit(t4_i32); virtual void ResetFileMapping(); virtual t4_i32 FileSize(); virtual t4_i32 FreshGeneration(); void SetBase(t4_i32); t4_i32 EndOfData(t4_i32 = -1); /// True if the storage format is not native (default is false) bool _bytesFlipped; /// Error code of last failed I/O operation, zero if I/O was ok int _failure; /// First byte in file mapping, zero if not active const t4_byte *_mapStart; /// Number of bytes filled with active data t4_i32 _dataSize; /// All file positions are relative to this offset t4_i32 _baseOffset; /// The root position of the shallow tree walks t4_i32 _rootPos; /// The size of the root column t4_i32 _rootLen; }; //--------------------------------------------------------------------------- /// A sequence is an abstract base class for views on ranges of records. // // Sequences represent arrays of rows (or indexed collections / tables). // Insertion and removal of entries is allowed, but could take linear time. // A reference count is maintained to decide when the object should go away. class c4_Sequence { /// Reference count int _refCount; /// Pointer to dependency list, or null if nothing depends on this c4_Dependencies *_dependencies; protected: /// Optimization: cached property index int _propertyLimit; /// Optimization: property map for faster access short *_propertyMap; // see c4_HandlerSeq::Reset() /// allocated on first use by c4_Sequence::Buffer() c4_Bytes *_tempBuf; public: /* General */ /// Abstract constructor c4_Sequence(); virtual int Compare(int, c4_Cursor) const; virtual bool RestrictSearch(c4_Cursor, int &, int &); void SetAt(int, c4_Cursor); virtual int RemapIndex(int, const c4_Sequence *) const; /* Reference counting */ void IncRef(); void DecRef(); int NumRefs() const; /* Adding / removing rows */ /// Return the current number of rows virtual int NumRows() const = 0; void Resize(int, int = -1); virtual void InsertAt(int, c4_Cursor, int = 1); virtual void RemoveAt(int, int = 1); virtual void Move(int, int); /* Properties */ int NthPropId(int) const; int PropIndex(int); int PropIndex(const c4_Property &); /// Return the number of data handlers in this sequence virtual int NumHandlers() const = 0; /// Return a reference to the N-th handler in this sequence virtual c4_Handler &NthHandler(int) const = 0; /// Return the context of the N-th handler in this sequence virtual const c4_Sequence *HandlerContext(int) const = 0; /// Add the specified data handler to this sequence virtual int AddHandler(c4_Handler *) = 0; /// Create a handler of the appropriate type virtual c4_Handler *CreateHandler(const c4_Property &) = 0; virtual const char *Description(); /* Element access */ /// Return width of specified data item virtual int ItemSize(int, int); /// Retrieve one data item from this sequence virtual bool Get(int, int, c4_Bytes &); /// Store a data item into this sequence virtual void Set(int, const c4_Property &, const c4_Bytes &); /* Dependency notification */ void Attach(c4_Sequence *); void Detach(c4_Sequence *); /// Return a pointer to the dependencies, or null c4_Dependencies *GetDependencies() const; virtual c4_Notifier *PreChange(c4_Notifier &); virtual void PostChange(c4_Notifier &); const char *UseTempBuffer(const char *); protected: virtual ~c4_Sequence(); void ClearCache(); public: //! for c4_Table::Sequence setup virtual void SetNumRows(int) = 0; virtual c4_Persist *Persist() const; c4_Bytes &Buffer(); private: c4_Sequence(const c4_Sequence &); // not implemented void operator=(const c4_Sequence &); // not implemented }; //--------------------------------------------------------------------------- /// A reference is used to get or set typed data, using derived classes. // // Objects of this class are only intended to be used as a temporary handle // while getting and setting properties in a row. They are normally only // constructed as result of function overload operators: "property (row)". class c4_Reference { protected: /// The cursor which points to the data c4_Cursor _cursor; /// The property associated to this reference const c4_Property &_property; public: /// Constructor c4_Reference(const c4_RowRef &, const c4_Property &); /// Assignment of one data item c4_Reference &operator=(const c4_Reference &); /// Return width of the referenced data item int GetSize() const; /// Retrieve the value of the referenced data item bool GetData(c4_Bytes &) const; /// Store a value into the referenced data item void SetData(const c4_Bytes &) const; /// Return true if the contents of both references is equal friend bool operator==(const c4_Reference &, const c4_Reference &); /// Return true if the contents of both references is not equal friend bool operator!=(const c4_Reference &, const c4_Reference &); private: void operator&() const; // not implemented }; //--------------------------------------------------------------------------- /// Used to get or set integer values. class c4_IntRef : public c4_Reference { public: /// Constructor c4_IntRef(const c4_Reference &); /// Get the value as integer operator t4_i32() const; /// Set the value to the specified integer c4_IntRef &operator=(t4_i32); }; #if !defined(q4_TINY) /// Used to get or set long int values. class c4_LongRef : public c4_Reference { public: /// Constructor c4_LongRef(const c4_Reference &); /// Get the value as long int operator t4_i64() const; /// Set the value to the specified long int c4_LongRef &operator=(t4_i64); }; /// Used to get or set floating point values. class c4_FloatRef : public c4_Reference { public: /// Constructor c4_FloatRef(const c4_Reference &); /// Get the value as floating point operator double() const; /// Set the value to the specified floating point c4_FloatRef &operator=(double); }; /// Used to get or set double precision values. class c4_DoubleRef : public c4_Reference { public: /// Constructor c4_DoubleRef(const c4_Reference &); /// Get the value as floating point operator double() const; /// Set the value to the specified floating point c4_DoubleRef &operator=(double); }; #endif // !q4_TINY /// Used to get or set binary object values. class c4_BytesRef : public c4_Reference { public: /// Constructor c4_BytesRef(const c4_Reference &); /// Get the value as binary object operator c4_Bytes() const; /// Set the value to the specified binary object c4_BytesRef &operator=(const c4_Bytes &); /// Fetch data from the memo field, up to end if length is zero c4_Bytes Access(t4_i32, int = 0, bool = false) const; /// Store data, resize by diff_ bytes, return true if successful bool Modify(const c4_Bytes &, t4_i32, int = 0) const; }; /// Used to get or set string values. class c4_StringRef : public c4_Reference { public: /// Constructor c4_StringRef(const c4_Reference &); /// Get the value as string operator const char *() const; /// Set the value to the specified string c4_StringRef &operator=(const char *); }; /// Used to get or set view values. class c4_ViewRef : public c4_Reference { public: /// Constructor c4_ViewRef(const c4_Reference &); /// Get the value as view operator c4_View() const; /// Set the value to the specified view c4_ViewRef &operator=(const c4_View &); }; //--------------------------------------------------------------------------- // Debug logging option, can generate log of changes for one/all properties #if defined(q4_LOGPROPMODS) FILE *f4_LogPropMods(FILE *fp_, int propId_); #else #define f4_LogPropMods(a, b) 0 #endif //--------------------------------------------------------------------------- #if defined(q4_INLINE) #include "mk4.inl" #endif //--------------------------------------------------------------------------- #endif // __MK4_H__ diff --git a/plugins/mk4storage/metakit/include/mk4.inl b/plugins/mk4storage/metakit/include/mk4.inl index 0206bfc0..071ecfd4 100644 --- a/plugins/mk4storage/metakit/include/mk4.inl +++ b/plugins/mk4storage/metakit/include/mk4.inl @@ -1,873 +1,873 @@ // mk4.inl -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit/ /** @file * Public definitions which are usually inlined */ ///////////////////////////////////////////////////////////////////////////// // Reordered inlines so they are always defined before their first use d4_inline c4_Cursor c4_RowRef::operator& () const { return _cursor; } /** Return a unique id for this property * * A property object in fact merely represents an entry in a globally * maintained symbol table. Each property is assigned a unique id, * which remains valid as long as some reference to that property * exists. In general, property id's remain unique as long as the * application runs. Do not store id's on file, since they are * not guaranteed to remain the same across program invocations. * All properties with the same name are given the same id. */ d4_inline int c4_Property::GetId() const { return _id; } ////////////////////////////////////////////////////////////////////////////////// #if !defined(q4_LONG64) && !defined (LONG_LONG) && !defined(HAVE_LONG_LONG) d4_inline bool operator== (const t4_i64 a_, const t4_i64 b_) { return a_.l1 == b_.l1 && a_.l2 == b_.l2; } d4_inline bool operator< (const t4_i64 a_, const t4_i64 b_) { return a_.l2 < b_.l2 || a_.l2 == b_.l2 && a_.l2 < b_.l2; } #endif ////////////////////////////////////////////////////////////////////////////////// // c4_View /// Returns the number of entries in this view. d4_inline int c4_View::GetSize() const { return _seq->NumRows(); } /** Change the size of this view * Since views act like dynamic arrays, you can quickly * change their size. Increasing the size will append rows * with zero/empty values, while decreasing it will delete * the last rows. The growBy_ parameter is currently unused. */ d4_inline void c4_View::SetSize(int newSize_, int growBy_) { _seq->Resize(newSize_, growBy_); } /// Removes all entries (sets size to zero). d4_inline void c4_View::RemoveAll() { SetSize(0); } /// Return a pointer to the persistence handler, or zero d4_inline c4_Persist* c4_View::Persist() const { return _seq->Persist(); } /** * Change the value of the specified entry. If the new value has * other properties, these will be added to the underlying view. * * @param index_ the zero-based row index * @param newElem_ the row to copy to this view */ d4_inline void c4_View::SetAt(int index_, const c4_RowRef& newElem_) { _seq->SetAt(index_, &newElem_); } /** * Insert a copy of the contents of another view. This is identical to * inserting the specified number of default entries and then setting * each of them to the new element value passed as argument. */ d4_inline void c4_View::InsertAt( int index_, ///< zero-based row index const c4_RowRef& newElem_, ///< the value to insert int count_ ///< number of copies to insert, must be > 0 ) { _seq->InsertAt(index_, &newElem_, count_); } /** * Remove entries starting at the given index. Entries which have * other view references may cause these views to be deleted if their * reference counts drop to zero because of this removal. * * @param index_ the zero-based row index * @param count_ the number of entries to remove */ d4_inline void c4_View::RemoveAt(int index_, int count_) { _seq->RemoveAt(index_, count_); } /** Return the number of properties present in this view. * @return A non-negative integer */ d4_inline int c4_View::NumProperties() const { return _seq->NumHandlers(); } /** Find the index of a property, given its id * @param propId_ Unique id associated to a specific propoerty * @return The index of the property, or -1 of it was not found */ d4_inline int c4_View::FindProperty(int propId_) { return _seq->PropIndex(propId_); } /// Return a decription if there is a fixed structure, else zero d4_inline const char* c4_View::Description() const { return _seq->Description(); } /// Increase the reference count of the associated sequence d4_inline void c4_View::_IncSeqRef() { _seq->IncRef(); } /// Decrease the reference count of the associated sequence d4_inline void c4_View::_DecSeqRef() { _seq->DecRef(); } /// Destructor, decrements reference count d4_inline c4_View::~c4_View () { _DecSeqRef(); } /// Return true if the contents of both views are equal d4_inline bool operator== (const c4_View& a_, const c4_View& b_) { return a_.GetSize() == b_.GetSize() && a_.Compare(b_) == 0; } /// Return true if the contents of both views are not equal d4_inline bool operator!= (const c4_View& a_, const c4_View& b_) { return !(a_ == b_); } /// True if first view is less than second view d4_inline bool operator< (const c4_View& a_, const c4_View& b_) { return a_.Compare(b_) < 0; } /// True if first view is greater than second view d4_inline bool operator> (const c4_View& a_, const c4_View& b_) { return b_ < a_; } /// True if first view is less or equal to second view d4_inline bool operator<= (const c4_View& a_, const c4_View& b_) { return !(b_ < a_); } /// True if first view is greater or equal to second view d4_inline bool operator>= (const c4_View& a_, const c4_View& b_) { return !(a_ < b_); } ///////////////////////////////////////////////////////////////////////////// // c4_Cursor /** Constructs a new cursor. * * Cursor cannot be created without an underlying view, but you could * define a global "nullView" object and then initialize the cursor with * "&nullView[0]". This works because cursors need not point to a valid row. */ d4_inline c4_Cursor::c4_Cursor (c4_Sequence& seq_, int index_) : _seq (&seq_), _index (index_) { } /// Pre-increments the cursor. d4_inline c4_Cursor& c4_Cursor::operator++ () { ++_index; return *this; } /// Post-increments the cursor. d4_inline c4_Cursor c4_Cursor::operator++ (int) { return c4_Cursor (*_seq, _index++); } /// Pre-decrements the cursor. d4_inline c4_Cursor& c4_Cursor::operator-- () { --_index; return *this; } /// Post-decrements the cursor. d4_inline c4_Cursor c4_Cursor::operator-- (int) { return c4_Cursor (*_seq, _index--); } /// Advances by a given offset. d4_inline c4_Cursor& c4_Cursor::operator+= (int offset_) { _index += offset_; return *this; } /// Backs up by a given offset. d4_inline c4_Cursor& c4_Cursor::operator-= (int offset_) { _index -= offset_; return *this; } /// Subtracts a specified offset. d4_inline c4_Cursor c4_Cursor::operator- (int offset_) const { return c4_Cursor (*_seq, _index - offset_); } /// Returns the distance between two cursors. d4_inline int c4_Cursor::operator- (c4_Cursor cursor_) const { return _index - cursor_._index; } /// Add a specified offset. d4_inline c4_Cursor operator+ (c4_Cursor cursor_, int offset_) { return c4_Cursor (*cursor_._seq, cursor_._index + offset_); } /// Adds specified offset to cursor. d4_inline c4_Cursor operator+ (int offset_, c4_Cursor cursor_) { return cursor_ + offset_; } d4_inline bool operator== (c4_Cursor a_, c4_Cursor b_) { return a_._seq == b_._seq && a_._index == b_._index; } d4_inline bool operator!= (c4_Cursor a_, c4_Cursor b_) { return !(a_ == b_); } d4_inline bool operator< (c4_Cursor a_, c4_Cursor b_) { return a_._seq < b_._seq || ( a_._seq == b_._seq && a_._index < b_._index ); } d4_inline bool operator> (c4_Cursor a_, c4_Cursor b_) { return b_ < a_; } d4_inline bool operator<= (c4_Cursor a_, c4_Cursor b_) { return !(b_ < a_); } d4_inline bool operator>= (c4_Cursor a_, c4_Cursor b_) { return !(a_ < b_); } ///////////////////////////////////////////////////////////////////////////// // c4_RowRef d4_inline c4_RowRef::c4_RowRef (c4_Cursor cursor_) : _cursor (cursor_) { } d4_inline c4_RowRef c4_RowRef::operator= (const c4_RowRef& rowRef_) { if (_cursor != rowRef_._cursor) _cursor._seq->SetAt(_cursor._index, &rowRef_); return *this; } d4_inline c4_View c4_RowRef::Container() const { return _cursor._seq; } d4_inline bool operator== (const c4_RowRef& a_, const c4_RowRef& b_) { return (&a_)._seq->Compare((&a_)._index, &b_) == 0; } d4_inline bool operator!= (const c4_RowRef& a_, const c4_RowRef& b_) { return !(a_ == b_); } d4_inline bool operator< (const c4_RowRef& a_, const c4_RowRef& b_) { // 25-5-1998: don't exchange a and b, this comparison is -not- symmetric return (&a_)._seq->Compare((&a_)._index, &b_) < 0; } d4_inline bool operator> (const c4_RowRef& a_, const c4_RowRef& b_) { // 25-5-1998: don't exchange a and b, this comparison is -not- symmetric return (&a_)._seq->Compare((&a_)._index, &b_) > 0; } d4_inline bool operator<= (const c4_RowRef& a_, const c4_RowRef& b_) { return !(a_ > b_); } d4_inline bool operator>= (const c4_RowRef& a_, const c4_RowRef& b_) { return !(a_ < b_); } ///////////////////////////////////////////////////////////////////////////// // c4_Bytes /// Construct an empty binary object d4_inline c4_Bytes::c4_Bytes () : _size (0), _copy (false) { - _contents = 0; // moved out of intializers for DEC CXX 5.7 + _contents = nullptr; // moved out of intializers for DEC CXX 5.7 } /// Construct an object with contents, no copy d4_inline c4_Bytes::c4_Bytes (const void* buf_, int len_) : _size (len_), _copy (false) { _contents = (t4_byte*) buf_; // moved out of intializers for DEC CXX 5.7 } /// Returns a pointer to the contents. d4_inline const t4_byte* c4_Bytes::Contents() const { return _contents; } /// Returns the number of bytes of its contents. d4_inline int c4_Bytes::Size() const { return _size; } d4_inline void c4_Bytes::_LoseCopy() { if (_copy) delete [] (char*) _contents; } /// Returns true if the contents of both objects is not equal. d4_inline bool operator!= (const c4_Bytes& a_, const c4_Bytes& b_) { return !(a_ == b_); } /// Destructor, if a copy was made, it will be released here. d4_inline c4_Bytes::~c4_Bytes () { _LoseCopy(); } ///////////////////////////////////////////////////////////////////////////// // c4_Reference d4_inline c4_Reference::c4_Reference (const c4_RowRef& rowRef_, const c4_Property& prop_) : _cursor (&rowRef_), _property (prop_) { } d4_inline int c4_Reference::GetSize() const { return _cursor._seq->ItemSize(_cursor._index, _property.GetId()); } d4_inline bool c4_Reference::GetData(c4_Bytes& buf_) const { return _cursor._seq->Get(_cursor._index, _property.GetId(), buf_); } d4_inline void c4_Reference::SetData(const c4_Bytes& buf_) const { _cursor._seq->Set(_cursor._index, _property, buf_); } d4_inline bool operator!= (const c4_Reference& a_, const c4_Reference& b_) { return !(a_ == b_); } ///////////////////////////////////////////////////////////////////////////// // c4_IntRef d4_inline c4_IntRef::c4_IntRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// #if !defined(q4_TINY) ///////////////////////////////////////////////////////////////////////////// // c4_LongRef d4_inline c4_LongRef::c4_LongRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// // c4_FloatRef d4_inline c4_FloatRef::c4_FloatRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// // c4_DoubleRef d4_inline c4_DoubleRef::c4_DoubleRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// #endif // !q4_TINY ///////////////////////////////////////////////////////////////////////////// // c4_BytesRef d4_inline c4_BytesRef::c4_BytesRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// // c4_StringRef d4_inline c4_StringRef::c4_StringRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// // c4_ViewRef d4_inline c4_ViewRef::c4_ViewRef (const c4_Reference& value_) : c4_Reference (value_) { } ///////////////////////////////////////////////////////////////////////////// // c4_Property d4_inline c4_Property::c4_Property (char type_, int id_) : _id ((short) id_), _type (type_) { } /// Get or set this untyped property in a row d4_inline c4_Reference c4_Property::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } /// Return a view like the first, with a property appended to it d4_inline c4_View c4_Property::operator, (const c4_Property& prop_) const { return c4_View (*this), prop_; } /// Return the type of this property d4_inline char c4_Property::Type() const { return _type; } ///////////////////////////////////////////////////////////////////////////// // c4_IntProp d4_inline c4_IntProp::c4_IntProp (const char* name_) : c4_Property ('I', name_) { } d4_inline c4_IntProp::~c4_IntProp () { } d4_inline c4_IntRef c4_IntProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline t4_i32 c4_IntProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_IntProp::Set(const c4_RowRef& rowRef_, t4_i32 value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_IntProp::AsRow(t4_i32 value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_IntProp::operator[] (t4_i32 value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// #if !defined(q4_TINY) ///////////////////////////////////////////////////////////////////////////// // c4_LongProp d4_inline c4_LongProp::c4_LongProp (const char* name_) : c4_Property ('L', name_) { } d4_inline c4_LongProp::~c4_LongProp () { } d4_inline c4_LongRef c4_LongProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline t4_i64 c4_LongProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_LongProp::Set(const c4_RowRef& rowRef_, t4_i64 value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_LongProp::AsRow(t4_i64 value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_LongProp::operator[] (t4_i64 value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// // c4_FloatProp d4_inline c4_FloatProp::c4_FloatProp (const char* name_) : c4_Property ('F', name_) { } d4_inline c4_FloatProp::~c4_FloatProp () { } d4_inline c4_FloatRef c4_FloatProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline double c4_FloatProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_FloatProp::Set(const c4_RowRef& rowRef_, double value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_FloatProp::AsRow(double value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_FloatProp::operator[] (double value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// // c4_DoubleProp d4_inline c4_DoubleProp::c4_DoubleProp (const char* name_) : c4_Property ('D', name_) { } d4_inline c4_DoubleProp::~c4_DoubleProp () { } d4_inline c4_DoubleRef c4_DoubleProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline double c4_DoubleProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_DoubleProp::Set(const c4_RowRef& rowRef_, double value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_DoubleProp::AsRow(double value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_DoubleProp::operator[] (double value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// #endif // !q4_TINY ///////////////////////////////////////////////////////////////////////////// // c4_BytesProp d4_inline c4_BytesProp::c4_BytesProp (const char* name_) : c4_Property ('B', name_) { } d4_inline c4_BytesProp::~c4_BytesProp () { } d4_inline c4_BytesRef c4_BytesProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline c4_Bytes c4_BytesProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_BytesProp::Set(const c4_RowRef& rowRef_, const c4_Bytes& value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_BytesProp::AsRow(const c4_Bytes& value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_BytesProp::operator[] (const c4_Bytes& value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// // c4_StringProp d4_inline c4_StringProp::c4_StringProp (const char* name_) : c4_Property ('S', name_) { } d4_inline c4_StringProp::~c4_StringProp () { } d4_inline c4_StringRef c4_StringProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline const char* c4_StringProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_StringProp::Set(const c4_RowRef& rowRef_, const char* value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_StringProp::AsRow(const char* value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_StringProp::operator[] (const char* value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// // c4_ViewProp d4_inline c4_ViewProp::c4_ViewProp (const char* name_) : c4_Property ('V', name_) { } d4_inline c4_ViewProp::~c4_ViewProp () { } d4_inline c4_ViewRef c4_ViewProp::operator() (const c4_RowRef& rowRef_) const { return c4_Reference (rowRef_, *this); } d4_inline c4_View c4_ViewProp::Get(const c4_RowRef& rowRef_) const { return operator() (rowRef_); } d4_inline void c4_ViewProp::Set(const c4_RowRef& rowRef_, const c4_View& value_) const { operator() (rowRef_) = value_; } d4_inline c4_Row c4_ViewProp::AsRow(const c4_View& value_) const { c4_Row row; operator() (row) = value_; return row; } d4_inline c4_Row c4_ViewProp::operator[] (const c4_View& value_) const { return AsRow(value_); } ///////////////////////////////////////////////////////////////////////////// // c4_Strategy /// True if we can do I/O with this object d4_inline bool c4_Strategy::IsValid() const { return false; } ///////////////////////////////////////////////////////////////////////////// // c4_CustomViewer d4_inline c4_CustomViewer::c4_CustomViewer() { } d4_inline int c4_CustomViewer::Lookup(const c4_RowRef& r_, int& n_) { return Lookup(&r_, n_); // c4_Cursor } d4_inline bool c4_CustomViewer::InsertRows(int p_, const c4_RowRef& r_, int n_) { return InsertRows(p_, &r_, n_); // c4_Cursor } ///////////////////////////////////////////////////////////////////////////// // c4_Sequence d4_inline c4_Dependencies* c4_Sequence::GetDependencies() const { return _dependencies; } ///////////////////////////////////////////////////////////////////////////// // Reordered inlines so they are always used after their definition /// Dereferences this cursor to "almost" a row. d4_inline c4_RowRef c4_Cursor::operator* () const { return *(c4_Cursor*) this; // cast avoids a const problem with BCPP 4.52 } /// This is the same as *(cursor + offset). d4_inline c4_RowRef c4_Cursor::operator[] (int offset_) const { return *(*this + offset_); } /// Returns a reference to specified entry, for use as RHS or LHS d4_inline c4_RowRef c4_View::GetAt(int index_) const { return * c4_Cursor (*_seq, index_); } /** Element access, shorthand for GetAt * @return A reference to the specified row in the view. * This reference can be used on either side of the assignment operator. */ d4_inline c4_RowRef c4_View::operator[] ( int index_ ///< zero-based row index ) const { return GetAt(index_); } /** Element access, shorthand for GetAt * @return A reference to the specified row in the view. * This reference can be used on either side of the assignment operator. */ d4_inline c4_RowRef c4_View::ElementAt( int index_ ///< zero-based row index ) { return GetAt(index_); } ///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/mk4storage/metakit/include/mk4io.h b/plugins/mk4storage/metakit/include/mk4io.h index b21f53ae..af100be2 100644 --- a/plugins/mk4storage/metakit/include/mk4io.h +++ b/plugins/mk4storage/metakit/include/mk4io.h @@ -1,65 +1,65 @@ // mk4io.h -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit/ /** @file * Declaration of the file stream and strategy classes. */ #ifndef __MK4IO_H__ #define __MK4IO_H__ #include ///////////////////////////////////////////////////////////////////////////// /// A file stream can be used to serialize using the stdio library. class c4_FileStream : public c4_Stream { public: c4_FileStream(FILE *stream_, bool owned_ = false); virtual ~c4_FileStream(); int Read(void *buffer_, int length_) override; bool Write(const void *buffer_, int length_) override; FILE *_stream; bool _owned; }; ///////////////////////////////////////////////////////////////////////////// /// A file strategy encapsulates code dealing with all file I/O. class c4_FileStrategy : public c4_Strategy { public: /// Construct a new strategy object - c4_FileStrategy(FILE *file_ = 0); + c4_FileStrategy(FILE *file_ = nullptr); virtual ~c4_FileStrategy(); /// True if we can do I/O with this object bool IsValid() const override; /// Open a data file by name virtual bool DataOpen(const char *fileName_, int mode_); /// Read a number of bytes int DataRead(t4_i32 pos_, void *buffer_, int length_) override; /// Write a number of bytes, return true if successful void DataWrite(t4_i32 pos_, const void *buffer_, int length_) override; /// Flush and truncate file void DataCommit(t4_i32 newSize_) override; /// Support for memory-mapped files void ResetFileMapping() override; /// Report total size of the datafile t4_i32 FileSize() override; /// Return a good value to use as fresh generation counter t4_i32 FreshGeneration() override; protected: /// Pointer to file object FILE *_file; /// Pointer to same file object, if it must be deleted at end FILE *_cleanup; }; ///////////////////////////////////////////////////////////////////////////// #endif // __MK4IO_H__ diff --git a/plugins/mk4storage/metakit/include/mk4str.inl b/plugins/mk4storage/metakit/include/mk4str.inl index 72f7399a..a53f0b76 100644 --- a/plugins/mk4storage/metakit/include/mk4str.inl +++ b/plugins/mk4storage/metakit/include/mk4str.inl @@ -1,298 +1,298 @@ // mk4str.inl -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit/ /** @file * Members of the string package which are usually inlined */ ///////////////////////////////////////////////////////////////////////////// // c4_String #if defined(q4_MFC) // Microsoft Foundation Classes #elif defined(q4_STD) // STL and standard strings /// Construct empty string d4_inline c4_String::c4_String () { } d4_inline c4_String::c4_String (char ch_, int nDup_) : string (nDup_, ch_) { } d4_inline c4_String::c4_String (const char* str_) : string (str_) { } d4_inline c4_String::c4_String (const void* ptr_, int len_) : string ((const char*) ptr_, len_) { } d4_inline c4_String::c4_String (const d4_std::string& s_) : string (s_) { } d4_inline c4_String::c4_String (const c4_String& s_) : string (s_) { } d4_inline c4_String::~c4_String () { } d4_inline const c4_String& c4_String::operator= (const c4_String& s_) { *(string*) this = s_; return *this; } d4_inline c4_String::operator const char* () const { return c_str(); } d4_inline char c4_String::operator[] (int i) const { return at(i); } d4_inline c4_String operator+ (const c4_String& a_, const c4_String& b_) { return (d4_std::string) a_ + (d4_std::string) b_; } d4_inline c4_String operator+ (const c4_String& a_, const char* b_) { return (d4_std::string) a_ + (d4_std::string) b_; } d4_inline c4_String operator+ (const char* a_, const c4_String& b_) { return (d4_std::string) a_ + (d4_std::string) b_; } d4_inline const c4_String& c4_String::operator+= (const c4_String& s_) { *(string*) this += s_; return *this; } d4_inline const c4_String& c4_String::operator+= (const char* s_) { *(string*) this += s_; return *this; } d4_inline int c4_String::GetLength() const { return length(); } d4_inline bool c4_String::IsEmpty() const { return empty(); } d4_inline void c4_String::Empty() { erase(); } d4_inline c4_String c4_String::Left(int nCount_) const { if (nCount_ > length()) nCount_ = length(); return substr(0, nCount_); } d4_inline c4_String c4_String::Right(int nCount_) const { if (nCount_ > length()) nCount_ = length(); return substr(length() - nCount_, nCount_); } d4_inline int c4_String::Compare(const char* str_) const { return compare(str_); } d4_inline bool c4_String::operator< (const c4_String& str_) const { return compare(str_) < 0; } d4_inline int c4_String::Find(char ch_) const { return find(ch_); } d4_inline int c4_String::ReverseFind(char ch_) const { return rfind(ch_); } d4_inline int c4_String::FindOneOf(const char* set_) const { return find_first_of(set_); } d4_inline int c4_String::Find(const char* sub_) const { return find(sub_); } d4_inline c4_String c4_String::SpanIncluding(const char* set_) const { return substr(0, find_first_not_of(set_)); } d4_inline c4_String c4_String::SpanExcluding(const char* set_) const { return substr(0, find_first_of(set_)); } d4_inline bool operator== (const c4_String& a_, const c4_String& b_) { return (d4_std::string) a_ == (d4_std::string) b_; } d4_inline bool operator!= (const c4_String& a_, const c4_String& b_) { return (d4_std::string) a_ != (d4_std::string) b_; } d4_inline bool operator== (const c4_String& a_, const char* b_) { return (d4_std::string) a_ == (d4_std::string) b_; } d4_inline bool operator== (const char* a_, const c4_String& b_) { return (d4_std::string) a_ == (d4_std::string) b_; } d4_inline bool operator!= (const c4_String& a_, const char* b_) { return (d4_std::string) a_ != (d4_std::string) b_; } d4_inline bool operator!= (const char* a_, const c4_String& b_) { return (d4_std::string) a_ != (d4_std::string) b_; } #else // Universal replacement classes /// Construct empty string d4_inline c4_String::c4_String () { - Init(0, 0); + Init(nullptr, 0); } /// Construct string from Pascal-style data d4_inline c4_String::c4_String (const unsigned char* ptr) { Init(ptr + 1, ptr ? *ptr : 0); } /// Construct string from a specified area in memory d4_inline c4_String::c4_String (const void* ptr, int len) { Init(ptr, len); } d4_inline const c4_String& c4_String::operator+= (const c4_String& s) { return *this = *this + s; } d4_inline const c4_String& c4_String::operator+= (const char* s) { return *this += (c4_String) s; } d4_inline const char* c4_String::Data() const { return (const char*) (_value + 2); } d4_inline c4_String::operator const char* () const { return Data(); } d4_inline c4_String::operator const unsigned char* () const { return (unsigned char*) Data() - 1; } d4_inline char c4_String::operator[] (int i) const { return Data()[i]; } d4_inline int c4_String::GetLength() const { return _value[1] != 255 ? _value[1] : FullLength(); } d4_inline bool c4_String::IsEmpty() const { return GetLength() == 0; } d4_inline void c4_String::Empty() { *this = ""; } d4_inline bool c4_String::operator< (const c4_String& a) const { return Compare(a) < 0; } d4_inline c4_String operator+ (const char* a, const c4_String& b) { return (c4_String) a + b; } d4_inline c4_String operator+ (const c4_String& a, const char* b) { return a + (c4_String) b; } d4_inline bool operator== (const char* a, const c4_String& b) { return b.Compare(a) == 0; } d4_inline bool operator== (const c4_String& a, const char* b) { return a.Compare(b) == 0; } d4_inline bool operator!= (const c4_String& a, const c4_String& b) { return !(a == b); } d4_inline bool operator!= (const char* a, const c4_String& b) { return b.Compare(a) != 0; } d4_inline bool operator!= (const c4_String& a, const char* b) { return a.Compare(b) != 0; } #endif // q4_UNIV ///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/mk4storage/metakit/src/column.cpp b/plugins/mk4storage/metakit/src/column.cpp index 83d6e6f4..af5ff79d 100644 --- a/plugins/mk4storage/metakit/src/column.cpp +++ b/plugins/mk4storage/metakit/src/column.cpp @@ -1,1646 +1,1646 @@ // column.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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 (!defined(HAVE_MEMMOVE) && !defined(HAVE_BCOPY)) || (!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 defined(q4_CHECK) && 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; + return _position > 1 && _persist != nullptr && Strategy()._mapStart != nullptr; } bool c4_Column::UsesMap(const t4_byte *ptr_) const { // the most common falsifying case is checked first - return _persist != 0 + return _persist != nullptr && ptr_ >= Strategy()._mapStart && Strategy()._dataSize != 0 && ptr_ < Strategy()._mapStart + Strategy()._dataSize; } bool c4_Column::RequiresMap() const { - if (_persist != 0 && Strategy()._mapStart != 0) { + if (_persist != nullptr && Strategy()._mapStart != nullptr) { 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); + _segments.SetAt(i, nullptr); } 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); + _segments.InsertAt(i1, nullptr, 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); + _segments.SetAt(i, nullptr); _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); + _segments.SetAt(i, nullptr); } 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); + _segments.SetAt(i, nullptr); _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))) { + if (l2bp1 > 4 && (_mustFlip || (Persist() != nullptr && 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/plugins/mk4storage/metakit/src/column.inl b/plugins/mk4storage/metakit/src/column.inl index ab1a741b..6c294abf 100644 --- a/plugins/mk4storage/metakit/src/column.inl +++ b/plugins/mk4storage/metakit/src/column.inl @@ -1,88 +1,88 @@ // column.inl -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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) + : _column (col_), _limit (limit_), _pos (offset_), _len (0), _ptr (nullptr) { } 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/plugins/mk4storage/metakit/src/custom.cpp b/plugins/mk4storage/metakit/src/custom.cpp index 258b900f..1e9942c9 100644 --- a/plugins/mk4storage/metakit/src/custom.cpp +++ b/plugins/mk4storage/metakit/src/custom.cpp @@ -1,1075 +1,1075 @@ // custom.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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(); int ItemSize(int index_) override; const void *Get(int index_, int &length_) override; void Set(int index_, const c4_Bytes &buf_) override; void Insert(int index_, const c4_Bytes &buf_, int count_) override; void Remove(int index_, int count_) override; }; ///////////////////////////////////////////////////////////////////////////// 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) +c4_CustomSeq::c4_CustomSeq(c4_CustomViewer *viewer_) : c4_HandlerSeq(nullptr) , _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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; //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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; }; 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(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; }; 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/plugins/mk4storage/metakit/src/derived.cpp b/plugins/mk4storage/metakit/src/derived.cpp index 832845d2..e0a264fb 100644 --- a/plugins/mk4storage/metakit/src/derived.cpp +++ b/plugins/mk4storage/metakit/src/derived.cpp @@ -1,1007 +1,1007 @@ // derived.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @file * Derived views are virtual views which track changes */ #include "header.h" #include "handler.h" #include "store.h" #include "derived.h" #include // 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) + bool Match(int index_, c4_Sequence &seq_, const int * = nullptr, const int * = nullptr) const; bool MatchOne(int prop_, const c4_Bytes &data_) const; public: c4_FilterSeq(c4_Sequence &seq_, c4_Cursor low_, c4_Cursor high_); int RemapIndex(int, const c4_Sequence *) const override; int NumRows() const override; int Compare(int, c4_Cursor) const override; bool Get(int, int, c4_Bytes &) override; void InsertAt(int, c4_Cursor, int = 1) override; void RemoveAt(int, int = 1) override; void Set(int, const c4_Property &, const c4_Bytes &) override; virtual void SetSize(int); c4_Notifier *PreChange(c4_Notifier &nf_) override; void PostChange(c4_Notifier &nf_) override; }; ///////////////////////////////////////////////////////////////////////////// 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; + return nullptr; } 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(); c4_Notifier *PreChange(c4_Notifier &nf_) override; void PostChange(c4_Notifier &nf_) override; 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); int Compare(int, c4_Cursor) const override; 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) + , _info(nullptr) , _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; + _info[j]._handler = nullptr; // everything is ready, go sort the row index vector MergeSort((T *)&_rowMap.ElementAt(0), NumRows()); delete [] _info; - _info = 0; + _info = nullptr; 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; + return nullptr; } #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; + return nullptr; } 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(); int NumHandlers() const override; c4_Handler &NthHandler(int) const override; const c4_Sequence *HandlerContext(int) const override; int AddHandler(c4_Handler *) override; bool Get(int, int, c4_Bytes &) override; void Set(int, const c4_Property &, const c4_Bytes &) override; }; ///////////////////////////////////////////////////////////////////////////// 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/plugins/mk4storage/metakit/src/derived.h b/plugins/mk4storage/metakit/src/derived.h index 3b3119bc..6a96e12b 100644 --- a/plugins/mk4storage/metakit/src/derived.h +++ b/plugins/mk4storage/metakit/src/derived.h @@ -1,23 +1,23 @@ // derived.h -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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); +extern c4_Sequence *f4_CreateSort(c4_Sequence &, c4_Sequence * = nullptr); +extern c4_Sequence *f4_CreateProject(c4_Sequence &, c4_Sequence &, bool, c4_Sequence * = nullptr); ///////////////////////////////////////////////////////////////////////////// #endif diff --git a/plugins/mk4storage/metakit/src/field.cpp b/plugins/mk4storage/metakit/src/field.cpp index b3d8c22e..7992e5cf 100644 --- a/plugins/mk4storage/metakit/src/field.cpp +++ b/plugins/mk4storage/metakit/src/field.cpp @@ -1,124 +1,124 @@ // field.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @file * Implementation of the field structure tree */ #include "header.h" #include "field.h" #include // 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) { + if (p != nullptr && 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; + sf = nullptr; break; } } - if (sf != 0) { + if (sf != nullptr) { _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/plugins/mk4storage/metakit/src/field.h b/plugins/mk4storage/metakit/src/field.h index 0747ffd1..bf354bd4 100644 --- a/plugins/mk4storage/metakit/src/field.h +++ b/plugins/mk4storage/metakit/src/field.h @@ -1,63 +1,63 @@ // field.h -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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); + c4_Field(const char * &, c4_Field * = nullptr); //: 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 original '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/plugins/mk4storage/metakit/src/fileio.cpp b/plugins/mk4storage/metakit/src/fileio.cpp index 3540e1d6..65fd27e1 100644 --- a/plugins/mk4storage/metakit/src/fileio.cpp +++ b/plugins/mk4storage/metakit/src/fileio.cpp @@ -1,471 +1,471 @@ // fileio.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @file * Implementation of c4_FileStrategy and c4_FileStream */ #include "header.h" #include "mk4io.h" #if defined(q4_WIN32) && q4_WIN32 #if q4_MSVC && !q4_STRICT #pragma warning(disable: 4201) // nonstandard extension used : ... #endif #define WIN32_LEAN_AND_MEAN #include #include #include #include #endif #if q4_UNIX && defined(HAVE_MMAP) && HAVE_MMAP #include #include #endif #if q4_UNIX #include #include #endif #if defined(q4_WINCE) && 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 #include #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 defined(q4_CHECK) && q4_CHECK #include 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) + , _cleanup(nullptr) { InitializeIO(); ResetFileMapping(); } c4_FileStrategy::~c4_FileStrategy() { - _file = 0; + _file = nullptr; ResetFileMapping(); if (_cleanup) { fclose(_cleanup); } d4_assert(_mapStart == 0); FinalizeIO(); } bool c4_FileStrategy::IsValid() const { - return _file != 0; + return _file != nullptr; } 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 defined(q4_WIN32) && 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); if (h) { _mapStart = (t4_byte *)::MapViewOfFile(h, FILE_MAP_READ, 0, 0, len); if (_mapStart != 0) { _mapStart += _baseOffset; _dataSize = len - _baseOffset; } d4_dbgdef(BOOL f = ) ::CloseHandle(h); d4_assert(f); } } } #elif defined(HAVE_MMAP) && HAVE_MMAP && !NO_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 } #if defined(q4_WIN32) && q4_WIN32 && !q4_BORC && !q4_WINCE static DWORD GetPlatformId() { static OSVERSIONINFO os; if (os.dwPlatformId == 0) { os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&os); } return os.dwPlatformId; } #endif bool c4_FileStrategy::DataOpen(const char *fname_, int mode_) { d4_assert(!_file); #if defined(q4_WIN32) && q4_WIN32 && !q4_BORC && !q4_WINCE int flags = _O_BINARY | _O_NOINHERIT | (mode_ > 0 ? _O_RDWR : _O_RDONLY); int fd = -1; if (GetPlatformId() != VER_PLATFORM_WIN32_NT) { fd = _open(fname_, flags); } if (fd == -1) { WCHAR wName[MAX_PATH]; MultiByteToWideChar(CP_UTF8, 0, fname_, -1, wName, MAX_PATH); fd = _wopen(wName, 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) { + if (_file != nullptr) { fcntl(fileno(_file), F_SETFD, FD_CLOEXEC); } #endif //q4_UNIX #endif //q4_WIN32 && !q4_BORC && !q4_WINCE - if (_file != 0) { + if (_file != nullptr) { ResetFileMapping(); return true; } if (mode_ > 0) { #if defined(q4_WIN32) && q4_WIN32 && !q4_BORC && !q4_WINCE WCHAR wName[MAX_PATH]; MultiByteToWideChar(CP_UTF8, 0, fname_, -1, wName, MAX_PATH); fd = _wopen(wName, 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) { + if (_file != nullptr) { 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 (defined(q4_WIN32) && q4_WIN32) || (defined(__hpux) && __hpux) || (defined(__MACH__) && __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/plugins/mk4storage/metakit/src/format.cpp b/plugins/mk4storage/metakit/src/format.cpp index cb57a5af..ac9f6ab5 100644 --- a/plugins/mk4storage/metakit/src/format.cpp +++ b/plugins/mk4storage/metakit/src/format.cpp @@ -1,1415 +1,1415 @@ // format.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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(); bool IsPersistent() const override; 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; + return _owner.Persist() != nullptr; } ///////////////////////////////////////////////////////////////////////////// class c4_FormatX : public c4_FormatHandler { public: c4_FormatX(const c4_Property &prop_, c4_HandlerSeq &seq_, int width_ = sizeof(t4_i32)); void Define(int, const t4_byte **) override; void OldDefine(char type_, c4_Persist &) override; void FlipBytes() override; int ItemSize(int index_) override; const void *Get(int index_, int &length_) override; void Set(int index_, const c4_Bytes &buf_) override; void Insert(int index_, const c4_Bytes &buf_, int count_) override; void Remove(int index_, int count_) override; void Commit(c4_SaveContext &ar_) override; void Unmapped() override; 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) { + if (ptr_ != nullptr) { _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 !defined(q4_TINY) || !q4_TINY ///////////////////////////////////////////////////////////////////////////// class c4_FormatL : public c4_FormatX { public: c4_FormatL(const c4_Property &prop_, c4_HandlerSeq &seq_); void Define(int, const t4_byte **) override; 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) { + if (ptr_ == nullptr && 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_); void Define(int, const t4_byte **) override; 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) { + if (ptr_ == nullptr && 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(); void Define(int, const t4_byte **) override; void OldDefine(char type_, c4_Persist &) override; void Commit(c4_SaveContext &ar_) override; int ItemSize(int index_) override; const void *Get(int index_, int &length_) override; void Set(int index_, const c4_Bytes &buf_) override; void Insert(int index_, const c4_Bytes &buf_, int count_) override; void Remove(int index_, int count_) override; c4_Column *GetNthMemoCol(int index_, bool alloc_) override; void Unmapped() override; 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) { + if (col_ != nullptr) { 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) { + if (cp != nullptr) { cp->ReleaseAllSegments(); } } } void c4_FormatB::Define(int, const t4_byte **ptr_) { d4_assert(_memos.GetSize() == 0); - if (ptr_ != 0) { + if (ptr_ != nullptr) { _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 !defined(q4_OLD_IS_ALWAYS_V2) || !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 !defined(q4_OLD_IS_PRE_V2) || !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) { + if (!ignoreMemos_ && _memos.GetAt(index_) != nullptr) { 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_); + _memos.InsertAt(index_, nullptr, 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) { + if (col != nullptr) { 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 + _memos.SetAt(r, nullptr); // 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_); int ItemSize(int index_) override; const void *Get(int index_, int &length_) override; void Set(int index_, const c4_Bytes &buf_) override; void Insert(int index_, const c4_Bytes &buf_, int count_) override; 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(); void Define(int rows_, const t4_byte **ptr_) override; void OldDefine(char type_, c4_Persist &) override; void Commit(c4_SaveContext &ar_) override; void FlipBytes() override; int ItemSize(int index_) override; const void *Get(int index_, int &length_) override; void Set(int index_, const c4_Bytes &buf_) override; void Insert(int index_, const c4_Bytes &buf_, int count_) override; void Remove(int index_, int count_) override; void Unmapped() override; bool HasSubview(int index_) override; 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) { + if (hs == nullptr) { 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) { + if (ptr_ != nullptr) { _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) { + if (hs == nullptr) { 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(); + return hs == nullptr ? 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) { + if (curr != nullptr) { d4_assert(&curr->Parent() == &Owner()); curr->DetachFromParent(); curr->DetachFromStorage(true); curr->DecRef(); - curr = 0; + curr = nullptr; } 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_); + _subSeqs.InsertAt(index_, nullptr, 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; + return _subSeqs.ElementAt(index_) != nullptr; } void c4_FormatV::ForgetSubview(int index_) { c4_HandlerSeq * &seq = (c4_HandlerSeq * &)_subSeqs.ElementAt(index_); - if (seq != 0) { + if (seq != nullptr) { d4_assert(&seq->Parent() == &Owner()); seq->DetachFromParent(); seq->DetachFromStorage(true); seq->UnmappedAll(); seq->DecRef(); - seq = 0; + seq = nullptr; } } void c4_FormatV::Commit(c4_SaveContext &ar_) { if (!_inited) { SetupAllSubviews(); } int rows = _subSeqs.GetSize(); d4_assert(rows > 0); - c4_Column temp(0); + c4_Column temp(nullptr); 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 !defined(q4_TINY) || !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 !defined(q4_TINY) || !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 !defined(q4_TINY) || !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/plugins/mk4storage/metakit/src/handler.cpp b/plugins/mk4storage/metakit/src/handler.cpp index 184a108f..8eaa4514 100644 --- a/plugins/mk4storage/metakit/src/handler.cpp +++ b/plugins/mk4storage/metakit/src/handler.cpp @@ -1,524 +1,524 @@ // handler.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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) + (nullptr) + , _parent(nullptr) , _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) { + if (rootLevel && pers != nullptr) { 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) { + if (_field != nullptr) { const char *desc = "[]"; c4_Field f(desc); d4_assert(!*desc); Restructure(f, false); - _field = 0; + _field = nullptr; } - _parent = 0; + _parent = nullptr; } void c4_HandlerSeq::DetachFromStorage(bool full_) { - if (_persist != 0) { + if (_persist != nullptr) { 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; + _persist = nullptr; } } } 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; + if (_field == nullptr) { + return nullptr; } 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); + NthHandler(i).Define(NumRows(), nullptr); } 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_; + _field = remove_ ? nullptr : &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) { + } else if (seq._field != nullptr) { 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; + return _field != nullptr ? _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) { + if (ptr_ != nullptr) { 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; + return nullptr; } 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/plugins/mk4storage/metakit/src/handler.inl b/plugins/mk4storage/metakit/src/handler.inl index 0a4cbdd8..cdc105f0 100644 --- a/plugins/mk4storage/metakit/src/handler.inl +++ b/plugins/mk4storage/metakit/src/handler.inl @@ -1,89 +1,89 @@ // handler.inl -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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; + return nullptr; } 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/plugins/mk4storage/metakit/src/persist.cpp b/plugins/mk4storage/metakit/src/persist.cpp index 3a5ebd26..df7b94c0 100644 --- a/plugins/mk4storage/metakit/src/persist.cpp +++ b/plugins/mk4storage/metakit/src/persist.cpp @@ -1,1316 +1,1316 @@ // persist.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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_); - t4_i32 FreeCounts(t4_i32 *bytes_ = 0); + t4_i32 FreeCounts(t4_i32 *bytes_ = nullptr); 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 + . // * 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 defined(q4_CHECK) && q4_CHECK #include 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 t4_i32 c4_Allocator::FreeCounts(t4_i32 *bytes_) { - if (bytes_ != 0) { + if (bytes_ != nullptr) { t4_i32 total = 0; for (int i = 2; i < GetSize() - 2; i += 2) { total += GetAt(i + 1) - GetAt(i); } *bytes_ = total; } return GetSize() / 2 - 2; } ///////////////////////////////////////////////////////////////////////////// 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) + (nullptr) , _differ(differ_) , _space(space_) - , _cleanup(0) - , _nextSpace(0) + , _cleanup(nullptr) + , _nextSpace(nullptr) , _preflight (true) , _fullScan(fullScan_) , _mode(mode_) , _nextPosIndex(0) , _bufPtr(_buffer) , _curr(_buffer) , _limit(_buffer) { - if (_space == 0) { + if (_space == nullptr) { _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) { + if (ap != nullptr) { ap->Dump(str_); } } void c4_SaveContext::FlushBuffer() { int n = _curr - _bufPtr; - if (_walk != 0 && n > 0) { + if (_walk != nullptr && 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) { + if (_walk == nullptr) { 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 (_differ == nullptr) { 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); + SetWalkBuffer(nullptr); CommitColumn(walk); c4_Bytes tempWalk; walk.FetchBytes(0, walk.ColSize(), tempWalk, true); t4_i32 limit = _nextSpace->AllocationLimit(); d4_assert(limit >= 8 || _differ != 0); if (limit < 0) { // 2006-01-12 #2: catch file size exceeding 2 Gb _strategy._failure = -1; // unusual non-zero value flags this case return; } 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) { + if (!_fullScan && limit <= 11 && _differ == nullptr) { _space->Initialize(); _nextSpace->Initialize(); changed = false; } if (!changed) { return; } //AllocDump("b1", false); //AllocDump("b2", true); - if (_differ != 0) { + if (_differ != nullptr) { 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 { /* 18-11-2005 write new end marker and flush it before *anything* else! */ if (!_fullScan && end0 < limit) { c4_FileMark mark1(limit, 0); _strategy.DataWrite(limit, &mark1, sizeof mark1); _strategy.DataCommit(0); if (_strategy._failure != 0) { return; } } 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 defined(q4_WIN32) && 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) { + if (_strategy._mapStart != nullptr) { 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) { + if (spacePtr_ != nullptr && _space != _nextSpace) { d4_assert(*spacePtr_ == _space); delete *spacePtr_; *spacePtr_ = _nextSpace; - _nextSpace = 0; + _nextSpace = nullptr; } } 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) + (nullptr) , _strategy(strategy_) - , _root(0) - , _differ(0) - , _fCommit(0) + , _root(nullptr) + , _differ(nullptr) + , _fCommit(nullptr) , _mode(mode_) , _owned(owned_) - , _oldBuf(0) - , _oldCurr(0) - , _oldLimit(0) + , _oldBuf(nullptr) + , _oldCurr(nullptr) + , _oldLimit(nullptr) , _oldSeek(-1) { if (_mode == 1) { _space = d4_new c4_Allocator; } } c4_Persist::~c4_Persist() { delete _differ; if (_owned) { - if (_root != 0) { + if (_root != nullptr) { _root->UnmappedAll(); } delete &_strategy; } delete _space; - if (_oldBuf != 0) { + if (_oldBuf != nullptr) { 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; + bool prev = _fCommit != nullptr; if (flag_) { _fCommit = &c4_Persist::Commit; } else { - _fCommit = 0; + _fCommit = nullptr; } return prev; } void c4_Persist::DoAutoCommit() { - if (_fCommit != 0) { + if (_fCommit != nullptr) { (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; + return _differ != nullptr ? &_differ->_storage : nullptr; } 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_)) { + if (_mode == 0 && (_differ == nullptr || 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); + c4_SaveContext ar(_strategy, false, _mode, full_ ? nullptr : _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; + _root = nullptr; - if (_space != 0) { + if (_space != nullptr) { _space->Initialize(); } c4_HandlerSeq *seq = d4_new c4_HandlerSeq(this); seq->DefineRoot(); SetRoot(seq); if (full_) { delete _differ; - _differ = 0; + _differ = nullptr; } 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 + if (_strategy._mapStart != nullptr && _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); } // 2006-08-01: maintain stable-storage space usage on re-open OccupySpace(_strategy._rootPos, _strategy._rootLen); // 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); } } t4_i32 c4_Persist::FreeBytes(t4_i32 *bytes_) { - return _space == 0 ? -1 : _space->FreeCounts(bytes_); + return _space == nullptr ? -1 : _space->FreeCounts(bytes_); } 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; + return nullptr; } // 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; + return nullptr; } 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) { + if (p != nullptr) { strat._bytesFlipped = p->Strategy()._bytesFlipped; } - c4_SaveContext ar(strat, true, 0, 0, 0); + c4_SaveContext ar(strat, true, 0, nullptr, nullptr); c4_Bytes tempWalk; - ar.SaveIt(root_, 0, tempWalk); + ar.SaveIt(root_, nullptr, 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) { + if (_space != nullptr) { _space->Occupy(pos_, len_); } } ///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/mk4storage/metakit/src/persist.h b/plugins/mk4storage/metakit/src/persist.h index 2b17a566..0be32fef 100644 --- a/plugins/mk4storage/metakit/src/persist.h +++ b/plugins/mk4storage/metakit/src/persist.h @@ -1,128 +1,128 @@ // persist.h -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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_); - t4_i32 FreeBytes(t4_i32 *bytes_ = 0); + t4_i32 FreeBytes(t4_i32 *bytes_ = nullptr); static c4_HandlerSeq *Load(c4_Stream *); static void Save(c4_Stream *, c4_HandlerSeq &root_); }; ///////////////////////////////////////////////////////////////////////////// #endif diff --git a/plugins/mk4storage/metakit/src/remap.cpp b/plugins/mk4storage/metakit/src/remap.cpp index 7c245965..01942c4d 100644 --- a/plugins/mk4storage/metakit/src/remap.cpp +++ b/plugins/mk4storage/metakit/src/remap.cpp @@ -1,1245 +1,1245 @@ // remap.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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() { } c4_View GetTemplate() override { return _base.Clone(); } int GetSize() override { return _base.GetSize(); } int Lookup(c4_Cursor key_, int &count_) override { int pos = 0; count_ = _base.GetSize(); return _base.RestrictSearch(*key_, pos, count_); } bool GetItem(int row_, int col_, c4_Bytes &buf_) override { 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); + c4_HashViewer(c4_Sequence &seq_, int numKeys_, c4_Sequence *map_ = nullptr); virtual ~c4_HashViewer(); c4_View GetTemplate() override; int GetSize() override; int Lookup(c4_Cursor key_, int &count_) override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; ///////////////////////////////////////////////////////////////////////////// // 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 , 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 _last_base, _last_limit, _last_slot; c4_View _last_view; int Slot(int &pos_); void Split(int block_, int row_); void Merge(int block_); void Validate() const; // 2004-04-27 new cache logic, thx MB void SetLast(int row_); void ClearLast(int slot_) { if (_last_slot >= slot_) { _last_limit = _last_slot = -1; _last_view = c4_View(); } } public: c4_BlockedViewer(c4_Sequence &seq_); virtual ~c4_BlockedViewer(); c4_View GetTemplate() override; int GetSize() override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; ///////////////////////////////////////////////////////////////////////////// #if defined(q4_CHECK) && 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") , _last_base(-1) , _last_limit(-1) , _last_slot(-1) { 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_) { const int n = _offsets.GetSize(); d4_assert(n > 0); d4_assert(pos_ <= (int)_offsets.GetAt(n - 1)); #if 0 // not sure the following adds any performance (the logic looks ok) // reason: won't work if inserts/removes always invalidate the cache if (_last_base <= pos_ && pos_ < _last_limit) { pos_ -= _last_base; return _last_slot; } #endif #if 0 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 = n - 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_) { ClearLast(bno_); int z = _offsets.GetSize(); 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_) { ClearLast(bno_); int z = _offsets.GetSize(); 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; } void c4_BlockedViewer::SetLast(int row_) { int orig = row_; int i = Slot(row_); d4_assert(0 <= i && i < _offsets.GetSize()); _last_limit = _offsets.GetAt(i); if (_last_limit == orig) { row_ = i; i = _offsets.GetSize(); _last_limit = 0; // force miss next time, but view is still cached } if (i != _last_slot) { _last_slot = i; _last_view = _pBlock(_base[i]); } _last_base = orig - row_; } bool c4_BlockedViewer::GetItem(int row_, int col_, c4_Bytes &buf_) { if (row_ < _last_base || row_ >= _last_limit) { SetLast(row_); } return _last_view.GetItem(row_ - _last_base, col_, buf_); } bool c4_BlockedViewer::SetItem(int row_, int col_, const c4_Bytes &buf_) { if (row_ < _last_base || row_ >= _last_limit) { SetLast(row_); } _last_view.SetItem(row_ - _last_base, 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 = _offsets.GetSize(); int i = Slot(pos_); d4_assert(0 <= i && i < z); ClearLast(i); 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 = _offsets.GetSize(); int i = Slot(pos_); d4_assert(0 <= i && i < z); ClearLast(i); 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(); c4_View GetTemplate() override; int GetSize() override; int Lookup(c4_Cursor key_, int &count_) override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; ///////////////////////////////////////////////////////////////////////////// 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(); c4_View GetTemplate() override; int GetSize() override; int Lookup(c4_Cursor key_, int &count_) override; bool GetItem(int row_, int col_, c4_Bytes &buf_) override; bool SetItem(int row_, int col_, const c4_Bytes &buf_) override; bool InsertRows(int pos_, c4_Cursor value_, int count_ = 1) override; bool RemoveRows(int pos_, int count_ = 1) override; }; ///////////////////////////////////////////////////////////////////////////// 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/plugins/mk4storage/metakit/src/remap.h b/plugins/mk4storage/metakit/src/remap.h index f191e83b..776322a0 100644 --- a/plugins/mk4storage/metakit/src/remap.h +++ b/plugins/mk4storage/metakit/src/remap.h @@ -1,25 +1,25 @@ // remap.h -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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_CreateHash(c4_Sequence &, int, c4_Sequence * = nullptr); 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/plugins/mk4storage/metakit/src/store.cpp b/plugins/mk4storage/metakit/src/store.cpp index 84be0941..b2d1c4e9 100644 --- a/plugins/mk4storage/metakit/src/store.cpp +++ b/plugins/mk4storage/metakit/src/store.cpp @@ -1,618 +1,618 @@ // store.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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; + _chain->_next = nullptr; 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 * successfully 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) { + if (root_.Persist() != nullptr) { // 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) { + if (q != nullptr) { c4_String vname(description_, q - description_); const char *d = Description(vname); - if (d != 0) { + if (d != nullptr) { 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) { + if (name_ == nullptr || *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) { + if (newRoot == nullptr) { 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()); } t4_i32 c4_Storage::FreeSpace(t4_i32 *bytes_) { return Persist()->FreeBytes(bytes_); } ///////////////////////////////////////////////////////////////////////////// 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; + return nullptr; } 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) +c4_StreamStrategy::c4_StreamStrategy(t4_i32 buflen_) : _stream(nullptr) , _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) + , _buffer(nullptr) , _buflen(0) , _position(0) { } c4_StreamStrategy::~c4_StreamStrategy() { - _mapStart = 0; + _mapStart = nullptr; _dataSize = 0; - if (_buffer != 0) { + if (_buffer != nullptr) { delete [] _buffer; } } bool c4_StreamStrategy::IsValid() const { return true; } int c4_StreamStrategy::DataRead(t4_i32 pos_, void *buffer_, int length_) { - if (_buffer != 0) { + if (_buffer != nullptr) { 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; + length_ = _stream != nullptr ? _stream->Read(buffer_, length_) : 0; } _position += length_; return length_; } void c4_StreamStrategy::DataWrite(t4_i32 pos_, const void *buffer_, int length_) { - if (_buffer != 0) { + if (_buffer != nullptr) { 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_)) { + if (_stream != nullptr && !_stream->Write(buffer_, length_)) { ++_failure; } } _position += length_; } t4_i32 c4_StreamStrategy::FileSize() { return _position; } ///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/mk4storage/metakit/src/store.inl b/plugins/mk4storage/metakit/src/store.inl index 497295a4..70b74dbf 100644 --- a/plugins/mk4storage/metakit/src/store.inl +++ b/plugins/mk4storage/metakit/src/store.inl @@ -1,19 +1,19 @@ // store.inl -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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), + : _origin (origin_), _chain (nullptr), _next (nullptr), _type (kNone), _index (0), _propId (0), _count (0), - _cursor (0), _bytes (0) + _cursor (nullptr), _bytes (nullptr) { d4_assert(_origin != 0); } ///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/mk4storage/metakit/src/string.cpp b/plugins/mk4storage/metakit/src/string.cpp index 9a5da82c..84ab6854 100644 --- a/plugins/mk4storage/metakit/src/string.cpp +++ b/plugins/mk4storage/metakit/src/string.cpp @@ -1,312 +1,312 @@ // string.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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 #include #include #include #ifdef _AIX #include #endif #if !q4_INLINE #include "mk4str.inl" #endif ///////////////////////////////////////////////////////////////////////////// #if defined(q4_WINCE) && 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 strcasecmp(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; } const char *strrchr(const char *p, char ch) { const char *q = 0; while (*p) { if (*p++ == ch) { q = p; } } return q; } #elif (defined(q4_MSVC) && q4_MSVC) || (defined(q4_WATC) && q4_WATC) || (defined(q4_BORC) && q4_BORC) || (defined(q4_MWCW) && q4_MWCW && __MWERKS__ < 0x3000) #define strcasecmp stricmp #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 cannot 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; +d4_reentrant static unsigned char *nullVec = nullptr; 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 if (n > 0) { 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); + Init(p, p != nullptr ? 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) { + if (p == nullptr || 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; + return p != nullptr ? p - Data() : -1; } int c4_String::ReverseFind(char ch) const { const char *p = strrchr(Data(), ch); - return p != 0 ? p - Data() : -1; + return p != nullptr ? p - Data() : -1; } int c4_String::FindOneOf(const char *set) const { const char *p = strpbrk(Data(), set); - return p != 0 ? p - Data() : -1; + return p != nullptr ? p - Data() : -1; } int c4_String::Find(const char *sub) const { const char *p = strstr(Data(), sub); - return p != 0 ? p - Data() : -1; + return p != nullptr ? 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/plugins/mk4storage/metakit/src/univ.cpp b/plugins/mk4storage/metakit/src/univ.cpp index 6ebd4664..dfa56471 100644 --- a/plugins/mk4storage/metakit/src/univ.cpp +++ b/plugins/mk4storage/metakit/src/univ.cpp @@ -1,236 +1,236 @@ // univ.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @file * A simple implementation of dynamic arrays */ #include "header.h" #if q4_UNIV // until end of source ///////////////////////////////////////////////////////////////////////////// #include // 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 defined(q4_BORC) && q4_BORC #include #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) +c4_BaseArray::c4_BaseArray() : _data(nullptr) , _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); + _data = _data == nullptr ? n == 0 ? (char *)nullptr : (char *)malloc(n) : n == 0 ? (free + (_data), (char *)nullptr) : (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); + SetAt(i++, nullptr); } _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); + int n = _ptrs.Add(nullptr); SetAt(n, newElement); return n; } void c4_StringArray::InsertAt(int nIndex, const char *newElement, int nCount) { - _ptrs.InsertAt(nIndex, 0, nCount); + _ptrs.InsertAt(nIndex, nullptr, 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); + SetAt(nIndex + i, nullptr); } _ptrs.RemoveAt(nIndex, nCount); } ///////////////////////////////////////////////////////////////////////////// #endif // q4_UNIV diff --git a/plugins/mk4storage/metakit/src/view.cpp b/plugins/mk4storage/metakit/src/view.cpp index 36771583..b9fba0d7 100644 --- a/plugins/mk4storage/metakit/src/view.cpp +++ b/plugins/mk4storage/metakit/src/view.cpp @@ -1,1306 +1,1306 @@ // view.cpp -- // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html /** @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(); ~c4_ThreadLock(); class Hold { public: Hold(); ~Hold(); }; }; ///////////////////////////////////////////////////////////////////////////// #if defined(q4_MULTI) && 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 static CRITICAL_SECTION gCritSect; c4_ThreadLock::c4_ThreadLock() { InitializeCriticalSection(&gCritSect); } c4_ThreadLock::~c4_ThreadLock() { DeleteCriticalSection(&gCritSect); } c4_ThreadLock::Hold::Hold() { EnterCriticalSection(&gCritSect); } c4_ThreadLock::Hold::~Hold() { LeaveCriticalSection(&gCritSect); } #else /* q4_WIN32 */ #include static pthread_mutex_t gMutex; d4_inline c4_ThreadLock::c4_ThreadLock() { pthread_mutex_init(&gMutex, 0); } d4_inline c4_ThreadLock::~c4_ThreadLock() { pthread_mutex_destroy(&gMutex); } 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::~c4_ThreadLock() { } d4_inline c4_ThreadLock::Hold::Hold() { } d4_inline c4_ThreadLock::Hold::~Hold() { } #endif ///////////////////////////////////////////////////////////////////////////// #if defined(q4_LOGPROPMODS) && 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); + _seq = d4_new c4_HandlerSeq(nullptr); } _IncSeqRef(); } /// Construct a view based on a custom viewer -c4_View::c4_View(c4_CustomViewer *viewer_) : _seq(0) +c4_View::c4_View(c4_CustomViewer *viewer_) : _seq(nullptr) { 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); + if (_seq == nullptr) { + _seq = d4_new c4_HandlerSeq(nullptr); } _IncSeqRef(); } /// Construct an empty view with one property -c4_View::c4_View(const c4_Property &prop_) : _seq(d4_new c4_HandlerSeq(0)) +c4_View::c4_View(const c4_Property &prop_) : _seq(d4_new c4_HandlerSeq(nullptr)) { _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()) { + if (h1->Persist() == nullptr || 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 = h2->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 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 equivalent 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 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) { + if (pos_ != nullptr) { *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); + c4_Sequence *seq = d4_new c4_HandlerSeq(nullptr); 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; +static c4_ThreadLock *sThreadLock = nullptr; +static c4_StringArray *sPropNames = nullptr; +static c4_DWordArray *sPropCounts = nullptr; /// Call this to get rid of some internal datastructues (on exit) void c4_Property::CleanupInternalData() { delete sPropNames; - sPropNames = 0; // race + sPropNames = nullptr; // race delete sPropCounts; - sPropCounts = 0; // race + sPropCounts = nullptr; // race delete sThreadLock; - sThreadLock = 0; // race + sThreadLock = nullptr; // race } c4_Property::c4_Property(char type_, const char *name_) : _type(type_) { - if (sThreadLock == 0) { + if (sThreadLock == nullptr) { sThreadLock = d4_new c4_ThreadLock; } c4_ThreadLock::Hold lock; // grabs the lock until end of scope - if (sPropNames == 0) { + if (sPropNames == nullptr) { sPropNames = d4_new c4_StringArray; } - if (sPropCounts == 0) { + if (sPropCounts == nullptr) { 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 defined(q4_CHECK) && 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/plugins/mk4storage/metakit/src/viewx.cpp b/plugins/mk4storage/metakit/src/viewx.cpp index b4a7e891..3789cd46 100644 --- a/plugins/mk4storage/metakit/src/viewx.cpp +++ b/plugins/mk4storage/metakit/src/viewx.cpp @@ -1,885 +1,885 @@ // viewx.cpp -- // This is part of Metakit, see http://www.equi4.com/metakit.html /** @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) + , _dependencies(nullptr) , _propertyLimit(0) - , _propertyMap(0) - , _tempBuf(0) + , _propertyMap(nullptr) + , _tempBuf(nullptr) { } 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; + return nullptr; } /// 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) { + if (_tempBuf == nullptr) { _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); int colNum = PropIndex(h.Property()); d4_assert(colNum >= 0); if (h.Property().Type() == 'V') { // If inserting from self: Make sure we get a copy of the bytes, // so we don't get an invalid pointer if the memory get realloc'ed h.GetBytes(ri, data, newElem_._seq == this); // 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 { h.GetBytes(ri, data); 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; + return nullptr; } 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; + _dependencies = nullptr; } 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; + return nullptr; } /// 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 !defined(q4_TINY) || !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_, bool noCopy_) 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_; } if (len_ > 0) { c4_Column *col = h.GetNthMemoCol(_cursor._index, true); - if (col != 0) { + if (col != nullptr) { if (noCopy_) { // 21-11-2005 optimization by A. Stigsen // return just the first segment (even if it is smaller than // len). this avoids any expensive memcopies, but you have to // remember to check length of the returned bytes. c4_ColIter iter(*col, off_, off_ + len_); iter.Next(); return c4_Bytes(iter.BufLoad(), iter.BufLen() < len_ ? iter.BufLen() : len_); } else { const t4_byte *bytes = col->FetchBytes(off_, len_, buffer, false); if (bytes == buffer.Contents()) { return buffer; } return c4_Bytes(bytes, len_); } } 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 (col != nullptr) { 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; + return (c4_Sequence *)nullptr; } // 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) + , _mapStart(nullptr) , _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) { + if (_mapStart != nullptr) { _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] & 0xF0) == 0x90 /* 2006-11-11 */ || (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 && (mark[3] & 0x40) == 0; 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) { + if (_mapStart != nullptr) { _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/plugins/mk4storage/mk4plugin.cpp b/plugins/mk4storage/mk4plugin.cpp index 65815f46..50c65f14 100644 --- a/plugins/mk4storage/mk4plugin.cpp +++ b/plugins/mk4storage/mk4plugin.cpp @@ -1,54 +1,54 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "mk4plugin.h" #include "storagefactorymk4impl.h" #include "storagefactoryregistry.h" namespace Akregator { namespace Backend { K_PLUGIN_FACTORY(MK4PluginFactory, registerPlugin(); ) void MK4Plugin::doInitialize() { m_factory = new StorageFactoryMK4Impl(); StorageFactoryRegistry::self()->registerFactory(m_factory, QStringLiteral("metakit")); } MK4Plugin::MK4Plugin(QObject *parent, const QVariantList ¶ms) : Plugin(parent, params) - , m_factory(0) + , m_factory(nullptr) { } MK4Plugin::~MK4Plugin() { StorageFactoryRegistry::self()->unregisterFactory(QStringLiteral("metakit")); delete m_factory; } } // namespace Backend } // namespace Akregator #include "mk4plugin.moc" diff --git a/src/articleviewerwidget.cpp b/src/articleviewerwidget.cpp index ca363cd7..a8de3fba 100644 --- a/src/articleviewerwidget.cpp +++ b/src/articleviewerwidget.cpp @@ -1,420 +1,420 @@ /* This file is part of Akregator. Copyright (C) 2004 Teemu Rytilahti 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "articleviewerwidget.h" #include "akregatorconfig.h" #include "aboutdata.h" #include "actionmanager.h" #include "actions.h" #include "article.h" #include "articleformatter.h" #include "articlejobs.h" #include "articlematcher.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "utils.h" #include "openurlrequest.h" #include "akregator_debug.h" #include "akregator-version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akregator; using namespace Akregator::Filters; ArticleViewerWidget::ArticleViewerWidget(const QString &grantleeDirectory, KActionCollection *ac, QWidget *parent) : QWidget(parent) , m_imageDir(QUrl::fromLocalFile(QString(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/")))) - , m_node(0) + , m_node(nullptr) , m_viewMode(NormalView) , m_articleViewerWidgetNg(new Akregator::ArticleViewerWebEngineWidgetNg(ac, this)) , m_grantleeDirectory(grantleeDirectory) { QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_articleViewerWidgetNg); m_articleHtmlWriter = new Akregator::ArticleHtmlWebEngineWriter(m_articleViewerWidgetNg->articleViewerNg(), this); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::signalOpenUrlRequest, this, &ArticleViewerWidget::signalOpenUrlRequest); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::showStatusBarMessage, this, &ArticleViewerWidget::showStatusBarMessage); } ArticleViewerWidget::~ArticleViewerWidget() { } QSharedPointer ArticleViewerWidget::normalViewFormatter() { if (!m_normalViewFormatter.data()) { m_normalViewFormatter = QSharedPointer(new DefaultNormalViewFormatter(m_grantleeDirectory, m_imageDir, m_articleViewerWidgetNg->articleViewerNg())); } return m_normalViewFormatter; } QSharedPointer ArticleViewerWidget::combinedViewFormatter() { if (!m_combinedViewFormatter.data()) { m_combinedViewFormatter = QSharedPointer(new DefaultCombinedViewFormatter(m_grantleeDirectory, m_imageDir, m_articleViewerWidgetNg->articleViewerNg())); } return m_combinedViewFormatter; } void ArticleViewerWidget::slotZoomChangeInFrame(qreal value) { m_articleViewerWidgetNg->articleViewerNg()->setZoomFactor(value); } void ArticleViewerWidget::slotCopy() { m_articleViewerWidgetNg->articleViewerNg()->slotCopy(); } void ArticleViewerWidget::slotSelectionChanged() { ActionManager::getInstance()->action(QStringLiteral("viewer_copy"))->setEnabled(!m_articleViewerWidgetNg->articleViewerNg()->selectedText().isEmpty()); } void ArticleViewerWidget::slotPrint() { m_articleViewerWidgetNg->slotPrint(); } void ArticleViewerWidget::slotPrintPreview() { m_articleViewerWidgetNg->slotPrintPreview(); } void ArticleViewerWidget::connectToNode(TreeNode *node) { if (node) { if (m_viewMode == CombinedView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotUpdateCombinedView); connect(node, &TreeNode::signalArticlesAdded, this, &ArticleViewerWidget::slotArticlesAdded); connect(node, &TreeNode::signalArticlesRemoved, this, &ArticleViewerWidget::slotArticlesRemoved); connect(node, &TreeNode::signalArticlesUpdated, this, &ArticleViewerWidget::slotArticlesUpdated); } else if (m_viewMode == SummaryView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotShowSummary); } connect(node, &TreeNode::signalDestroyed, this, &ArticleViewerWidget::slotClear); } } void ArticleViewerWidget::disconnectFromNode(TreeNode *node) { if (node) { node->disconnect(this); } } void ArticleViewerWidget::renderContent(const QString &text) { m_currentText = text; reload(); } void ArticleViewerWidget::beginWriting() { m_articleHtmlWriter->begin(); } void ArticleViewerWidget::endWriting() { m_articleHtmlWriter->end(); } void ArticleViewerWidget::slotShowSummary(TreeNode *node) { m_viewMode = SummaryView; if (!node) { slotClear(); return; } if (node != m_node) { disconnectFromNode(m_node); connectToNode(node); m_node = node; } const QString summary = normalViewFormatter()->formatSummary(node); m_link.clear(); renderContent(summary); setArticleActionsEnabled(false); } void ArticleViewerWidget::showArticle(const Akregator::Article &article) { if (article.isNull() || article.isDeleted()) { slotClear(); return; } const QUrl xmlUrl = QUrl(article.feed()->xmlUrl()); qCDebug(AKREGATOR_LOG) << "showing Article - xmlUrl:" << xmlUrl; m_articleHtmlWriter->setBaseUrl(xmlUrl); m_viewMode = NormalView; disconnectFromNode(m_node); m_article = article; m_node = nullptr; m_link = article.link(); if (article.feed()->loadLinkedWebsite()) { openUrl(article.link()); } else { renderContent(normalViewFormatter()->formatArticles(QVector() << article, ArticleFormatter::ShowIcon)); } setArticleActionsEnabled(true); } bool ArticleViewerWidget::openUrl(const QUrl &url) { if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite()) { m_articleViewerWidgetNg->articleViewerNg()->load(url); } else { reload(); } return true; } void ArticleViewerWidget::setFilters(const std::vector< QSharedPointer > &filters) { if (filters == m_filters) { return; } m_filters = filters; slotUpdateCombinedView(); } void ArticleViewerWidget::slotUpdateCombinedView() { if (m_viewMode != CombinedView) { return; } if (!m_node) { return slotClear(); } m_articleViewerWidgetNg->saveCurrentPosition(); QString text; int num = 0; QTime spent; spent.start(); const std::vector< QSharedPointer >::const_iterator filterEnd = m_filters.cend(); QVector
articles; for (const Article &i : qAsConst(m_articles)) { if (i.isDeleted()) { continue; } auto func = [i](const QSharedPointer &matcher) -> bool { return !matcher->matches(i); }; if (std::find_if(m_filters.cbegin(), filterEnd, func) != filterEnd) { continue; } articles << i; ++num; } text = combinedViewFormatter()->formatArticles(articles, ArticleFormatter::NoIcon); qCDebug(AKREGATOR_LOG) << "Combined view rendering: (" << num << " articles):" << "generating HTML:" << spent.elapsed() << "ms"; renderContent(text); qCDebug(AKREGATOR_LOG) << "HTML rendering:" << spent.elapsed() << "ms"; } void ArticleViewerWidget::slotArticlesUpdated(TreeNode * /*node*/, const QVector
& /*list*/) { if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesAdded(TreeNode * /*node*/, const QVector
&list) { if (m_viewMode == CombinedView) { //TODO sort list, then merge m_articles << list; std::sort(m_articles.begin(), m_articles.end()); slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesRemoved(TreeNode * /*node*/, const QVector
&list) { Q_UNUSED(list) if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotClear() { disconnectFromNode(m_node); m_node = nullptr; m_article = Article(); m_articles.clear(); renderContent(QString()); } void ArticleViewerWidget::showNode(TreeNode *node) { m_viewMode = CombinedView; if (node != m_node) { disconnectFromNode(m_node); } connectToNode(node); m_articles.clear(); m_article = Article(); m_node = node; delete m_listJob; m_listJob = node->createListJob(); connect(m_listJob.data(), &ArticleListJob::finished, this, &ArticleViewerWidget::slotArticlesListed); m_listJob->start(); slotUpdateCombinedView(); } qreal ArticleViewerWidget::zoomFactor() const { return m_articleViewerWidgetNg->articleViewerNg()->zoomFactor(); } void ArticleViewerWidget::slotArticlesListed(KJob *job) { Q_ASSERT(job); Q_ASSERT(job == m_listJob); TreeNode *node = m_listJob->node(); if (job->error() || !node) { if (!node) { qCWarning(AKREGATOR_LOG) << "Node to be listed is already deleted"; } else { qCWarning(AKREGATOR_LOG) << job->errorText(); } slotUpdateCombinedView(); return; } m_articles = m_listJob->articles(); std::sort(m_articles.begin(), m_articles.end()); if (node && !m_articles.isEmpty()) { m_link = m_articles.first().link(); } else { m_link = QUrl(); } slotUpdateCombinedView(); } void ArticleViewerWidget::keyPressEvent(QKeyEvent *e) { e->ignore(); } void ArticleViewerWidget::updateAfterConfigChanged() { switch (m_viewMode) { case NormalView: if (!m_article.isNull()) { renderContent(normalViewFormatter()->formatArticles(QVector() << m_article, ArticleFormatter::ShowIcon)); } break; case CombinedView: slotUpdateCombinedView(); break; case SummaryView: slotShowSummary(m_node); break; } } void ArticleViewerWidget::reload() { beginWriting(); m_articleHtmlWriter->queue(m_currentText); endWriting(); } QSize ArticleViewerWidget::sizeHint() const { // Increase height a bit so that we can (roughly) read 25 lines of text QSize sh = QWidget::sizeHint(); sh.setHeight(qMax(sh.height(), 25 * fontMetrics().height())); return sh; } void ArticleViewerWidget::displayAboutPage() { m_articleViewerWidgetNg->articleViewerNg()->showAboutPage(); } void ArticleViewerWidget::setArticleActionsEnabled(bool enabled) { ActionManager::getInstance()->setArticleActionsEnabled(enabled); } Akregator::ArticleViewerWebEngineWidgetNg *ArticleViewerWidget::articleViewerWidgetNg() const { return m_articleViewerWidgetNg; } diff --git a/src/feed/feed.cpp b/src/feed/feed.cpp index b480fb91..698470bd 100644 --- a/src/feed/feed.cpp +++ b/src/feed/feed.cpp @@ -1,947 +1,947 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "feed.h" #include "akregatorconfig.h" #include "article.h" #include "articlejobs.h" #include "feedstorage.h" #include "fetchqueue.h" #include "folder.h" #include "notificationmanager.h" #include "storage.h" #include "treenodevisitor.h" #include "types.h" #include "utils.h" #include "feedretriever.h" #include #include "akregator_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Syndication::ItemPtr; using namespace Akregator; template class Container> QVector valuesToVector(const Container &container) { QVector values; values.reserve(container.size()); for (const Value &value : container) { values << value; } return values; } class Q_DECL_HIDDEN Akregator::Feed::Private { Akregator::Feed *const q; public: explicit Private(Backend::Storage *storage, Akregator::Feed *qq); Backend::Storage *storage = nullptr; bool autoFetch = false; int fetchInterval; ArchiveMode archiveMode; int maxArticleAge; int maxArticleNumber; bool markImmediatelyAsRead = false; bool useNotification = false; bool loadLinkedWebsite = false; int lastFetched; Syndication::ErrorCode fetchErrorCode; int fetchTries; bool followDiscovery = false; Syndication::Loader *loader = nullptr; bool articlesLoaded = false; Backend::FeedStorage *archive = nullptr; QString xmlUrl; QString htmlUrl; QString description; /** list of feed articles */ QHash articles; /** list of deleted articles. This contains **/ QVector
deletedArticles; /** caches guids of deleted articles for notification */ QVector
addedArticlesNotify; QVector
removedArticlesNotify; QVector
updatedArticlesNotify; QPixmap imagePixmap; Syndication::ImagePtr image; QIcon favicon; mutable int totalCount; void setTotalCountDirty() const { totalCount = -1; } }; QString Akregator::Feed::archiveModeToString(ArchiveMode mode) { switch (mode) { case keepAllArticles: return QStringLiteral("keepAllArticles"); case disableArchiving: return QStringLiteral("disableArchiving"); case limitArticleNumber: return QStringLiteral("limitArticleNumber"); case limitArticleAge: return QStringLiteral("limitArticleAge"); default: break; } return QStringLiteral("globalDefault"); } -Akregator::Feed *Akregator::Feed::fromOPML(QDomElement e, Backend::Storage *storage) +Akregator::Feed *Akregator::Feed::fromOPML(const QDomElement &e, Backend::Storage *storage) { if (!e.hasAttribute(QStringLiteral("xmlUrl")) && !e.hasAttribute(QStringLiteral("xmlurl")) && !e.hasAttribute(QStringLiteral("xmlURL"))) { return nullptr; } QString title = e.hasAttribute(QStringLiteral("text")) ? e.attribute(QStringLiteral("text")) : e.attribute(QStringLiteral("title")); QString xmlUrl = e.hasAttribute(QStringLiteral("xmlUrl")) ? e.attribute(QStringLiteral("xmlUrl")) : e.attribute(QStringLiteral("xmlurl")); if (xmlUrl.isEmpty()) { xmlUrl = e.attribute(QStringLiteral("xmlURL")); } bool useCustomFetchInterval = e.attribute(QStringLiteral("useCustomFetchInterval")) == QLatin1String("true"); QString htmlUrl = e.attribute(QStringLiteral("htmlUrl")); QString description = e.attribute(QStringLiteral("description")); int fetchInterval = e.attribute(QStringLiteral("fetchInterval")).toInt(); ArchiveMode archiveMode = stringToArchiveMode(e.attribute(QStringLiteral("archiveMode"))); int maxArticleAge = e.attribute(QStringLiteral("maxArticleAge")).toUInt(); int maxArticleNumber = e.attribute(QStringLiteral("maxArticleNumber")).toUInt(); bool markImmediatelyAsRead = e.attribute(QStringLiteral("markImmediatelyAsRead")) == QLatin1String("true"); bool useNotification = e.attribute(QStringLiteral("useNotification")) == QLatin1String("true"); bool loadLinkedWebsite = e.attribute(QStringLiteral("loadLinkedWebsite")) == QLatin1String("true"); uint id = e.attribute(QStringLiteral("id")).toUInt(); Feed *const feed = new Feed(storage); feed->setTitle(title); feed->setXmlUrl(xmlUrl); feed->setCustomFetchIntervalEnabled(useCustomFetchInterval); feed->setHtmlUrl(htmlUrl); feed->setId(id); feed->setDescription(description); feed->setArchiveMode(archiveMode); feed->setUseNotification(useNotification); feed->setFetchInterval(fetchInterval); feed->setMaxArticleAge(maxArticleAge); feed->setMaxArticleNumber(maxArticleNumber); feed->setMarkImmediatelyAsRead(markImmediatelyAsRead); feed->setLoadLinkedWebsite(loadLinkedWebsite); if (!feed->d->archive && storage) { // Instead of loading the articles, we use the cache from storage feed->d->archive = storage->archiveFor(xmlUrl); feed->d->totalCount = feed->d->archive->totalCount(); } return feed; } bool Akregator::Feed::accept(TreeNodeVisitor *visitor) { if (visitor->visitFeed(this)) { return true; } else { return visitor->visitTreeNode(this); } } QVector Akregator::Feed::folders() const { return QVector(); } QVector Akregator::Feed::folders() { return QVector(); } QVector Akregator::Feed::feeds() const { QVector list; list.append(this); return list; } QVector Akregator::Feed::feeds() { QVector list; list.append(this); return list; } Article Akregator::Feed::findArticle(const QString &guid) const { return d->articles.value(guid); } QVector
Akregator::Feed::articles() { if (!d->articlesLoaded) { loadArticles(); } return valuesToVector(d->articles); } Backend::Storage *Akregator::Feed::storage() { return d->storage; } void Akregator::Feed::loadArticles() { if (d->articlesLoaded) { return; } if (!d->archive && d->storage) { d->archive = d->storage->archiveFor(xmlUrl()); } QStringList list = d->archive->articles(); for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { Article mya(*it, this, d->archive); d->articles[mya.guid()] = mya; if (mya.isDeleted()) { d->deletedArticles.append(mya); } } d->articlesLoaded = true; enforceLimitArticleNumber(); recalcUnreadCount(); } void Akregator::Feed::recalcUnreadCount() { QVector
tarticles = articles(); QVector
::ConstIterator it; QVector
::ConstIterator en = tarticles.constEnd(); int oldUnread = d->archive->unread(); int unread = 0; for (it = tarticles.constBegin(); it != en; ++it) { if (!(*it).isDeleted() && (*it).status() != Read) { ++unread; } } if (unread != oldUnread) { d->archive->setUnread(unread); nodeModified(); } } Akregator::Feed::ArchiveMode Akregator::Feed::stringToArchiveMode(const QString &str) { if (str == QLatin1String("globalDefault")) { return globalDefault; } else if (str == QLatin1String("keepAllArticles")) { return keepAllArticles; } else if (str == QLatin1String("disableArchiving")) { return disableArchiving; } else if (str == QLatin1String("limitArticleNumber")) { return limitArticleNumber; } else if (str == QLatin1String("limitArticleAge")) { return limitArticleAge; } return globalDefault; } Akregator::Feed::Private::Private(Backend::Storage *storage_, Akregator::Feed *qq) : q(qq) , storage(storage_) , autoFetch(false) , fetchInterval(30) , archiveMode(globalDefault) , maxArticleAge(60) , maxArticleNumber(1000) , markImmediatelyAsRead(false) , useNotification(false) , loadLinkedWebsite(false) , lastFetched(0) , fetchErrorCode(Syndication::Success) , fetchTries(0) , followDiscovery(false) , loader(nullptr) , articlesLoaded(false) , archive(nullptr) , totalCount(-1) { Q_ASSERT(q); Q_ASSERT(storage); } Akregator::Feed::Feed(Backend::Storage *storage) : TreeNode() , d(new Private(storage, this)) { } Akregator::Feed::~Feed() { slotAbortFetch(); emitSignalDestroyed(); delete d; d = nullptr; } void Akregator::Feed::loadFavicon(const QUrl &url) { KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url); connect(job, &KIO::FavIconRequestJob::result, this, [job, this](KJob *) { if (!job->error()) { setFavicon(QIcon(job->iconFile())); } }); } bool Akregator::Feed::useCustomFetchInterval() const { return d->autoFetch; } void Akregator::Feed::setCustomFetchIntervalEnabled(bool enabled) { d->autoFetch = enabled; } int Akregator::Feed::fetchInterval() const { return d->fetchInterval; } void Akregator::Feed::setFetchInterval(int interval) { d->fetchInterval = interval; } int Akregator::Feed::maxArticleAge() const { return d->maxArticleAge; } void Akregator::Feed::setMaxArticleAge(int maxArticleAge) { d->maxArticleAge = maxArticleAge; } int Akregator::Feed::maxArticleNumber() const { return d->maxArticleNumber; } void Akregator::Feed::setMaxArticleNumber(int maxArticleNumber) { d->maxArticleNumber = maxArticleNumber; } bool Akregator::Feed::markImmediatelyAsRead() const { return d->markImmediatelyAsRead; } bool Akregator::Feed::isFetching() const { return d->loader != nullptr; } void Akregator::Feed::setMarkImmediatelyAsRead(bool enabled) { d->markImmediatelyAsRead = enabled; } void Akregator::Feed::setUseNotification(bool enabled) { d->useNotification = enabled; } bool Akregator::Feed::useNotification() const { return d->useNotification; } void Akregator::Feed::setLoadLinkedWebsite(bool enabled) { d->loadLinkedWebsite = enabled; } bool Akregator::Feed::loadLinkedWebsite() const { return d->loadLinkedWebsite; } QPixmap Akregator::Feed::image() const { return d->imagePixmap; } QString Akregator::Feed::xmlUrl() const { return d->xmlUrl; } void Akregator::Feed::setXmlUrl(const QString &s) { d->xmlUrl = s; if (!Settings::fetchOnStartup()) { QTimer::singleShot(KRandom::random() % 4000, this, &Feed::slotAddFeedIconListener); // TODO: let's give a gui some time to show up before starting the fetch when no fetch on startup is used. replace this with something proper later... } } QString Akregator::Feed::htmlUrl() const { return d->htmlUrl; } void Akregator::Feed::setHtmlUrl(const QString &s) { d->htmlUrl = s; } QString Akregator::Feed::description() const { return d->description; } void Akregator::Feed::setDescription(const QString &s) { d->description = s; } bool Akregator::Feed::fetchErrorOccurred() const { return d->fetchErrorCode != Syndication::Success; } Syndication::ErrorCode Akregator::Feed::fetchErrorCode() const { return d->fetchErrorCode; } bool Akregator::Feed::isArticlesLoaded() const { return d->articlesLoaded; } QDomElement Akregator::Feed::toOPML(QDomElement parent, QDomDocument document) const { QDomElement el = document.createElement(QStringLiteral("outline")); el.setAttribute(QStringLiteral("text"), title()); el.setAttribute(QStringLiteral("title"), title()); el.setAttribute(QStringLiteral("xmlUrl"), d->xmlUrl); el.setAttribute(QStringLiteral("htmlUrl"), d->htmlUrl); el.setAttribute(QStringLiteral("id"), QString::number(id())); el.setAttribute(QStringLiteral("description"), d->description); el.setAttribute(QStringLiteral("useCustomFetchInterval"), (useCustomFetchInterval() ? QStringLiteral("true") : QStringLiteral("false"))); el.setAttribute(QStringLiteral("fetchInterval"), QString::number(fetchInterval())); el.setAttribute(QStringLiteral("archiveMode"), archiveModeToString(d->archiveMode)); el.setAttribute(QStringLiteral("maxArticleAge"), d->maxArticleAge); el.setAttribute(QStringLiteral("maxArticleNumber"), d->maxArticleNumber); if (d->markImmediatelyAsRead) { el.setAttribute(QStringLiteral("markImmediatelyAsRead"), QStringLiteral("true")); } if (d->useNotification) { el.setAttribute(QStringLiteral("useNotification"), QStringLiteral("true")); } if (d->loadLinkedWebsite) { el.setAttribute(QStringLiteral("loadLinkedWebsite"), QStringLiteral("true")); } el.setAttribute(QStringLiteral("maxArticleNumber"), d->maxArticleNumber); el.setAttribute(QStringLiteral("type"), QStringLiteral("rss")); // despite some additional fields, it is still "rss" OPML el.setAttribute(QStringLiteral("version"), QStringLiteral("RSS")); parent.appendChild(el); return el; } KJob *Akregator::Feed::createMarkAsReadJob() { ArticleModifyJob *job = new ArticleModifyJob; Q_FOREACH (const Article &i, articles()) { const ArticleId aid = { xmlUrl(), i.guid() }; job->setStatus(aid, Read); } return job; } void Akregator::Feed::slotAddToFetchQueue(FetchQueue *queue, bool intervalFetchOnly) { if (!intervalFetchOnly) { queue->addFeed(this); } else { int interval = -1; if (useCustomFetchInterval()) { interval = fetchInterval() * 60; } else if (Settings::useIntervalFetch()) { interval = Settings::autoFetchInterval() * 60; } uint lastFetch = d->archive->lastFetch().toTime_t(); uint now = QDateTime::currentDateTimeUtc().toTime_t(); if (interval > 0 && (now - lastFetch) >= static_cast(interval)) { queue->addFeed(this); } } } void Akregator::Feed::slotAddFeedIconListener() { loadFavicon(QUrl(d->xmlUrl)); } void Akregator::Feed::appendArticles(const Syndication::FeedPtr &feed) { d->setTotalCountDirty(); bool changed = false; const bool notify = useNotification() || Settings::useNotifications(); QList items = feed->items(); QList::ConstIterator it = items.constBegin(); QList::ConstIterator en = items.constEnd(); int nudge = 0; QVector
deletedArticles = d->deletedArticles; for (; it != en; ++it) { if (!d->articles.contains((*it)->id())) { // article not in list Article mya(*it, this); mya.offsetPubDate(nudge); nudge--; appendArticle(mya); d->addedArticlesNotify.append(mya); if (!mya.isDeleted() && !markImmediatelyAsRead()) { mya.setStatus(New); } else { mya.setStatus(Read); } if (notify) { NotificationManager::self()->slotNotifyArticle(mya); } changed = true; } else { // article is in list // if the article's guid is no hash but an ID, we have to check if the article was updated. That's done by comparing the hash values. Article old = d->articles[(*it)->id()]; Article mya(*it, this); if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted()) { mya.setKeep(old.keep()); int oldstatus = old.status(); old.setStatus(Read); d->articles.remove(old.guid()); appendArticle(mya); mya.setStatus(oldstatus); d->updatedArticlesNotify.append(mya); changed = true; } else if (old.isDeleted()) { deletedArticles.removeAll(mya); } } } QVector
::ConstIterator dit = deletedArticles.constBegin(); QVector
::ConstIterator dtmp; QVector
::ConstIterator den = deletedArticles.constEnd(); // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore while (dit != den) { dtmp = dit; ++dit; d->articles.remove((*dtmp).guid()); d->archive->deleteArticle((*dtmp).guid()); d->removedArticlesNotify.append(*dtmp); changed = true; d->deletedArticles.removeAll(*dtmp); } if (changed) { articlesModified(); } } bool Akregator::Feed::usesExpiryByAge() const { return (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->archiveMode == limitArticleAge; } bool Akregator::Feed::isExpired(const Article &a) const { QDateTime now = QDateTime::currentDateTime(); int expiryAge = -1; // check whether the feed uses the global default and the default is limitArticleAge if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) { expiryAge = Settings::maxArticleAge() * 24 * 3600; } else // otherwise check if this feed has limitArticleAge set if (d->archiveMode == limitArticleAge) { expiryAge = d->maxArticleAge * 24 * 3600; } return expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge; } void Akregator::Feed::appendArticle(const Article &a) { if ((a.keep() && Settings::doNotExpireImportantArticles()) || (!usesExpiryByAge() || !isExpired(a))) { // if not expired if (!d->articles.contains(a.guid())) { d->articles[a.guid()] = a; if (!a.isDeleted() && a.status() != Read) { setUnread(unread() + 1); } } } } void Akregator::Feed::fetch(bool followDiscovery) { d->followDiscovery = followDiscovery; d->fetchTries = 0; // mark all new as unread for (auto it = d->articles.begin(), end = d->articles.end(); it != end; ++it) { if ((*it).status() == New) { (*it).setStatus(Unread); } } Q_EMIT fetchStarted(this); tryFetch(); } void Akregator::Feed::slotAbortFetch() { if (d->loader) { d->loader->abort(); } } void Akregator::Feed::tryFetch() { d->fetchErrorCode = Syndication::Success; d->loader = Syndication::Loader::create(this, SLOT(fetchCompleted(Syndication::Loader*, Syndication::FeedPtr, Syndication::ErrorCode))); d->loader->loadFrom(QUrl(d->xmlUrl), new FeedRetriever()); } void Akregator::Feed::slotImageFetched(const QPixmap &image) { setImage(image); } void Akregator::Feed::fetchCompleted(Syndication::Loader *l, Syndication::FeedPtr doc, Syndication::ErrorCode status) { // Note that loader instances delete themselves d->loader = nullptr; // fetching wasn't successful: if (status != Syndication::Success) { if (status == Syndication::Aborted) { d->fetchErrorCode = Syndication::Success; Q_EMIT fetchAborted(this); } else if (d->followDiscovery && (status == Syndication::InvalidXml) && (d->fetchTries < 3) && (l->discoveredFeedURL().isValid())) { d->fetchTries++; d->xmlUrl = l->discoveredFeedURL().url(); Q_EMIT fetchDiscovery(this); tryFetch(); } else { d->fetchErrorCode = status; Q_EMIT fetchError(this); } markAsFetchedNow(); return; } loadArticles(); // TODO: make me fly: make this delayed loadFavicon(QUrl(xmlUrl())); d->fetchErrorCode = Syndication::Success; if (d->imagePixmap.isNull()) { const QString imageFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/") + Utils::fileNameForUrl(d->xmlUrl) + QLatin1String(".png"); d->imagePixmap = QPixmap(imageFileName, "PNG"); } if (title().isEmpty()) { setTitle(Syndication::htmlToPlainText(doc->title())); } d->description = doc->description(); d->htmlUrl = doc->link(); appendArticles(doc); markAsFetchedNow(); Q_EMIT fetched(this); } void Akregator::Feed::markAsFetchedNow() { if (d->archive) { d->archive->setLastFetch(QDateTime::currentDateTimeUtc()); } } QIcon Akregator::Feed::icon() const { if (fetchErrorOccurred()) { return QIcon::fromTheme(QStringLiteral("dialog-error")); } return !d->favicon.isNull() ? d->favicon : QIcon::fromTheme(QStringLiteral("text-html")); } void Akregator::Feed::deleteExpiredArticles(ArticleDeleteJob *deleteJob) { if (!usesExpiryByAge()) { return; } setNotificationMode(false); QList toDelete; const QString feedUrl = xmlUrl(); const bool useKeep = Settings::doNotExpireImportantArticles(); for (const Article &i : qAsConst(d->articles)) { if ((!useKeep || !i.keep()) && isExpired(i)) { const ArticleId aid = { feedUrl, i.guid() }; toDelete.append(aid); } } deleteJob->appendArticleIds(toDelete); setNotificationMode(true); } void Akregator::Feed::setFavicon(const QIcon &icon) { d->favicon = icon; nodeModified(); } void Akregator::Feed::setImage(const QPixmap &p) { if (p.isNull()) { return; } d->imagePixmap = p; const QString filename = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/") + Utils::fileNameForUrl(d->xmlUrl) + QLatin1String(".png"); QFileInfo fileInfo(filename); QDir().mkpath(fileInfo.absolutePath()); d->imagePixmap.save(filename, "PNG"); nodeModified(); } Akregator::Feed::ArchiveMode Akregator::Feed::archiveMode() const { return d->archiveMode; } void Akregator::Feed::setArchiveMode(ArchiveMode archiveMode) { d->archiveMode = archiveMode; } int Akregator::Feed::unread() const { return d->archive ? d->archive->unread() : 0; } void Akregator::Feed::setUnread(int unread) { if (d->archive && unread != d->archive->unread()) { d->archive->setUnread(unread); nodeModified(); } } void Akregator::Feed::setArticleDeleted(Article &a) { d->setTotalCountDirty(); if (!d->deletedArticles.contains(a)) { d->deletedArticles.append(a); } d->updatedArticlesNotify.append(a); articlesModified(); } void Akregator::Feed::setArticleChanged(Article &a, int oldStatus, bool process) { int newStatus = a.status(); if (oldStatus != -1) { if (oldStatus == Read && newStatus != Read) { setUnread(unread() + 1); } else if (oldStatus != Read && newStatus == Read) { setUnread(unread() - 1); } } d->updatedArticlesNotify.append(a); if (process) { articlesModified(); } } int Akregator::Feed::totalCount() const { if (d->totalCount == -1) { d->totalCount = std::count_if(d->articles.constBegin(), d->articles.constEnd(), [](const Article &art) -> bool { return !art.isDeleted(); }); } return d->totalCount; } TreeNode *Akregator::Feed::next() { if (nextSibling()) { return nextSibling(); } Folder *p = parent(); while (p) { if (p->nextSibling()) { return p->nextSibling(); } else { p = p->parent(); } } return nullptr; } const TreeNode *Akregator::Feed::next() const { if (nextSibling()) { return nextSibling(); } const Folder *p = parent(); while (p) { if (p->nextSibling()) { return p->nextSibling(); } else { p = p->parent(); } } return nullptr; } void Akregator::Feed::doArticleNotification() { if (!d->addedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->addedArticlesNotify; Q_EMIT signalArticlesAdded(this, l); d->addedArticlesNotify.clear(); } if (!d->updatedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->updatedArticlesNotify; Q_EMIT signalArticlesUpdated(this, l); d->updatedArticlesNotify.clear(); } if (!d->removedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->removedArticlesNotify; Q_EMIT signalArticlesRemoved(this, l); d->removedArticlesNotify.clear(); } TreeNode::doArticleNotification(); } void Akregator::Feed::enforceLimitArticleNumber() { int limit = -1; if (d->archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber) { limit = Settings::maxArticleNumber(); } else if (d->archiveMode == limitArticleNumber) { limit = maxArticleNumber(); } if (limit == -1 || limit >= d->articles.count() - d->deletedArticles.count()) { return; } QVector
articles = valuesToVector(d->articles); std::sort(articles.begin(), articles.end()); int c = 0; const bool useKeep = Settings::doNotExpireImportantArticles(); for (Article i : qAsConst(articles)) { if (c < limit) { if (!i.isDeleted() && (!useKeep || !i.keep())) { ++c; } } else if (!useKeep || !i.keep()) { i.setDeleted(); } } } diff --git a/src/feed/feed.h b/src/feed/feed.h index 9c957298..90e5a9ee 100644 --- a/src/feed/feed.h +++ b/src/feed/feed.h @@ -1,304 +1,304 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_FEED_H #define AKREGATOR_FEED_H #include "akregator_export.h" #include "treenode.h" #include #include class QDomElement; class QString; namespace Akregator { class Article; class FetchQueue; class TreeNodeVisitor; class ArticleDeleteJob; namespace Backend { class Storage; } /** represents a feed */ class AKREGATOR_EXPORT Feed : public TreeNode { friend class ::Akregator::Article; friend class ::Akregator::Folder; Q_OBJECT public: /** the archiving modes */ enum ArchiveMode { globalDefault, /**< use default from Settings (default) */ keepAllArticles, /**< Don't delete any articles */ disableArchiving, /**< Don't save any articles except articles with keep flag set (equal to maxArticleNumber() == 0) */ limitArticleNumber, /**< Save maxArticleNumber() articles, plus the ones with keep flag set */ limitArticleAge /**< Save articles not older than maxArticleAge() (or keep flag set) */ }; // class methods /** converts strings to ArchiveMode value if parsing fails, it returns ArchiveMode::globalDefault */ static ArchiveMode stringToArchiveMode(const QString &str); /** converts ArchiveMode values to corresponding strings */ static QString archiveModeToString(ArchiveMode mode); /** creates a Feed object from a description in OPML format */ - static Feed *fromOPML(QDomElement e, Akregator::Backend::Storage *storage); + static Feed *fromOPML(const QDomElement &e, Akregator::Backend::Storage *storage); /** default constructor */ explicit Feed(Akregator::Backend::Storage *storage); ~Feed(); bool accept(TreeNodeVisitor *visitor) override; /** exports the feed settings to OPML */ QDomElement toOPML(QDomElement parent, QDomDocument document) const override; /** returns whether this feed uses its own fetch interval or the global setting @return @c true iff this feed has a custom fetch interval */ bool useCustomFetchInterval() const; /** set if the feed has its custom fetch interval or uses the global setting @param enabled @c true: use custom interval, @c false: use global default */ void setCustomFetchIntervalEnabled(bool enabled); // FIXME is it -1 or 0 to disable interval fetching? /** Returns custom auto fetch interval of this feed. @return custom fetch interval in minutes, 0 if disabled */ int fetchInterval() const; /** Sets custom auto fetch interval. @param interval interval in minutes, -1 for disabling auto fetching */ void setFetchInterval(int interval); /** returns the archiving mode which is used for this feed */ ArchiveMode archiveMode() const; /** sets the archiving mode for this feed */ void setArchiveMode(ArchiveMode archiveMode); /** returns the maximum age of articles used for expiration by age (used in @c limitArticleAge archive mode) @return expiry age in days */ int maxArticleAge() const; /** sets the maximum age of articles used for expiration by age (used in @c limitArticleAge archive mode) @param maxArticleAge expiry age in days */ void setMaxArticleAge(int maxArticleAge); /** returns the article count limit used in @c limitArticleNumber archive mode **/ int maxArticleNumber() const; /** sets the article count limit used in @c limitArticleNumber archive mode **/ void setMaxArticleNumber(int maxArticleNumber); /** if @c true, new articles are marked immediately as read instead of new/unread. Useful for high-traffic feeds. */ bool markImmediatelyAsRead() const; void setMarkImmediatelyAsRead(bool enabled); void setUseNotification(bool enabled); bool useNotification() const; /** if true, the linked URL is loaded directly in the article viewer instead of showing the description */ void setLoadLinkedWebsite(bool enabled); bool loadLinkedWebsite() const; /** returns the feed image */ QPixmap image() const; /** sets the feed image */ void setImage(const QPixmap &p); /** returns the url of the actual feed source (rss/rdf/atom file) */ QString xmlUrl() const; /** sets the url of the actual feed source (rss/rdf/atom file) */ void setXmlUrl(const QString &s); /** returns the URL of the HTML page of this feed */ QString htmlUrl() const; /** sets the URL of the HTML page of this feed */ void setHtmlUrl(const QString &s); /** returns the description of this feed */ QString description() const; /** sets the description of this feed */ void setDescription(const QString &s); /** returns article by guid * @param guid the guid of the article to be returned * @return the article object with the given guid, or a * null article if non-existent */ Article findArticle(const QString &guid) const; /** returns whether a fetch error has occurred */ bool fetchErrorOccurred() const; Syndication::ErrorCode fetchErrorCode() const; /** returns the unread count for this feed */ int unread() const override; /** returns the number of total articles in this feed @return number of articles */ int totalCount() const override; /** returns if the article archive of this feed is loaded */ bool isArticlesLoaded() const; /** returns if this node is a feed group (@c false here) */ bool isGroup() const override { return false; } //impl bool isAggregation() const override { return false; } /** returns the next node in the tree. Calling next() unless it returns 0 iterates through the tree in pre-order */ const TreeNode *next() const override; TreeNode *next() override; //impl QIcon icon() const override; /** deletes expired articles */ void deleteExpiredArticles(Akregator::ArticleDeleteJob *job); bool isFetching() const; QVector feeds() const override; QVector feeds() override; QVector folders() const override; QVector folders() override; KJob *createMarkAsReadJob() override; public Q_SLOTS: /** starts fetching */ void fetch(bool followDiscovery = false); void slotAbortFetch(); /** add this feed to the fetch queue @c queue */ void slotAddToFetchQueue(Akregator::FetchQueue *queue, bool intervalFetchOnly = false) override; void slotAddFeedIconListener(); Q_SIGNALS: /** emitted when fetching started */ void fetchStarted(Akregator::Feed *); /** emitted when feed finished fetching */ void fetched(Akregator::Feed *); /** emitted when a fetch error occurred */ void fetchError(Akregator::Feed *); /** emitted when a feed URL was found by auto discovery */ void fetchDiscovery(Akregator::Feed *); /** emitted when a fetch is aborted */ void fetchAborted(Akregator::Feed *); private: Akregator::Backend::Storage *storage(); private: void setFavicon(const QIcon &icon); void loadFavicon(const QUrl &url); QVector
articles() override; /** loads articles from archive **/ void loadArticles(); void enforceLimitArticleNumber(); void recalcUnreadCount(); void doArticleNotification() override; /** sets the unread count for this feed */ void setUnread(int unread); /** notifies that article @c mya was set to "deleted". To be called by @ref Article */ void setArticleDeleted(Article &a); /** Notifies that article @p a was changed. @param oldStatus The old status if the status was changed, or -1 if the status was not changed @param process Set to @c false to disable processing the change (updating article list and updating on-screen unread count) To be called by @ref Article */ void setArticleChanged(Article &a, int oldStatus = -1, bool process = true); void appendArticles(const Syndication::FeedPtr &feed); /** appends article @c a to the article list */ void appendArticle(const Article &a); /** checks whether article @c a is expired (considering custom and global archive mode settings) */ bool isExpired(const Article &a) const; /** returns @c true if either this article uses @c limitArticleAge as custom setting or uses the global default, which is @c limitArticleAge */ bool usesExpiryByAge() const; /** executes the actual fetch action */ void tryFetch(); void markAsFetchedNow(); private Q_SLOTS: void fetchCompleted(Syndication::Loader *loader, Syndication::FeedPtr doc, Syndication::ErrorCode errorCode); void slotImageFetched(const QPixmap &image); private: class Private; Private *d; }; } // namespace Akregator #endif // AKREGATOR_FEED_H