blob: aeaa243b8540d6c453b2bfdd3610917b673d1bd0 [file] [log] [blame]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* etch_arraylist.c -- implementation of arraylist.
*/
#include "etch_arraylist.h"
#include "etch_mutex.h"
#include "etch_mem.h"
#include "etch_objecttypes.h"
extern etch_pool_t* g_etch_main_pool;
static int _destroy_arraylist(void*);
/**
* new_etch_arraylist()
* constructor for an etch_arraylist, which implements i_iterable.
* passed initial size, and initial expansion delta, both expressed as
* number of entries.
*/
etch_arraylist* new_etch_arraylist(const unsigned int initsize, const unsigned int deltsize)
{
etch_arraylist* list = (etch_arraylist*) new_object(sizeof(etch_arraylist), ETCHTYPEB_ARRAYLIST, CLASSID_ETCH_ARRAYLIST);
list->size = initsize > 0? initsize: ETCHARRAYLIST_DEFSIZE;
list->delta = deltsize > 0? deltsize: list->size;
list->size *= sizeof(void**); list->delta *= sizeof(void**);
list->base = etch_malloc(list->size, ETCHTYPEB_BYTES);
memset(list->base, 0, list->size);
((etch_object*)list)->destroy = _destroy_arraylist;
new_iterable(&list->iterable, NULL,
etch_arraylist_iterable_first,
etch_arraylist_iterable_next,
etch_arraylist_iterable_has_next);
return list;
}
/**
* new_arraylist_from()
*/
etch_arraylist* new_etch_arraylist_from(etch_arraylist* thatlist)
{
etch_arraylist* thislist = new_etch_arraylist(thatlist->size, thatlist->delta);
etch_arraylist_copyattrs(thislist, thatlist);
memcpy(thislist->base, thatlist->base, thatlist->count * sizeof(void*));
thislist->count = thatlist->count;
return thislist;
}
/**
* new_arraylist_synchronized()
* constructor for an etch_arraylist, which implements i_iterable.
* passed initial size, and initial expansion delta, both expressed as
* number of entries.
*/
etch_arraylist* new_etch_arraylist_synchronized(const unsigned int initialsize, const unsigned int deltasize)
{
etch_status_t status = ETCH_SUCCESS;
etch_arraylist* arraylist = NULL;
arraylist = new_etch_arraylist(initialsize, deltasize);
// TODO: pool
status = etch_mutex_create(&((etch_object*)arraylist)->synclock, ETCH_MUTEX_NESTED, NULL);
if(status != ETCH_SUCCESS) {
// errror
}
return arraylist;
}
/**
* new_etch_arraylist_synchronized_from()
*/
etch_arraylist* new_etch_arraylist_synchronized_from(etch_arraylist* thatlist)
{
etch_arraylist* thislist = new_etch_arraylist_synchronized(thatlist->size, thatlist->delta);
etch_arraylist_copyattrs(thislist, thatlist);
memcpy(thislist->base, thatlist->base, thatlist->count * sizeof(void*));
thislist->count = thatlist->count;
return thislist;
}
/**
* etcharraylist_destroy()
* destructor for an etch_arraylist.
* destroys the underlying list, the list shell, and the list content if requested.
* note that arraylist always owns its buffer and mutex. setting static content on
* an arraylist object applies only to the array content objects, if indeed content
* consists of object. if content is other than object, static content has no effect
* since the content buffer is freed regardless.
*/
void etch_arraylist_destroy(etch_arraylist* list, const int is_free_content)
{
etch_status_t status = ETCH_SUCCESS;
if(((etch_object*)list)->synclock != NULL) {
// disallow multiple destroy
status = etch_mutex_trylock(((etch_object*)list)->synclock);
if(status != ETCH_SUCCESS) {
return;
}
}
if (!is_etchobj_static_content(list)) /* clear and free content */
etch_arraylist_clear(list, is_free_content);
etch_free(list->base); /* arraylist always owns its buffer */
if(((etch_object*)list)->synclock) {
// release and free mutex
status = etch_mutex_unlock(((etch_object*)list)->synclock);
status = etch_mutex_destroy(((etch_object*)list)->synclock);
}
if (!is_etchobj_static_shell(list))
etch_free(list); /* free list object */
}
static int _destroy_arraylist(void* data)
{
etch_arraylist* list = data;
etch_arraylist_destroy(list, !list->is_readonly);
return 0;
}
/* - - - - - - - - - -
* list maintenance
* - - - - - - - - - -
*/
/**
* _arraylist_realloc()
* private method to resize an arraylist.
* if a size greater than current size is specified, the list is realloced to that size,
* otherwise it is realloced to current size plus the delta size specified at construction.
* @note presumed that this is invoked only via _arraylist_checksize, thus synchronized.
*/
int _arraylist_realloc(etch_arraylist* list, const unsigned size)
{
unsigned int copylength = 0, newsize = 0;
void* newbase = NULL;
newsize = size > list->size? size: list->size + list->delta;
newbase = etch_malloc(newsize, ETCHTYPEB_BYTES);
memset(newbase, 0, newsize);
copylength = list->count * sizeof(void**);
memcpy(newbase, list->base, copylength);
etch_free(list->base);
list->base = newbase;
list->size = newsize;
return 0;
}
/**
* _arraylist_checksize()
* private method ensures list has space required for pending content insertion.
*/
int _arraylist_checksize(etch_arraylist* list, const int pendingitems, const int is_locked)
{
etch_status_t status = ETCH_SUCCESS;
int result = 1;
unsigned currsize = 0, newspace = pendingitems * sizeof(void**);
if(((etch_object*)list)->synclock != NULL && !is_locked) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
currsize = list->count * sizeof(void**);
if (currsize + newspace > list->size)
_arraylist_realloc(list, newspace > list->delta? newspace: 0);
else
result = 0;
if(((etch_object*)list)->synclock != NULL && !is_locked) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* arraylist_clear()
* remove all content from the arraylist, freeing content memory only if requested.
* note that the list buffer does not shrink, rather retaining its current size.
*/
void etch_arraylist_clear(etch_arraylist* list, const int is_free_content)
{
etch_status_t status = ETCH_SUCCESS;
void** p = NULL;
arraycallback callback = NULL;
int items = 0, is_obj_content = 0, i = 0, freehandled = 0;
if (NULL == list || NULL == list->base) return;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
is_obj_content = list->content_type == ETCHARRAYLIST_CONTENT_OBJECT;
callback = list->freehook;
items = list->count;
p = list->base;
if (is_free_content && !list->is_readonly)
for(; i < (const int) items; i++, p++)
{
/* optional callback to handle free */
freehandled = callback? callback(i, *p): FALSE;
/* if we've marked the array as having etch object content,
* we invoke the object's destructor. */
if (freehandled);
else
if (is_obj_content)
((etch_object*)*p)->destroy(*p);
else etch_free(*p);
}
memset(list->base, 0, items * sizeof(void**));
list->count = 0;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
}
/* - - - - - - - - - -
* list api
* - - - - - - - - - -
*/
/*
* arraylist_add_from()
* add entries to the end of the list.
* returns count if items entered, or -1 if error.
* note that if the target array is not marked read only, this method must be
* used with great caution. since an arraylist not so marked will attempt to
* destroy its content when destroyed, and since we are copying memory references
* from one array to another here, the second destructor would attempt to free
* memory already freed in the first destructor. so to be safe, this method
* should be used only with arrays marked is_readonly = TRUE, and content_type =
* ETCHARRAYLIST_CONTENT_OBJECT.
*/
int etch_arraylist_add_from(etch_arraylist* list, etch_arraylist* newitems, etch_comparator compare)
{
etch_status_t status = ETCH_SUCCESS;
int count = 0;
etch_iterator iterator;
if (!list || !list->base) return -1;
if (newitems->count == 0) return 0;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
set_iterator(&iterator, newitems, &newitems->iterable);
while(iterator.has_next(&iterator))
{
if (etch_arraylist_contains(list, iterator.current_value, 0, compare))
continue;
_arraylist_checksize(list, 1, TRUE);
list->base[list->count++] = iterator.current_value;
count++;
iterator.next(&iterator);
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return count;
}
/*
* etch_arraylist_add()
* add an entry to the end of the list.
*/
int etch_arraylist_add(etch_arraylist* list, void* content)
{
etch_status_t status = ETCH_SUCCESS;
if (!list || !list->base) return -1;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
_arraylist_checksize(list, 1, TRUE);
list->base[list->count++] = content;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return 0;
}
/*
* etch_arraylist_insert()
* add a node anywhere in the list, returning 0 if OK, or -1 if error.
* this implementation cannot insert past the current end of list,
* in other words to insert at index n, there must be at least n
* entries currently in the list (list->count >= n), that is to say,
* the insertion must expand the size of the list by exactly one slot.
*/
int etch_arraylist_insert(etch_arraylist* list, const unsigned int i, void* content)
{
etch_status_t status = ETCH_SUCCESS;
unsigned shiftcount = 0, result = -1, j = 0;
void **p = NULL, **q = NULL;
if ((NULL == list) || (i < 0)) return -1;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
_arraylist_checksize(list, 1, TRUE);
if (i <= list->count)
{
shiftcount = list->count - i; /* shift higher entries up */
p = &list->base[list->count - 1];
q = &list->base[list->count];
for(j = 0; j < (const unsigned) shiftcount; j++, p--, q--)
*q = *p;
list->base[i] = content;
++list->count;
result = 0;
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* etch_arraylist_containsp()
* return 1 or 0 indicating if the list contains the supplied content pointer.
*/
int etch_arraylist_containsp(etch_arraylist* list, void* content, const unsigned startat)
{
return etch_arraylist_indexofp(list, content, startat) == -1? FALSE: TRUE;
}
/*
* etch_arraylist_count()
* return the number of elements or -1 if list is NULL
*/
int etch_arraylist_count(etch_arraylist* list)
{
if (NULL==list)
return -1;
else
return list->count;
}
/*
* etch_arraylist_indexofp()
* if the list contains the supplied content pointer, return its index;
* return -1 if not found or if a parameter was in error.
*/
int etch_arraylist_indexofp(etch_arraylist* list, void* content, const unsigned startat)
{
etch_status_t status = ETCH_SUCCESS;
void** p = NULL;
int result = -1;
unsigned int i = startat;
if ((NULL == list) || (i < 0)) return result;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (i < list->count)
{
p = &list->base[i];
for(; i < list->count; i++, p++)
{
if (*p == content)
{ result = i;
break;
}
}
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* arraylist_contains()
* return 1 or 0 indicating if the list contains the supplied content.
* caller must supply a comparator function int (*f)(void* this, void* that);
* which should return -2 bad params, -1 compares less, 0 equal, 1 greater.
*/
int etch_arraylist_contains(etch_arraylist* list, void* content, const unsigned startat,
etch_comparator compare)
{
return etch_arraylist_indexof(list, content, startat, compare) == -1? FALSE: TRUE;
}
/*
* etch_arraylist_indexof()
* if the list contains the supplied content, return its index.
* return -1 if a parameter was in error.
* caller must supply a comparator function int (*f)(void* this, void* that);
* which should return -2 bad params, -1 compares less, 0 equal, 1 greater.
*/
int etch_arraylist_indexof(etch_arraylist* list,
void* content, const unsigned startat, etch_comparator compare)
{
etch_status_t status = ETCH_SUCCESS;
void** p = NULL;
unsigned i = startat;
int result = -1;
if ((NULL == list) || (i < 0)) return result;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (i < list->count)
{
p = &list->base[i];
for(; i < list->count; i++, p++)
{
if (0 == compare(content, *p))
{ result = i;
break;
}
}
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* etch_arraylist_set()
* replace content at specified index position.
*/
int etch_arraylist_set (etch_arraylist* list, const unsigned i, void* content)
{
etch_status_t status = ETCH_SUCCESS;
int result = -1;
if ((NULL == list) || (i < 0)) return -1;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (i < list->count)
{
list->base[i] = content;
result = 0;
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* etch_arraylist_get()
* return content at specified index position, or NULL if parameter error.
* a non-disposable reference is returned.
* todo: mark arraylist content objects static, such that destroy() is benign,
* unlocking them only at such time as the arraylist itself is cleared or
* destroyed, or a remove() with is_free_content is requested.
*/
void* etch_arraylist_get (etch_arraylist* list, const unsigned i)
{
etch_status_t status = ETCH_SUCCESS;
void* result = NULL;
if ((NULL == list) || (i < 0)) return NULL;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (i < list->count)
result = list->base[i];
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* etch_arraylist_remove()
* remove entry at specified index position, freeing content memory if requested.
* return -1 if a parameter was in error, or zero if OK.
*/
int etch_arraylist_remove (etch_arraylist* list, const unsigned i, const int is_free_content)
{
etch_status_t status = ETCH_SUCCESS;
unsigned j = 0, newcount = 0, is_obj_content = 0, freehandled = 0, result = -1;
arraycallback callback = NULL;
void *content = NULL, **p = NULL, **q = NULL;
if ((NULL == list) || (i < 0)) return -1;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (i < list->count)
{
is_obj_content = list->content_type == ETCHARRAYLIST_CONTENT_OBJECT;
callback = list->freehook;
if (is_free_content && !list->is_readonly)
{
if (content = list->base[i])
{
freehandled = callback? callback(i, content): FALSE;
if (freehandled);
else
if (is_obj_content)
((etch_object*)content)->destroy(content);
else etch_free(content);
}
}
newcount = list->count - 1;
p = &list->base[i]; /* shift higher entries down */
q = &list->base[i + 1]; /* we eschew platform-dependent memmove() */
for(j = i; j < (const unsigned)newcount; j++, p++, q++)
*p = *q;
list->base[newcount] = NULL; /* zero the now-extra slot */
list->count = newcount;
result = 0;
}
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* arraylist_remove_content()
* remove entry whose value is specified.
*/
int etch_arraylist_remove_content(etch_arraylist* list,
void* content, const unsigned startat, etch_comparator compare)
{
etch_status_t status = ETCH_SUCCESS;
int result = -1, index = 0;
if (NULL == list) return result;
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (-1 != (index = etch_arraylist_indexof(list, content, startat, compare)))
result = etch_arraylist_remove(list, index, TRUE);
if(((etch_object*)list)->synclock != NULL) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/*
* etch_arraylist_copyattrs()
* copy array attributes from one array to another
*/
void etch_arraylist_copyattrs(etch_arraylist* to, etch_arraylist* from)
{
((etch_object*)to)->class_id = ((etch_object*)from)->class_id;
to->content_obj_type = from->content_obj_type;
to->content_class_id = from->content_class_id;
((etch_object*)to)->is_static = ((etch_object*)from)->is_static;
to->content_type = from->content_type;
to->is_readonly = from->is_readonly;
to->freehook = from->freehook;
}
/* - - - - - - - - - -
* iteration lock
* - - - - - - - - - -
*/
/*
* etch_arraylist_getlock()
* explicitly set this list's synchronization lock, waiting if unavailable.
* this should be used only for locking a list prior to iterating the list.
* for synchronization of list operations, the presence of list.synchook
* and list.synclock is sufficient.
*/
int etch_arraylist_getlock (etch_arraylist* list)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(list && ((etch_object*)list)->synclock);
if(((etch_object*)list)->synclock) {
status = etch_mutex_lock(((etch_object*)list)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/*
* etch_arraylist_trylock()
* explicitly set this list's synchronization lock, failing if unavailable.
* this should be used only for locking a list prior to iterating the list.
* for synchronization of list operations, the presence of list.synchook
* and list.synclock is sufficient.
*/
int etch_arraylist_trylock (etch_arraylist* list)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(list && ((etch_object*)list)->synclock);
if(((etch_object*)list)->synclock) {
status = etch_mutex_trylock(((etch_object*)list)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/*
* etch_arraylist_rellock()
* release explicitly set this list's synchronization lock.
* this should be used only for unlocking a list after iterating the list.
* for synchronization of list operations, the presence of list.synchook
* and list.synclock is sufficient.
*/
int etch_arraylist_rellock (etch_arraylist* list)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(list && ((etch_object*)list)->synclock);
if(((etch_object*)list)->synclock) {
status = etch_mutex_unlock(((etch_object*)list)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/* - - - - - - - - - -
* i_iterable
* - - - - - - - - - -
*/
/*
* etch_arraylist_iterable_first()
* i_iterable first() implementation
*/
int etch_arraylist_iterable_first(etch_iterator* iter)
{
etch_arraylist* list = NULL;
if (!iter || !iter->collection) return -1;
list = iter->collection;
if (!list->base || !list->count) return -1;
iter->current_value = etch_arraylist_get(list, 0);
iter->ordinal = iter->current_value? 1: 0;
return iter->ordinal? 0: -1;
}
/*
* etch_arraylist_iterable_next()
* i_iterable next() implementation
* functions as first() if there is no current position.
*/
int etch_arraylist_iterable_next(etch_iterator* iter)
{
etch_arraylist* list = iter? iter->collection: NULL;
const int count = list? list->count: 0;
if (!count || !iter->ordinal) return -1;
iter->current_value = etch_arraylist_get(list, iter->ordinal);
iter->ordinal = iter->current_value? ++iter->ordinal: 0;
return iter->ordinal? 0: -1;
}
/*
* etch_arraylist_iterable_has_next()
* i_iterable has_next() implementation.
*/
int etch_arraylist_iterable_has_next(etch_iterator* iter)
{
etch_arraylist* list = iter? iter->collection: NULL;
const int count = list? list->count: 0;
return count && iter->ordinal && (iter->ordinal <= count);
}