Browse DevX
Sign up for e-mail newsletters from DevX


Back to Basics: How to Manage Collections in Your Legacy Brew Applications : Page 3

Tired of managing collections by hand in your legacy Brew development? Then try using a more general approach, such as a vector or dictionary wrapped in a reusable Brew interface.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Deleting Elements
There are three ways to delete elements:
  • Replace an existing element with NULL by calling IVECTOR_ReplaceAt. This does not affect the position of any other item in the vector.
  • Delete an existing item by calling IVECTOR_Delete. This moves every item after the item you're deleting back one position after freeing the item you delete.
  • Delete all existing items by calling IVECTOR_DeleteAll, which releases each item in the vector and releases the vector's element store at pElements.

  • IVECTOR_Delete is the most interesting of the methods, as you see here:

    static int Vector_Delete( IVector *p, uint32 index ) { CVector *pMe = VECTOR_FROM_INTERFACE( p ); if ( !pMe ) return EBADPARM; if ( index > pMe->length ) return ENOSUCH; if ( pMe->pElements[ index ] ) (pMe->pfnDeallocator)( pMe->pElements[ index ] ); MEMMOVE( &pMe->pElements[ index ], &pMe->pElements[ index + 1 ], ( pMe->length - index - 1 ) * sizeof( void * ) ); pMe->length--; return SUCCESS; }

    The routine opens simply enough, validating the incoming index, and if invalid, returning an error. Next the code calls the registered deallocator on the indicated element. Once that's done, it uses MEMMOVE to slide each element after the deleted element down one, covering the deleted element. Finally, the length of the vector is decremented to reflect the change in indexes.

    I could implement IVECTOR_DeleteAll with successive calls to IVECTOR_Delete with an index of zero, but of course it's far more efficient to move the loop to within IVECTOR_DeleteAll and skip the MEMMOVE operation entirely:

    static int Vector_DeleteAll( IVector *p ) { CVector *pMe = VECTOR_FROM_INTERFACE( p ); uint32 i; if ( !pMe ) return EBADPARM; for ( i = 0; i < pMe->length; i++ ) { if ( pMe->pElements[ i ] ) (pMe->pfnDeallocator)(pMe->pElements[ i ] ); } FREEIF( pMe->pElements ); pMe->capacity = 0; pMe->length = 0; return SUCCESS; }

    Accessing Elements
    There are three ways to access elements in the vector: one at a time using IVECTOR_Get, through the enumeration interface, or through the comprehension interface. As all boil down to a dereference of an element of pElement, let's look at IVECTOR_Get first:

    static int Vector_Get( IVector *p, uint32 index, void **ppObject ) { CVector *pMe = VECTOR_FROM_INTERFACE( p ); if ( !p || !ppObject ) return EBADPARM; if ( index < pMe->length ) { *ppObject = pMe->pElements[ index ]; return SUCCESS; } else { return ENOSUCH; } }

    This is straightforward: do some argument checking, validate the incoming index, and return either a pointer to the indexth item of the vector or an error on failure.

    The enumeration methods are trivial as well; I encourage you to look at the implementation if you can’t see how it would work. The IVECTOR_Comprehend method is a little trickier, because it uses a loop and a function pointer to determine what should happen to each element, and then a second function pointer to actually do something to the elements that meet the match criteria:

    static int Vector_Comprehend( IVector *p, PFNMAPITEMTEST pfnTestItem, PFNMAPITEM pfnEachItem, void *pCtx ) { CVector *pMe = VECTOR_FROM_INTERFACE( p ); uint32 cursor; if ( !pMe ) return EFAILED; if ( !pfnEachItem ) return EBADPARM; for ( cursor = 0; cursor < pMe->length; cursor++ ) { if ( !pfnTestItem || (pfnTestItem)( pCtx, &cursor, pMe->pElements[ cursor ] ) ) { (pfnEachItem)( pCtx, &cursor, pMe->pElements[ cursor ] ); } } return SUCCESS; }

    The code begins with the obligatory error checking, and then simply loops over each item in the vector. If there's no match function, the code assumes that the iteration should match each item in the vector; if there's a match function, the match function is invoked and the each function is invoked only if the match function returns true.

    It should be obvious—convince yourself if it isn't!—that you can write IVECTOR_ForEach in terms of IVECTOR_Comprehend. I do this in the header file, defining IVECTOR_ForEach with a NULL item test function.

    Versatility Means Freedom
    Brew's component oriented approach lends itself well to the construction of big components, such as media codecs or GUI widgets. It's just as useful, though, for the small things, like collections, that you're apt to just dash off again and again. Why not just write it once and use it over and over again?

Ray Rischpater is the chief architect at Rocket Mobile, Inc., specializing in the design and development of messaging and information access applications for today's wireless devices. Ray is the author of several books on software development including "eBay Application Development" and "Software Development for the QUALCOMM BREW Platform," both available from Apress, and is an active amateur radio operator.
Thanks for your registration, follow us on our social networks to keep up-to-date