/* $NoKeywords: $ */ /* // // Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// */ #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif ON_VIRTUAL_OBJECT_IMPLEMENT(ON_UserData,ON_Object,"850324A7-050E-11d4-BFFA-0010830122F0"); ON_UserData::ON_UserData() : m_userdata_uuid(ON_nil_uuid), m_application_uuid(ON_nil_uuid), m_userdata_copycount(0), m_userdata_xform(ON_Xform::IdentityTransformation), m_userdata_owner(0), m_userdata_next(0) {} ON_UserData::ON_UserData(const ON_UserData& src) : ON_Object(src), m_userdata_uuid(src.m_userdata_uuid), m_application_uuid(src.m_application_uuid), m_userdata_copycount(src.m_userdata_copycount), m_userdata_xform(src.m_userdata_xform), m_userdata_owner(0), // do not copy owner m_userdata_next(0) // do not copy next { if ( m_userdata_copycount) { m_userdata_copycount++; if ( !m_userdata_copycount ) m_userdata_copycount = 1; } } //virtual bool ON_UserData::Archive() const { return false; } //virtual bool ON_UserData::WriteToArchive( const class ON_BinaryArchive& archive, const class ON_Object* parent_object ) const { return Archive() ? true : false; } // virtual bool ON_UserData::DeleteAfterWrite( const ON_BinaryArchive& archive, const ON_Object* parent_object ) const { return false; } // virtual bool ON_UserData::DeleteAfterRead( const ON_BinaryArchive& archive, ON_Object* parent_object ) const { return false; } //virtual bool ON_UserData::Transform(const ON_Xform& x ) { m_userdata_xform = x*m_userdata_xform; return true; } ON_UserData& ON_UserData::operator=(const ON_UserData& src) { // 16 January 2004 Dale Lear // Do not copy the m_userdata_uuid, m_application_uuid, // m_userdata_owner, or m_userdata_next values. // The m_userdata_uuid and m_application_uuid are // set when the class is constructed and should not be // changed. The m_userdata_owner and m_userdata_next // values are set when the user data is attached // to a parent object. if ( this != &src ) { ON_Object::operator=(src); m_userdata_copycount = src.m_userdata_copycount; m_userdata_xform = src.m_userdata_xform; if ( 0 != m_userdata_copycount ) { m_userdata_copycount++; if ( !m_userdata_copycount ) m_userdata_copycount = 1; } } return *this; } ON_UserData::~ON_UserData() { if ( 0 != m_userdata_owner ) { // remove this piece of user data from owner->m_userdata_list m_userdata_owner->DetachUserData(this); m_userdata_owner = 0; } } void ON_UserData::Dump( ON_TextLog& text_log ) const { text_log.Print("User Data:\n"); text_log.PushIndent(); // print class name and class uuid ON_Object::Dump(text_log); // developer's user data description ON_wString description; const_cast(this)->GetDescription(description); if ( description.IsEmpty() ) description = L"none"; const wchar_t* ws = static_cast< const wchar_t* >(description); text_log.Print("user data description: %ls\n",ws); text_log.Print("user data uuid: "); text_log.Print(m_userdata_uuid); text_log.Print("\n"); text_log.Print("user data copy count: %d\n",this->m_userdata_copycount); // archive setting text_log.Print("user data saved in 3dm archive: %s\n",Archive() ? "yes" : "no"); text_log.PopIndent(); } unsigned int ON_UserData::SizeOf() const { unsigned int sz = ON_Object::SizeOf(); sz += (sizeof(*this) - sizeof(ON_Object)); return sz; } bool ON_UserData::IsValid( ON_TextLog* text_log ) const { if ( 0 == ON_UuidCompare( &m_userdata_uuid, &ON_nil_uuid ) ) { if ( 0 != text_log ) { text_log->Print("invalid userdata - m_userdata_uuid = nil\n"); } return false; } if ( 0 == ON_UuidCompare( m_userdata_uuid, ON_UserData::ClassId()->Uuid() ) ) { if ( 0 != text_log ) { text_log->Print("invalid userdata - m_userdata_uuid in use. Use guidgen to get a unique id.\n"); } return false; } if ( Archive() && 0 == ON_UuidCompare( ClassId()->Uuid(), ON_UserData::ClassId()->Uuid() ) ) { // 8 January 2004 Dale Lear: // I added this test to help developers remember to use // the ON_DECLARE_OBJECT/ON_IMPLEMENT_OBJECT macros when // they create user data that gets archived. if ( 0 != text_log ) { text_log->Print("invalid userdata - classes derived from ON_UserData that get saved in 3dm archives must have a class id and name defined by ON_OBJECT_DECLARE/ON_OBJECT_IMPLEMENT.\n"); } return false; } return true; } ON_Object* ON_UserData::Owner() const { return m_userdata_owner; } ON_UserData* ON_UserData::Next() const { return m_userdata_next; } ON_UUID ON_UserData::UserDataClassUuid() const { const ON_ClassId* this_rtti = ClassId(); if (this_rtti == &ON_CLASS_RTTI(ON_UnknownUserData)) return ((ON_UnknownUserData*)this)->m_unknownclass_uuid; if (this_rtti == &ON_CLASS_RTTI(ON_ObsoleteUserData)) return ((ON_ObsoleteUserData*)this)->m_archive_class_uuid; return this_rtti->Uuid(); } bool ON_UserData::IsUnknownUserData() const { return (ClassId() == &ON_CLASS_RTTI(ON_UnknownUserData))?true:false; } bool ON_UserData::GetDescription( ON_wString& description ) { return true; } ///////////////////////////////////////////////////////////////// // // // ON_OBJECT_IMPLEMENT(ON_ObsoleteUserData, ON_UserData, "7C9305E5-947A-4E46-B102-8016849FABB1"); ON_ObsoleteUserData::ON_ObsoleteUserData() : m_archive_class_uuid(ON_nil_uuid) {} ON_ObsoleteUserData::~ON_ObsoleteUserData() {} ON_ObsoleteUserData::ON_ObsoleteUserData(const ON_ObsoleteUserData& src) : ON_UserData(src) , m_archive_class_uuid(ON_nil_uuid) {} ON_ObsoleteUserData& ON_ObsoleteUserData::operator=(const ON_ObsoleteUserData& src) { if (this != &src) { ON_UserData::operator=(src); m_archive_class_uuid = src.m_archive_class_uuid; } return *this; } // // // ///////////////////////////////////////////////////////////////// ON_OBJECT_IMPLEMENT(ON_UnknownUserData,ON_UserData,"850324A8-050E-11d4-BFFA-0010830122F0"); ON_UnknownUserData::ON_UnknownUserData() : m_unknownclass_uuid(ON_nil_uuid) , m_sizeof_buffer(0) , m_buffer(0) , m_3dm_version(0) , m_3dm_opennurbs_version_number(0) {} ON_UnknownUserData::ON_UnknownUserData(const ON_UnknownUserData& src) : ON_UserData(src) , m_unknownclass_uuid(ON_nil_uuid) , m_sizeof_buffer(0) , m_buffer(0) , m_3dm_version(0) , m_3dm_opennurbs_version_number(0) { if ( m_userdata_copycount > 0 && src.m_sizeof_buffer > 0 && src.m_buffer ) { // For most kinds of user data except ON_UnknownUserData, // m_userdata_uuid is set by the constructor and should not // be copied from src (which may be a derived class). However, // for ON_UnknownUserData, the value of m_userdata_uuid is // varies because it is set by the missing userdata class. // So it has to be copied here. m_userdata_uuid = src.m_userdata_uuid; m_unknownclass_uuid = src.m_unknownclass_uuid; m_sizeof_buffer = src.m_sizeof_buffer; m_buffer = onmemdup( src.m_buffer, src.m_sizeof_buffer); m_3dm_version = src.m_3dm_version; m_3dm_opennurbs_version_number = src.m_3dm_opennurbs_version_number; } } ON_UnknownUserData& ON_UnknownUserData::operator=(const ON_UnknownUserData& src) { if ( this != &src ) { m_sizeof_buffer = 0; if ( 0 != m_buffer ) { onfree(m_buffer); m_buffer = 0; } // ON_UserData::operator= handles setting m_userdata_copycount and // m_userdata_xform. ON_UserData::operator=(src); // For most kinds of user data except ON_UnknownUserData, // m_userdata_uuid and m_application_uuid are set by the // constructor and should not be altered by an operator=. // However, for ON_UnknownUserData, the value of m_userdata_uuid // and m_application_uuid vary because they are set by the // missing userdata class. So they have to be copied here. m_userdata_uuid = src.m_userdata_uuid; m_application_uuid = src.m_application_uuid; // fix added 26 January 2010 if ( m_userdata_copycount > 0 && src.m_sizeof_buffer > 0 && src.m_buffer ) { m_unknownclass_uuid = src.m_unknownclass_uuid; m_sizeof_buffer = src.m_sizeof_buffer; m_buffer = onmemdup( src.m_buffer, src.m_sizeof_buffer); m_3dm_version = src.m_3dm_version; m_3dm_opennurbs_version_number = src.m_3dm_opennurbs_version_number; } else { // The unknown user data is not supposed to copy m_userdata_uuid = ON_nil_uuid; m_unknownclass_uuid = ON_nil_uuid; m_sizeof_buffer = 0; m_buffer = 0; m_3dm_version = 0; m_3dm_opennurbs_version_number = 0; } } return *this; } ON_UnknownUserData::~ON_UnknownUserData() { if ( m_buffer ) onfree(m_buffer); } unsigned int ON_UnknownUserData::SizeOf() const { return ON_UserData::SizeOf() + (sizeof(ON_UnknownUserData)-sizeof(ON_UserData)) + m_sizeof_buffer; } bool ON_UnknownUserData::GetDescription( ON_wString& s ) { s = "Unknown user data. (Definition of class was not available when object was read.)"; return true; } bool ON_UnknownUserData::IsValid( ON_TextLog* text_log ) const { bool rc = ON_UserData::IsValid(text_log); // valid unknown user data must have something in it if (rc) rc = (m_sizeof_buffer>0); if (rc) rc = (m_buffer != nullptr); if (rc) { // the unknown class uuid cannot be nil if (ON_nil_uuid == m_unknownclass_uuid) rc = false; } if (rc) { // the unknown class uuid cannot be the ON_UnknownUserData class uuid const ON_UUID ON_UnknownUserData_classuuid = ON_CLASS_ID(ON_UnknownUserData); if ( m_unknownclass_uuid == ON_UnknownUserData_classuuid ) rc = false; } return rc; } void ON_UnknownUserData::Dump( ON_TextLog& dump ) const { ON_UserData::Dump(dump); dump.PushIndent(); dump.Print( "unknown class uuid: "); dump.Print( m_unknownclass_uuid ); dump.Print( "\n"); dump.Print( "Data size in 3dm archive: %d bytes\n",m_sizeof_buffer); dump.PopIndent(); } bool ON_UnknownUserData::Archive() const { // 22 January 2004 Dale Lear // Since unknown userdata is only created when // an archive is read, it has to be saved. return true; } bool ON_UnknownUserData::Write( ON_BinaryArchive& file ) const { return file.WriteByte(m_sizeof_buffer,m_buffer); } bool ON_UnknownUserData::Read( ON_BinaryArchive& file ) { m_buffer = onrealloc( m_buffer, m_sizeof_buffer ); m_3dm_version = file.Archive3dmVersion(); return file.ReadByte(m_sizeof_buffer,m_buffer); } class ON_UnknownUserDataArchive : public ON_BinaryArchive { // This class is used to define an ON_BinaryArchive that can be used // in ON_UnknownUserData::Convert() to initialize a ON_UserData class // from a memory buffer. public: ON_UnknownUserDataArchive( const ON_UnknownUserData& ); ~ON_UnknownUserDataArchive(); protected: // ON_BinaryArchive overrides ON__UINT64 Internal_CurrentPositionOverride() const override; bool Internal_SeekFromCurrentPositionOverride(int byte_offset) override; bool Internal_SeekToStartOverride() override; public: // ON_BinaryArchive overrides bool AtEnd() const override; protected: // ON_BinaryArchive overrides size_t Internal_ReadOverride( size_t, void* ) override; // return actual number of bytes read (like fread()) size_t Internal_WriteOverride( size_t, const void* ) override; bool Flush() override; private: ON_UnknownUserDataArchive(); size_t m_sizeof_buffer = 0; const unsigned char* m_buffer = nullptr; size_t m_buffer_position = 0; }; ON_UnknownUserDataArchive::ON_UnknownUserDataArchive(const ON_UnknownUserData& ud) : ON_BinaryArchive(ON::archive_mode::read3dm) { SetArchive3dmVersion(ud.m_3dm_version); m_sizeof_buffer = ud.m_sizeof_buffer; m_buffer = (const unsigned char*)ud.m_buffer; m_buffer_position = 0; } ON_UnknownUserDataArchive::~ON_UnknownUserDataArchive() { } ON__UINT64 ON_UnknownUserDataArchive::Internal_CurrentPositionOverride() const { return (ON__UINT64)m_buffer_position; } bool ON_UnknownUserDataArchive::Internal_SeekFromCurrentPositionOverride( int offset ) { bool rc = false; if ( offset >= 0 || m_buffer_position >= ((size_t)(-offset)) ) { size_t newpos = m_buffer_position + offset; if ( newpos < m_sizeof_buffer ) { m_buffer_position = newpos; rc = true; } } return rc; } bool ON_UnknownUserDataArchive::Internal_SeekToStartOverride() { m_buffer_position = 0; return true; } bool ON_UnknownUserDataArchive::AtEnd() const { return (m_buffer_position >= m_sizeof_buffer) ? true : false; } size_t ON_UnknownUserDataArchive::Internal_ReadOverride( size_t count, void* buffer ) { size_t maxcount = 0; if ( m_sizeof_buffer > m_buffer_position ) { maxcount = m_sizeof_buffer - m_buffer_position; } if ( count > maxcount ) { count = maxcount; } if ( count > 0 ) { memcpy( buffer, m_buffer+m_buffer_position, count ); m_buffer_position += count; } return count; } size_t ON_UnknownUserDataArchive::Internal_WriteOverride( size_t, const void* ) { // ON_UnknownUserDataArchive does not support Write() and Flush() return 0; } bool ON_UnknownUserDataArchive::Flush() { // ON_UnknownUserDataArchive does not support Write() and Flush() return false; } ON_UserData* ON_UnknownUserData::Convert() const { ON_UserData* ud = nullptr; if ( IsValid() ) { const ON_ClassId* pID = ON_ClassId::ClassId( m_unknownclass_uuid ); // if pID is nullptr, it means the definiton of the unknown user data // is still not available if ( pID ) { // The class definition has been dynamically loaded since the // user data was read from an archive. Use the class's Read() // to convert the buffer into a valid class ON_Object* pObject = pID->Create(); if ( pObject ) { ud = ON_UserData::Cast(pObject); if ( !ud ) delete pObject; else { // use class's Read() function to initialize class members from buffer ON_UnknownUserDataArchive file(*this); // copy values that would be set by reading the base class ud->m_userdata_copycount = m_userdata_copycount; ud->m_userdata_xform = m_userdata_xform; ud->Read(file); } } } } return ud; } unsigned int ON_UserDataHolder::MoveUserDataFrom( const ON_Object& source_object ) { PurgeUserData(); unsigned int item_count = MoveUserData(const_cast< ON_Object& >(source_object), ON_nil_uuid, ON_Object::UserDataConflictResolution::source_object,true); return item_count; } unsigned int ON_UserDataHolder::CopyUserDataFrom( const ON_Object& source_object, ON_UUID user_data_item_id ) { PurgeUserData(); unsigned int item_count = CopyUserData(source_object, user_data_item_id, ON_Object::UserDataConflictResolution::source_object); return item_count; } unsigned int ON_UserDataHolder::MoveUserDataTo( const ON_Object& destination_object, bool bAppend ) { if ( !bAppend ) const_cast< ON_Object& >(destination_object).PurgeUserData(); const ON_Object::UserDataConflictResolution userdata_conflict_resolution = bAppend ? ON_Object::UserDataConflictResolution::destination_object : ON_Object::UserDataConflictResolution::source_object; return MoveUserDataTo(destination_object,ON_nil_uuid,userdata_conflict_resolution); } unsigned int ON_UserDataHolder::MoveUserDataTo( const ON_Object& destination_object, ON_UUID user_data_item_id, ON_Object::UserDataConflictResolution userdata_conflict_resolution ) { unsigned int moved_count = const_cast< ON_Object& >(destination_object).MoveUserData(*this, user_data_item_id, userdata_conflict_resolution,true); return moved_count; } bool ON_UserDataHolder::IsValid( ON_TextLog* text_log ) const { return true; } ///////////////////////////////////////////////////////////////// // // Built in tool for attaching arbitrary string userdata // to any opennurbs object. The savvy user will probably // use XML in the string. ON_UserString::ON_UserString() { } ON_UserString::~ON_UserString() { } bool ON_UserString::Write(ON_BinaryArchive& archive) const { bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0); if (!rc) return false; for(;;) { rc = archive.WriteString(m_key); if (!rc) break; rc = archive.WriteString(m_string_value); if (!rc) break; break; } if ( !archive.EndWrite3dmChunk() ) rc = false; return rc; } bool ON_UserString::Read(ON_BinaryArchive& archive) { m_key.Empty(); m_string_value.Empty(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if (!rc) return false; for(;;) { rc = ( 1 == major_version ); if (!rc) break; rc = archive.ReadString(m_key); if (!rc) break; rc = archive.ReadString(m_string_value); if (!rc) break; break; } if ( !archive.EndRead3dmChunk() ) rc = false; return rc; } void ON_UserString::Dump(ON_TextLog& text_log) const { const wchar_t* ws = static_cast< const wchar_t* >(m_key); if ( !ws ) ws = L""; text_log.Print("Key: %ls\n", ws); ws = static_cast< const wchar_t* >(m_string_value); if ( !ws ) ws = L""; text_log.Print("Value: %ls\n",ws); } ON_OBJECT_IMPLEMENT(ON_UserStringList,ON_UserData,"CE28DE29-F4C5-4faa-A50A-C3A6849B6329"); ON_UserStringList::ON_UserStringList() { m_userdata_uuid = ON_CLASS_ID(ON_UserStringList); m_application_uuid = ON_opennurbs4_id; // opennurbs.dll reads/writes this userdata // The id must be the version 4 id because // V5 SaveAs V4 needs to work. m_userdata_copycount = 1; } ON_UserStringList::~ON_UserStringList() { } bool ON_UserStringList::GetDescription( ON_wString& description ) { description.Format(L"User text (%d entries)",m_e.Count()); return true; } bool ON_UserStringList::Archive() const { return true; } unsigned int ON_UserStringList::SizeOf() const { unsigned int sz = ON_UserData::SizeOf(); sz += sizeof(*this) - sizeof(ON_UserData); sz += m_e.SizeOfArray(); int i = m_e.Count(); while (i--) sz += m_e[i].m_string_value.Length()*sizeof(wchar_t); return sz; } ON__UINT32 ON_UserStringList::DataCRC(ON__UINT32 current_remainder) const { int count = m_e.Count(); for ( int i = 0; i < count; i++ ) { current_remainder = m_e[i].m_key.DataCRC(current_remainder); current_remainder = m_e[i].m_string_value.DataCRC(current_remainder); } return current_remainder; } void ON_UserStringList::Dump( ON_TextLog& text_log ) const { int i, count = m_e.Count(); text_log.Print("%d entries\n",count); text_log.PushIndent(); for ( i = 0; i < count; i++ ) { m_e[i].Dump(text_log); } text_log.PopIndent(); } bool ON_UserStringList::Write(ON_BinaryArchive& archive) const { bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0); if ( !rc ) return false; for(;;) { int count = m_e.Count(); rc = archive.WriteInt(count); if(!rc) break; for ( int i = 0; i < count && rc; i++ ) { rc = m_e[i].Write(archive); } if (!rc) break; break; } if ( !archive.EndWrite3dmChunk() ) rc = false; return rc; } bool ON_UserStringList::Read(ON_BinaryArchive& archive) { int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if ( !rc ) return false; for(;;) { rc = (1 == major_version); if (!rc) break; int count = 0; rc = archive.ReadInt(&count); if(!rc) break; for ( int i = 0; i < count; i++ ) { rc = m_e.AppendNew().Read(archive); if ( !rc) { m_e.Remove(); break; } } if (!rc) break; break; } if ( !archive.EndRead3dmChunk() ) rc = false; return rc; } bool ON_UserStringList::SetUserString( const wchar_t* key, const wchar_t* string_value ) { if ( !key || !key[0] ) return false; int i, count = m_e.Count(); for (i = 0; i < count; i++ ) { if ( !m_e[i].m_key.CompareOrdinal(key,true) ) { if ( string_value && string_value[0] ) { m_e[i].m_string_value = string_value; } else { m_e.Remove(i); } m_userdata_copycount++; return true; } } if ( string_value && string_value[0] ) { ON_UserString& e = m_e.AppendNew(); e.m_key = key; e.m_string_value = string_value; m_userdata_copycount++; return true; } return false; } bool ON_UserStringList::GetUserString( const wchar_t* key, ON_wString& string_value ) const { if ( key && key[0] ) { int i, count = m_e.Count(); for (i = 0; i < count; i++ ) { if ( !m_e[i].m_key.CompareOrdinal(key,true) ) { string_value = m_e[i].m_string_value; return true; } } } string_value = ON_wString::EmptyString; return false; } static int cmp_hash_2dex_ij(const void* a, const void* b) { const int* ai = (const int*)a; const int* bi = (const int*)b; // 26 January 2012 Dale Lear // Part of the fix for http://dev.mcneel.com/bugtrack/?q=97693 // // The "i" values are actually 32 bit hashes of a string // and are often large. The sign of (ai[0] - bi[0]) cannot // be used to compare ai[0] and bi[0] because integer // overflow occures with values that are large. // ////// NO! //////if ( 0 == (rc = ai[0] - bi[0]) ) ////// rc = ai[1] - bi[1]; //////return rc; if ( ai[0] < bi[0] ) return -1; if ( ai[0] > bi[0] ) return 1; if ( ai[1] < bi[1] ) return -1; if ( ai[1] > bi[1] ) return 1; return 0; } int ON_UserStringList::SetUserStrings( int count, const ON_UserString* us, bool bReplace ) { int added_count = 0; int i; if ( count <= 0 || 0 == us ) return 0; if ( 1 == count ) { // skip the hash table hoo haa if ( us[0].m_key.IsEmpty() ) return 0; for ( i = 0; i < m_e.Count(); i++ ) { if ( m_e[i].m_key.CompareOrdinal(us[0].m_key, true ) ) continue; if ( bReplace ) { if ( us[0].m_string_value.IsEmpty() ) m_e.Remove(i); else m_e[i] = us[0]; added_count++; } break; } return added_count; } size_t k0, k1; int count0 = m_e.Count(); size_t count0_plus_count = (size_t)(count0 + count); ON_2dex* hash = (ON_2dex*)onmalloc( (count0_plus_count + count)*sizeof(hash[0]) ); ON_2dex* hash1 = hash + (count0_plus_count); const ON_2dex* h; int deleted_count = 0; for ( i = 0; i < count0; i++ ) { hash[i].i = (int)m_e[i].m_key.DataCRCLower(0); hash[i].j = i; } for ( i = 0; i < count; i++ ) { hash1[i].i = (int)us[i].m_key.DataCRCLower(0); hash1[i].j = i; hash[i+count0].i = hash1[i].i; hash[i+count0].j = hash1[i].j+count0; } ON_qsort(hash,count0_plus_count,sizeof(hash[0]),cmp_hash_2dex_ij); m_e.Reserve(count0+count); for ( i = 0; i < count; i++) { if ( us[i].m_key.IsEmpty() ) continue; // Set k0, k1 so that hash[k0]....,hash[k1-1] are // the hash[] entries keys with the same hash code // as us[i].m_key. h = ON_BinarySearch2dexArray(hash1[i].i,hash,count0_plus_count); if ( 0 == h ) { ON_ERROR("There is a bug in this function."); continue; } k0 = h-hash; while ( k0 > 0 && h[-1].i == h[0].i ) { // set h = first element in hash[] with this hash code. k0--; h--; } for (k1 = k0+1; k1 < count0_plus_count; k1++ ) { if ( hash[k1].i != hash[k0].i ) break; if ( hash[k1].j > i+count0 ) break; } if ( hash[k0].j >= count0 ) { // There are no entries in m_e[] with key matching hash, // so us[i].m_key is not present in m_e. if ( !us[i].m_string_value.IsEmpty() ) { hash[k0].j = count0++; m_e.Append(us[i]); added_count++; } continue; } for (/* empty init*/; k0 < k1; k0++ ) { if ( hash[k0].j < count0 ) { if ( m_e[hash[k0].j].m_key.CompareOrdinal(us[i].m_key,true) ) continue; // different keys with same hash if ( bReplace ) { m_e[hash[k0].j] = us[i]; added_count++; if ( us[i].m_string_value.IsEmpty() ) deleted_count++; } break; } } if ( k0 >= k1 ) { // hash is unique up to this point, so us[i].m_key is unique, // so we add it if it is valid. if ( !us[i].m_string_value.IsEmpty() ) { hash[k0].j = count0++; m_e.Append(us[i]); added_count++; } } } onfree(hash); // remove deleted items. i = m_e.Count(); while ( i-- > 0 && deleted_count > 0 ) { if ( m_e[i].m_string_value.IsEmpty() ) { m_e.Remove(i); deleted_count--; } } return added_count; } ON_UserStringList* ON_UserStringList::FromObject( const ON_Object* p ) { return p ? ON_UserStringList::Cast(p->GetUserData(ON_CLASS_ID(ON_UserStringList))) : 0; } bool ON_Object::SetUserString( const wchar_t* key, const wchar_t* string_value ) { ON_UserStringList* us = ON_UserStringList::FromObject(this); bool b = false; if ( nullptr == us ) { us = new ON_UserStringList(); if ( !AttachUserData(us) ) { delete us; us = nullptr; } else { b = true; } } if ( us ) { if ( us->SetUserString(key,string_value) ) { if ( b && 2 == us->m_userdata_copycount ) { // user data is brand new - roll back the // m_userdata_copycount++ that happens in // SetUserString(). us->m_userdata_copycount = 1; } b = true; } else if ( b ) { // user data was new-ed up and has nothing in it // because the input was bogus. delete us; us = nullptr; b = false; } } return b; } int ON_Object::SetUserStrings( int count, const ON_UserString* user_strings, bool bReplace ) { if ( 0 == count || 0 == user_strings ) return 0; int add_count = 0; int del_count = 0; for ( int i = 0; i < count; i++ ) { if ( user_strings[i].m_key.IsEmpty() ) continue; if ( user_strings[i].m_string_value.IsEmpty() ) del_count++; else add_count++; } if ( 0 == add_count && 0 == del_count ) return 0; ON_UserStringList* us = ON_UserStringList::FromObject(this); if ( !us && add_count > 0) { us = new ON_UserStringList(); if ( !AttachUserData(us) ) { delete us; us = 0; } } return us ? us->SetUserStrings(count,user_strings,bReplace ) : 0; } bool ON_Object::GetUserString( const wchar_t* key, ON_wString& string_value ) const { const ON_UserStringList* us = ON_UserStringList::FromObject(this); if (nullptr != us) return us->GetUserString(key,string_value); // because key and string_value might be the same, don't empty string_value until it is no longer needed. string_value = ON_wString::EmptyString; return false; } int ON_Object::UserStringCount() const { const ON_UserStringList* us = ON_UserStringList::FromObject(this); return ( 0 != us ) ? us->m_e.Count() : 0; } int ON_Object::GetUserStrings( ON_ClassArray& user_strings ) const { const int count0 = user_strings.Count(); const ON_UserStringList* us = ON_UserStringList::FromObject(this); if ( us ) user_strings.Append(us->m_e.Count(),us->m_e.Array()); return user_strings.Count() - count0; } int ON_Object::GetUserStringKeys( ON_ClassArray& user_string_keys ) const { const int count0 = user_string_keys.Count(); const ON_UserStringList* us = ON_UserStringList::FromObject(this); if ( us ) { user_string_keys.Reserve( count0 + us->m_e.Count() ); for (int i = 0; i < us->m_e.Count(); i++ ) { user_string_keys.Append(us->m_e[i].m_key); } } return user_string_keys.Count() - count0; } ON_OBJECT_IMPLEMENT(ON_DocumentUserStringList,ON_Object,"06F3218E-F5EC-4f6c-B74C-14583F0ED7BC"); ON_DocumentUserStringList::ON_DocumentUserStringList() { } ON_DocumentUserStringList::~ON_DocumentUserStringList() { } bool ON_DocumentUserStringList::IsValid( ON_TextLog* text_log ) const { return true; } void ON_DocumentUserStringList::Dump( ON_TextLog& ) const { } ON__UINT32 ON_DocumentUserStringList::DataCRC(ON__UINT32 current_remainder) const { const ON_UserStringList* us = ON_UserStringList::FromObject(this); if ( us ) current_remainder = us->DataCRC(current_remainder); return current_remainder; } bool ON_DocumentUserStringList::Write(ON_BinaryArchive& binary_archive) const { // The key/value pairs are saved as ON_UserStringList user data. // A single char with value 1 is written to permit adding additional // information later. In that case change the 1 to a 2, uncomment the // begin block code, and the IO will still work. // unsigned char c = 1; bool rc = binary_archive.WriteChar(c); ////rc = binary_archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0); ////if (!rc) //// return false; ////for(;;) ////{ //// rc = binary_archive.Write...(); //// if (!rc) break; //// break; ////} ////if ( !binary_archive.EndWrite3dmChunk() ) //// rc = false; return rc; } bool ON_DocumentUserStringList::Read(ON_BinaryArchive& binary_archive) { // The key/value pairs are saved as ON_UserStringList user data. unsigned char c = 0; bool rc = binary_archive.ReadChar(&c); if ( !rc || c < 1 || c > 2 ) return false; if ( 2 == c ) { // The code in this if(2==c) will not be used unless // the 1 in Write is changed to a 2. This code is here // so old versions of Rhino will be able to skip over // any new information that is added at a later date. int major_version = 0; int minor_version = 0; rc = binary_archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if (!rc) return false; for(;;) { rc = (1 == major_version); if (!rc) break; ////rc = binary_archive.Read(...); ////if (!rc) break; break; } if (!binary_archive.EndRead3dmChunk() ) rc = false; } return rc; }