When implementing your own data model object, you should make use of the standard C++ base classes that are available. These classes are provided for two main reasons:
There are a wide variety of classes available, so it can be daunting to figure out where to start. In general, they are designed as layers of increasingly higher-level functionality for the three core data model interfaces, INode, IIterable, and IDatum.
This document will start with the highest-level classes, and work down to the simpler ones. This should allow you to go down through the available classes until you find one that will do what you want, and stop there. Remember that you should always use one of these classes to implement the INode, IIterable, or IDatum interfaces; as a very last resort, that will be BGenericNode, BGenericIterable, and BGenericDatum, respectively.
In general, these classes provide an implementation of the Binder Data Model interfaces on top of some other data structure that you will provide. Providing the data structure means implementing a subclass of the desired data model class and filling in the appropriate virtuals to give it access to your data.
The implementation of the data model classes and the underlying data they access must be, however, closely related. In particular, there are many cases where the implementation will need to do a series of operations during which it is assured that the back-end data remains in a consistent state. Because of this requirement, the locking approach used for these classes is very different than what you will see elsewhere.
Almost all of the data model classes explicitly expose their internal locking, so that it can be synchronized with the back-end data. This takes the form of a couple public virtuals to explicitly lock and unlock the object, and a convention for users to know when that lock is being held.
In some cases, a method without the "Locked" suffix may be safe to call with or without the lock held; this situation will be documented. It is also, of course, safe to call the SAtom, BBinder, and other standard Binder APIs with or without the lock being held, since these use the standard Threading Conventions model.
For example, compare BGenericNode::SetMimeType() to BGenericNode::SetMimeTypeLocked().
The Lock() methods return a lock_status_t result, making them convenient to use with the SAutolock class, like so:
void set_catalog_mime_type(const sptr<BCatalog>& catalog) { SAutolock _l(catalog->Lock()); catalog->StoreMimeTypeLocked(mime_type); }
When mixing two classes together that have their own locks, you will usually need to re-implement their Lock() and Unlock() methods so that they share the same lock:
class NodeAndIterable : public BMetaDataNode, public BIndexedIterable { public: virtual lock_status_t Lock() const { return BMetaDataNode::Lock(); } virtual void Unlock() const { return BMetaDataNode::Unlock(); } };
This goes along with the standard mixing of the IBinder::Inspect() method that is done when mixing together multiple interfaces.
#include <support/Package.h> #include <storage/StructuredNode.h> #include <storage/SchemaDatabaseNode.h> #include <storage/SchemaRowIDJoin.h> #include <support/Autolock.h> #include <support/Iterator.h> #include <support/Node.h> #include <DataMgr.h> #include <Loader.h> #include <SchemaDatabases.h> // Top-level content provider object, containing sub-directories // to access the various tables of information. class AddressContentProvider : public BStructuredNode, public SPackageSptr { public: AddressContentProvider(const SContext& context, const SValue& args); void InitAtom(); virtual SValue ValueAtLocked(size_t index) const; private: sptr<BSchemaDatabaseNode> m_database; sptr<BSchemaTableNode> m_people; sptr<BSchemaTableNode> m_phones; sptr<BSchemaTableNode> m_extras; sptr<BSchemaRowIDJoin> m_phonesDir; sptr<BSchemaRowIDJoin> m_extrasDir; }; // --------------------------------------------------------------- // Various constants we will use elsewhere. B_STATIC_STRING_VALUE_LARGE(kPersonListMimeType, "application/vnd.palm.personlist" ,); B_STATIC_STRING_VALUE_LARGE(kPersonItemMimeType, "application/vnd.palm.personitem" ,); B_STATIC_STRING_VALUE_LARGE(kPhoneListMimeType, "application/vnd.palm.phonelist" ,); B_STATIC_STRING_VALUE_LARGE(kPhoneItemMimeType, "application/vnd.palm.phoneitem" ,); B_STATIC_STRING_VALUE_LARGE(kPersonExtrasListMimeType, "application/vnd.palm.personextraslist" ,); B_STATIC_STRING_VALUE_LARGE(kPersonExtrasItemMimeType, "application/vnd.palm.personextrasitem" ,); B_CONST_STRING_VALUE_LARGE(kAddressDBName, "AddressDBSNu" ,); B_CONST_STRING_VALUE_LARGE(kAddressMainTableName, "AddressBookMain" ,); B_CONST_STRING_VALUE_LARGE(kAddressPhoneTableName, "AddressBookPhone" ,); B_CONST_STRING_VALUE_LARGE(kAddressExtraTableName, "AddressBookExtra" ,); // --------------------------------------------------------------- // This class is used to implement a custom database column // based on the "FirstName" and "LastName" columns. const char* displayColumns[] = { "FirstName", "LastName", NULL }; class DisplayName : public BSchemaTableNode::CustomColumn, public SPackageSptr { public: DisplayName(const sptr<BSchemaTableNode>& table) : CustomColumn(table) { } virtual SValue ValueLocked(uint32_t columnOrRowID, const SValue* baseValues, const size_t* columnsToValues) const { SString result(baseValues[columnsToValues[1]].AsString()); const SString first(baseValues[columnsToValues[0]].AsString()); if (first != "") result += ", "; result += first; return SValue::String(result); } }; // --------------------------------------------------------------- // These are the top-level directories in the content provider. const char* kDirNames[] = { "people", "phones", "extras", "databases", }; enum { kPeopleDir = 0, kPhonesDir, kExtrasDir, kDatabasesDir, kDirCount }; AddressContentProvider::AddressContentProvider(const SContext& context, const SValue& args) : BStructuredNode(context, kDirNames, kDirCount) { } // Main initialization of content provider. void AddressContentProvider::InitAtom() { BStructuredNode::InitAtom(); // Try to open the database. If it doesn't exist, create it. SDatabase database(((const SString&)kAddressDBName).String(), 'add2', dmModeReadWrite, dbShareRead); if (database.StatusCheck() != errNone) { // Error opening database -- try to create it in case it doesn't exist. MemHandle memh; void * memp; DatabaseID dbid; status_t err = errNone; DmOpenRef appdb; SysGetModuleDatabase(SysGetRefNum(), NULL, &appdb); if ((memh = DmGetResource(appdb, 'scdb', SCHEMA_DEFINITION_ID)) == NULL) return; if ((memp = MemHandleLock(memh)) == NULL) { DmReleaseResource(memh); return; } if ((err = DmCreateDatabaseFromImage(memp, &dbid)) < errNone) { return; } MemHandleUnlock(memh); DmReleaseResource(memh); database = SDatabase(((const SString&)kAddressDBName).String(), 'add2', dmModeReadWrite, dbShareRead); } // Create the database node, and build up our content // provider's subdirectories from it. if (database.StatusCheck() == errNone) { // Create database object and retrieve its tables. m_database = new BSchemaDatabaseNode(Context(), database); m_database->Lock(); m_people = m_database->TableForLocked(kAddressMainTableName); m_phones = m_database->TableForLocked(kAddressPhoneTableName); m_extras = m_database->TableForLocked(kAddressExtraTableName); m_database->Unlock(); // Create a join between the people and phone number tables. if (m_people != NULL && m_phones != NULL) { m_phonesDir = new BSchemaRowIDJoin(Context(), m_people, m_phones, kPersonID, kperson); if (m_phonesDir != NULL) { SAutolock _l(m_phonesDir->Lock()); m_phonesDir->StoreMimeTypeLocked(kPhoneListMimeType); m_phonesDir->StoreRowMimeTypeLocked(kPhoneItemMimeType); } } // Create a join between the people and extra info tables. if (m_people != NULL && m_extras != NULL) { m_extrasDir = new BSchemaRowIDJoin(Context(), m_people, m_extras, kPersonID, kperson); if (m_emailsDir != NULL) { SAutolock _l(m_emailsDir->Lock()); m_extrasDir->StoreMimeTypeLocked(kPersonExtrasListMimeType); m_extrasDir->StoreRowMimeTypeLocked(kPersonExtrasItemMimeType); } } // Add a custom column to the people table and set its MIME type. if (m_people != NULL) { sptr<DisplayName> dname = new DisplayName(m_people); if (dname != NULL) dname->AttachColumn(SString("DisplayName"), displayColumns); SAutolock _l(m_people->Lock()); m_people->StoreMimeTypeLocked(kPersonListMimeType); m_people->StoreRowMimeTypeLocked(kPersonItemMimeType); } } } // Provide access to our sub-directories. SValue AddressContentProvider::ValueAtLocked(size_t index) const { switch (index) { case kPeopleDir: return SValue::Binder((BnNode*)m_people.ptr()); case kPhonesDir: return SValue::Binder((BnNode*)m_phonesDir.ptr()); case kExtrasDir: return SValue::Binder((BnNode*)m_extrasDir.ptr()); case kDatabasesDir: return SValue::Binder((BnNode*)m_database.ptr()); } return SValue::Undefined(); }