Merge pull request #10667 from chennes/toponamingHasherToDocument

App/Toponaming: Add StringHasher to Document
This commit is contained in:
Chris Hennes
2023-09-20 13:31:02 -05:00
committed by GitHub
8 changed files with 224 additions and 2 deletions

View File

@@ -64,6 +64,7 @@ recompute path. Also, it enables more complicated dependencies beyond trees.
#endif
#include <boost/algorithm/string.hpp>
#include <boost/bimap.hpp>
#include <boost/graph/strong_components.hpp>
#ifdef USE_OLD_DAG
@@ -102,6 +103,7 @@ recompute path. Also, it enables more complicated dependencies beyond trees.
#include "License.h"
#include "Link.h"
#include "MergeDocuments.h"
#include "StringHasher.h"
#include "Transactions.h"
#ifdef _MSC_VER
@@ -136,6 +138,7 @@ static bool globalIsRelabeling;
DocumentP::DocumentP()
{
Hasher = new StringHasher;
static std::random_device _RD;
static std::mt19937 _RGEN(_RD());
static std::uniform_int_distribution<> _RDIST(0, 5000);
@@ -921,11 +924,27 @@ std::string Document::getTransientDirectoryName(const std::string& uuid, const s
void Document::Save (Base::Writer &writer) const
{
d->hashers.clear();
addStringHasher(d->Hasher);
writer.Stream() << R"(<Document SchemaVersion="4" ProgramVersion=")"
<< App::Application::Config()["BuildVersionMajor"] << "."
<< App::Application::Config()["BuildVersionMinor"] << "R"
<< App::Application::Config()["BuildRevision"]
<< "\" FileVersion=\"" << writer.getFileVersion() << "\">" << endl;
<< "\" FileVersion=\"" << writer.getFileVersion()
<< "\" StringHasher=\"1\">\n";
writer.incInd();
d->Hasher->setPersistenceFileName("StringHasher.Table");
for (auto o : d->objectArray) {
o->beforeSave();
}
beforeSave();
d->Hasher->Save(writer);
writer.decInd();
PropertyContainer::Save(writer);
@@ -937,7 +956,9 @@ void Document::Save (Base::Writer &writer) const
void Document::Restore(Base::XMLReader &reader)
{
int i,Cnt;
d->hashers.clear();
d->touchedObjs.clear();
addStringHasher(d->Hasher);
setStatus(Document::PartialDoc,false);
reader.readElement("Document");
@@ -954,6 +975,12 @@ void Document::Restore(Base::XMLReader &reader)
reader.FileVersion = 0;
}
if (reader.hasAttribute("StringHasher")) {
d->Hasher->Restore(reader);
} else {
d->Hasher->clear();
}
// When this document was created the FileName and Label properties
// were set to the absolute path or file name, respectively. To save
// the document to the file it was loaded from or to show the file name
@@ -1018,6 +1045,30 @@ void Document::Restore(Base::XMLReader &reader)
reader.readEndElement("Document");
}
std::pair<bool,int> Document::addStringHasher(const StringHasherRef & hasher) const {
if (!hasher)
return std::make_pair(false, 0);
auto ret = d->hashers.left.insert(HasherMap::left_map::value_type(hasher,(int)d->hashers.size()));
if (ret.second)
hasher->clearMarks();
return std::make_pair(ret.second,ret.first->second);
}
StringHasherRef Document::getStringHasher(int idx) const {
if(idx<0) {
return d->Hasher;
return d->Hasher;
}
StringHasherRef hasher;
auto it = d->hashers.right.find(idx);
if(it == d->hashers.right.end()) {
hasher = new StringHasher;
d->hashers.right.insert(HasherMap::right_map::value_type(idx,hasher));
}else
hasher = it->second;
return hasher;
}
struct DocExportStatus {
Document::ExportStatus status;
std::set<const App::DocumentObject*> objs;
@@ -1054,6 +1105,7 @@ Document::ExportStatus Document::isExporting(const App::DocumentObject *obj) con
void Document::exportObjects(const std::vector<App::DocumentObject*>& obj, std::ostream& out) {
DocumentExporting exporting(obj);
d->hashers.clear();
if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
for(auto o : obj) {
@@ -1090,6 +1142,7 @@ void Document::exportObjects(const std::vector<App::DocumentObject*>& obj, std::
// write additional files
writer.writeFiles();
d->hashers.clear();
}
#define FC_ATTR_DEPENDENCIES "Dependencies"
@@ -1401,6 +1454,7 @@ void Document::addRecomputeObject(DocumentObject *obj) {
std::vector<App::DocumentObject*>
Document::importObjects(Base::XMLReader& reader)
{
d->hashers.clear();
Base::FlagToggler<> flag(globalIsRestoring, false);
Base::ObjectStatusLocker<Status, Document> restoreBit(Status::Restoring, this);
Base::ObjectStatusLocker<Status, Document> restoreBit2(Status::Importing, this);
@@ -1452,6 +1506,7 @@ Document::importObjects(Base::XMLReader& reader)
o->setStatus(App::ObjImporting,false);
}
d->hashers.clear();
return objs;
}
@@ -1464,6 +1519,8 @@ unsigned int Document::getMemSize () const
for (it = d->objectArray.begin(); it != d->objectArray.end(); ++it)
size += (*it)->getMemSize();
size += d->Hasher->getMemSize();
// size of the document properties...
size += PropertyContainer::getMemSize();

View File

@@ -23,6 +23,12 @@
#ifndef APP_DOCUMENT_H
#define APP_DOCUMENT_H
#include <CXX/Objects.hxx>
#include <Base/Observer.h>
#include <Base/Persistence.h>
#include <Base/Type.h>
#include <Base/Handle.h>
#include "PropertyContainer.h"
#include "PropertyLinks.h"
#include "PropertyStandard.h"
@@ -44,6 +50,8 @@ namespace App
class DocumentPy; // the python document class
class Application;
class Transaction;
class StringHasher;
using StringHasherRef = Base::Reference<StringHasher>;
}
namespace App
@@ -465,6 +473,33 @@ public:
(const App::DocumentObject* from, const App::DocumentObject* to) const;
//@}
/** Called by a property during save to store its StringHasher
*
* @param hasher: the input hasher
* @return Returns a pair<bool,int>. The boolean indicates if the
* StringHasher has been added before. The integer is the hasher index.
*
* The StringHasher object is designed to be shared among multiple objects.
* We must not save duplicate copies of the same hasher, and must be
* able to restore with the same sharing relationship. This function returns
* whether the hasher has been added before by other objects, and the index
* of the hasher. If the hasher has not been added before, the object must
* save the hasher by calling StringHasher::Save
*/
std::pair<bool,int> addStringHasher(const StringHasherRef & hasher) const;
/** Called by property to restore its StringHasher
*
* @param index: the index previously returned by calling addStringHasher()
* during save. Or if is negative, then return document's own string hasher.
*
* @return Return the resulting string hasher.
*
* The caller is responsible for restoring the hasher if the caller is the first
* owner of the hasher, i.e. if addStringHasher() returns true during save.
*/
StringHasherRef getStringHasher(int index=-1) const;
/** Return the links to a given object
*
* @param links: holds the links found

View File

@@ -261,6 +261,8 @@ public:
*/
int64_t getID() const {return _id;}
virtual void beforeSave() const {}
friend class PropertyContainer;
friend struct PropertyData;
friend class DynamicProperty;

View File

@@ -219,6 +219,23 @@ void PropertyContainer::handleChangedPropertyType(XMLReader &reader, const char
PropertyData PropertyContainer::propertyData;
void PropertyContainer::beforeSave() const
{
std::map<std::string, Property*> Map;
getPropertyMap(Map);
for (auto& entry : Map) {
auto prop = entry.second;
if (!prop->testStatus(Property::PropDynamic)
&& (prop->testStatus(Property::Transient)
|| ((getPropertyType(prop) & Prop_Transient) != 0))) {
// Nothing
}
else {
prop->beforeSave();
}
}
}
void PropertyContainer::Save (Base::Writer &writer) const
{
std::map<std::string,Property*> Map;
@@ -237,8 +254,9 @@ void PropertyContainer::Save (Base::Writer &writer) const
{
transients.push_back(prop);
it = Map.erase(it);
}else
} else {
++it;
}
}
writer.incInd(); // indentation for 'Properties Count'

View File

@@ -220,6 +220,7 @@ public:
void Save (Base::Writer &writer) const override;
void Restore(Base::XMLReader &reader) override;
virtual void beforeSave() const;
virtual void editProperty(const char * /*propName*/) {}

View File

@@ -25,11 +25,14 @@
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <App/StringHasher.h>
#include <CXX/Objects.hxx>
#include <boost/bimap.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <unordered_map>
#include <unordered_set>
// using VertexProperty = boost::property<boost::vertex_root_t, DocumentObject* >;
using DependencyList = boost::adjacency_list <
boost::vecS, // class OutEdgeListS : a Sequence or an AssociativeContainer
@@ -47,6 +50,7 @@ using Node = std::vector <size_t>;
using Path = std::vector <size_t>;
namespace App {
using HasherMap = boost::bimap<StringHasherRef, int>;
class Transaction;
// Pimpl class
@@ -74,6 +78,7 @@ struct DocumentP
unsigned int UndoMemSize;
unsigned int UndoMaxStackSize;
std::string programVersion;
mutable HasherMap hashers;
#ifdef USE_OLD_DAG
DependencyList DepList;
std::map<DocumentObject*, Vertex> VertexObjectList;
@@ -82,6 +87,8 @@ struct DocumentP
std::multimap<const App::DocumentObject*,
std::unique_ptr<App::DocumentObjectExecReturn> > _RecomputeLog;
StringHasherRef Hasher;
DocumentP();
void addRecomputeLog(const char *why, App::DocumentObject *obj) {

View File

@@ -4,6 +4,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/Application.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Branding.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ComplexGeoData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Document.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Expression.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ElementMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IndexedName.cpp

101
tests/src/App/Document.cpp Normal file
View File

@@ -0,0 +1,101 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "gtest/gtest.h"
#include <gmock/gmock.h>
#include "App/Application.h"
#include "App/Document.h"
#include "App/StringHasher.h"
#include "Base/Writer.h"
using ::testing::Eq;
using ::testing::Ne;
// NOLINTBEGIN(readability-magic-numbers)
class FakeWriter: public Base::Writer
{
void writeFiles() override
{}
std::ostream& Stream() override
{
return std::cout;
}
};
class DocumentTest: public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
if (App::Application::GetARGC() == 0) {
constexpr int argc = 1;
std::array<char*, argc> argv {"FreeCAD"};
App::Application::Config()["ExeName"] = "FreeCAD";
App::Application::init(argc, const_cast<char**>(argv.data())); // NOLINT
}
}
void SetUp() override
{
_docName = App::GetApplication().getUniqueDocumentName("test");
_doc = App::GetApplication().newDocument(_docName.c_str(), "testUser");
}
void TearDown() override
{
App::GetApplication().closeDocument(_docName.c_str());
}
App::Document* doc()
{
return _doc;
}
private:
std::string _docName;
App::Document* _doc {};
};
TEST_F(DocumentTest, addStringHasherIndicatesUnwrittenWhenNew)
{
// Arrange
App::StringHasherRef hasher(new App::StringHasher);
// Act
auto addResult = doc()->addStringHasher(hasher);
// Assert
EXPECT_TRUE(addResult.first);
EXPECT_THAT(addResult.second, Ne(-1));
}
TEST_F(DocumentTest, addStringHasherIndicatesAlreadyWritten)
{
// Arrange
App::StringHasherRef hasher(new App::StringHasher);
doc()->addStringHasher(hasher);
// Act
auto addResult = doc()->addStringHasher(hasher);
// Assert
EXPECT_FALSE(addResult.first);
}
TEST_F(DocumentTest, getStringHasherGivesExpectedHasher)
{
// Arrange
App::StringHasherRef hasher(new App::StringHasher);
auto pair = doc()->addStringHasher(hasher);
int index = pair.second;
// Act
auto foundHasher = doc()->getStringHasher(index);
// Assert
EXPECT_EQ(hasher, foundHasher);
}
// NOLINTEND(readability-magic-numbers)