| /* $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. | |
| */ | |
| /* | |
| * etchobj.c | |
| */ | |
| #include "etch_global.h" | |
| #include "etchexcp.h" | |
| unsigned int primitive_objsize[] = | |
| { | |
| sizeof(etch_byte), sizeof(etch_boolean), sizeof(etch_int8), sizeof(etch_int16), | |
| sizeof(etch_int32), sizeof(etch_int64), sizeof(etch_float), sizeof(etch_double), | |
| sizeof(etch_string),sizeof(etch_date), | |
| }; | |
| /** | |
| * destroy_object() | |
| * default virtual destructor for an objmask* based object | |
| * (other than etch_object), invoked by all other object dtors. | |
| */ | |
| int destroy_object(objmask* thisobj) | |
| { | |
| objmask* parentobj = NULL; | |
| if (thisobj == NULL) return -1; | |
| parentobj = thisobj->parent; | |
| /* if a calling destructor did not already decrement the refcount ... */ | |
| if (!is_etchobj_refcount_decremented(thisobj)) | |
| if (thisobj->refcount > 0) /* if object is refcounted */ | |
| if (--thisobj->refcount > 0) /* destroy only if last ref */ | |
| return -1; | |
| if (thisobj->result) | |
| { | |
| destroy_exception(thisobj->result->exception); | |
| etch_free(thisobj->result); | |
| } | |
| if (!is_etchobj_static_shell(thisobj)) | |
| etch_free(thisobj); | |
| if (parentobj && parentobj->destroy) | |
| parentobj->destroy(parentobj); | |
| return 0; | |
| } | |
| /** | |
| * destroy_objectex() | |
| * mark object as "refcount already decremented" and invoke destroy_object | |
| */ | |
| int destroy_objectex(objmask* thisobj) | |
| { /* we usurp hashkey for this purpose since object is being destroyed anyway */ | |
| if (thisobj) thisobj->hashkey = ETCH_NOREFCOUNT_MARKER; | |
| return destroy_object(thisobj); | |
| } | |
| /** | |
| * destroy_etch_object() | |
| * default virtual destructor for an anonymous object wrapper. | |
| * such a wrapper is final (it will not have a parent) | |
| */ | |
| int destroy_etch_object(etch_object* thisobj) | |
| { | |
| objmask* childobj = NULL; | |
| int is_childobj_etchobj = 0, is_childobj_owned = 0, is_static_content = 0; | |
| if (thisobj == NULL) return -1; | |
| if (thisobj->refcount > 0) /* if object is refcounted */ | |
| if (--thisobj->refcount > 0) /* destroy only if last ref */ | |
| return -1; | |
| if (thisobj->result) | |
| { /* a result object pointer is not part of static content since never copied */ | |
| destroy_exception(thisobj->result->exception); | |
| etch_free(thisobj->result); | |
| } | |
| childobj = thisobj->value; | |
| is_childobj_etchobj = thisobj->is_value_object; | |
| is_childobj_owned = thisobj->is_value_owned; | |
| is_static_content = is_etchobj_static_content(thisobj); | |
| if (!is_etchobj_static_shell(thisobj)) | |
| etch_free(thisobj); | |
| if (childobj && is_childobj_owned && !is_static_content) | |
| { if (is_childobj_etchobj) | |
| childobj->destroy(childobj); | |
| else etch_free(childobj); | |
| } | |
| return 0; | |
| } | |
| /** | |
| * destroy_etch_object_value() | |
| * destroy the content of an object wrapper. | |
| * this is not called by the object destructor because it destroys the wrapper first. | |
| */ | |
| int destroy_etch_object_value(etch_object* x) | |
| { | |
| int result = 0; | |
| if (is_etchobj_static_content(x)) | |
| result = -1; | |
| else | |
| if (x->is_value_owned) | |
| if (x->is_value_object) | |
| ((objmask*)x->value)->destroy(x->value); | |
| else etch_free(x->value); | |
| else result = -1; | |
| return result; | |
| } | |
| /** | |
| * set_etch_assignable_arg_from() | |
| * populate an argument to etchobj_is_assignable_from() from specified object | |
| */ | |
| void set_etch_assignable_arg_from(etch_objclass* arg, objmask* obj) | |
| { | |
| vtabmask* vtab = NULL; | |
| memset(arg, 0, sizeof(etch_objclass)); | |
| if (NULL == obj) return; | |
| arg->obj_type = obj->obj_type; | |
| arg->class_id = obj->class_id; | |
| arg->parent = obj->parent; | |
| if (vtab = (vtabmask*) obj->vtab) | |
| { arg->vtable_class_id = vtab->class_id; | |
| arg->inherits_from = vtab->inherits_from; | |
| } | |
| switch(obj->obj_type) | |
| { case ETCHTYPEB_NATIVEARRAY: | |
| arg->numdims = ((etch_nativearray*)obj)->numdims; | |
| arg->content_obj_type = ((etch_nativearray*)obj)->content_obj_type; | |
| arg->content_class_id = ((etch_nativearray*)obj)->content_class_id; | |
| break; | |
| case ETCHTYPEB_ARRAYVAL: | |
| arg->numdims = ((etch_collection_mask*)obj)->n; | |
| arg->content_obj_type = ((etch_collection_mask*)obj)->content_obj_type; | |
| arg->content_class_id = ((etch_collection_mask*)obj)->content_class_id; | |
| break; | |
| case ETCHTYPEB_ARRAYLIST: | |
| arg->numdims = 1; | |
| arg->content_obj_type = ((etch_collection_mask*)obj)->content_obj_type; | |
| arg->content_class_id = ((etch_collection_mask*)obj)->content_class_id; | |
| } | |
| } | |
| /** | |
| * etchobj_is_assignable_from() | |
| * determines if the class of thisobj is the same as, or is a superclass of, | |
| * the class specified by that_class_id. tests whether the type represented | |
| * by that_obj_type and that_class_id can be converted to the type of thisobj | |
| * via an identity conversion or via a widening reference conversion. | |
| * | |
| * @param target the class of left side of the assignment (to). | |
| * during validation, this is the class of object the validator expects | |
| * to validate. if target object is an array, this is the array content class. | |
| * @param source the class of right side of the assignment (from). | |
| * during validation, this is the class of the object being validated. | |
| * if target object is an array, this is the array content class. | |
| */ | |
| int etchobj_is_assignable_from(etch_objclass* target, etch_objclass* source) | |
| { | |
| if (source->class_id == target->class_id && source->numdims == 0) | |
| return TRUE; /* identity case */ | |
| /* if left side is Object wrapper (not Object[]), anything can be assigned | |
| * to it, since the c binding does not receive anything that is unwrapped. | |
| * i.e. in java Object.class is not assignable from int.class; however here | |
| * we don't have an int.class, only wrapped integers. if this were not the | |
| * case this test would come after the etch primitives test, since a class | |
| * not derived from object can't be assigned to object. | |
| */ | |
| if (is_etch_object_type(target->obj_type, target->class_id)) return TRUE; | |
| if (target->obj_type == ETCHTYPEB_PRIMITIVE | |
| || source->obj_type == ETCHTYPEB_PRIMITIVE) return FALSE; | |
| /* if left (target) side is an array, and right (source) side is an | |
| * array of the same dimensions, and either of the same content type, | |
| * or an array of Object, then it is assignable. | |
| * this code is not robust - need to rethink assignability for default | |
| * array validator with various validation object array types. | |
| * currently we are validating very loosely for array types. we need | |
| * a more general means of validating arrays, i.e. common attributes | |
| * among array types (native, value, list) | |
| */ | |
| if (is_etch_objarray_type (target->obj_type, target->class_id) | |
| || is_etch_arraylist_type (target->obj_type, target->class_id) | |
| || is_etch_nativearray_type(target->obj_type, target->class_id)) | |
| { | |
| if (target->numdims == source->numdims) | |
| { | |
| /* this line added to pass anything using default array validator */ | |
| if (target->content_obj_type == 0 && target->content_class_id == 0) | |
| return TRUE; | |
| if (target->content_class_id == CLASSID_UNWRAPPED) | |
| return target->content_obj_type == source->content_obj_type; | |
| if (target->content_class_id == CLASSID_OBJECT | |
| || target->content_class_id == source->content_class_id) | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| else /* if source inherits from target, source can be assigned to target */ | |
| if (source->parent) | |
| { /* inheritance model 2 - inherited objects are instantiated and chained */ | |
| objmask* thisparent = source->parent; | |
| while(thisparent) /* walk source object's inheritance chain */ | |
| { | |
| if (thisparent->class_id == target->class_id) return TRUE; | |
| thisparent = thisparent->parent; | |
| } | |
| } | |
| else | |
| { int ndx = 0; | |
| etchparentinfo* parentinfo; | |
| /* inheritance model 1 - inherited data flattened into single object */ | |
| while(1) /* iterate source object's inheritance list */ | |
| { | |
| if (NULL == (parentinfo = get_next_etch_parentex | |
| (source->class_id, source->inherits_from, ndx++))) | |
| break; | |
| if (parentinfo->class_id == target->class_id) | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * etchobj_is_assignable_from_ex() | |
| * see comments for etchobj_is_assignable_from() | |
| */ | |
| int etchobj_is_assignable_fromobj(objmask* targetobj, objmask* sourceobj) | |
| { | |
| etch_objclass targetarg, sourcearg; | |
| set_etch_assignable_arg_from(&targetarg, targetobj); | |
| set_etch_assignable_arg_from(&sourcearg, sourceobj); | |
| return etchobj_is_assignable_from(&targetarg, &sourcearg); | |
| } | |
| /* | |
| * etchobj_assign_to() | |
| * assign object b to object a, if legal to do so. | |
| * handles assignments of wrapped objects, array objects, and scalar objects. | |
| * @return 0 or -1 | |
| */ | |
| objmask* etchobj_assign_to(objmask* a, objmask* b) | |
| { | |
| objmask* resultobj = a; | |
| if (is_etch_object(a)) | |
| { | |
| if (is_etch_object(b)) | |
| { /* both sides are wrappers so just copy right value to left */ | |
| destroy_etch_object_value((etch_object*)a); | |
| ((etch_object*)a)->value = ((etch_object*)b)->value; | |
| ((etch_object*)a)->is_value_object = ((etch_object*)b)->is_value_object; | |
| ((etch_object*)a)->is_value_owned = ((etch_object*)b)->is_value_owned; | |
| } | |
| else /* left side is a wrapper, right side is not a wrapper */ | |
| { /* so embed right side value inside left side wrapper */ | |
| destroy_etch_object_value((etch_object*)a); | |
| ((etch_object*)a)->value = b; /* wrap b inside a */ | |
| ((etch_object*)a)->is_value_object = TRUE; | |
| ((etch_object*)a)->is_value_owned = FALSE; | |
| } | |
| } | |
| else | |
| if (is_etch_object(b)) | |
| { /* left side is not a wrapper, right side is a wrapper */ | |
| /* we do not currently support copying right side wrapped content | |
| * to the left side, although technically we could easily do so. | |
| */ | |
| resultobj = NULL; | |
| } | |
| else | |
| if (!etchobj_is_assignable_fromobj(a, b)) | |
| { /* source object is not legally assignable to target */ | |
| resultobj = NULL; | |
| } | |
| else /* neither side is a wrapper, right side is assignable to left */ | |
| if (is_etch_nativearray(a)) | |
| { /* both sides are arrays per is_assignable_from(), | |
| * attempt to copy right side array to left */ | |
| resultobj = (objmask*) etch_nativearray_assign_to | |
| ((etch_nativearray*) a, (etch_nativearray*) b); | |
| } | |
| else /* both sides are scalar */ | |
| if (b->parent != NULL) | |
| { /* inheritance type 2: parent object(s) is/are instantiated | |
| * and chained to the inheritor. with this type of inheritance, | |
| * it is possible that the memory reference we return is not the | |
| * same as the target reference we pass. this is because we can't | |
| * copy source to target to do the assignment, since the child | |
| * object we would want to assign to it may in fact be larger | |
| * than the target object. | |
| */ | |
| objmask* thisparent = b; | |
| resultobj = NULL; | |
| while(thisparent) /* walk the parent chain */ | |
| { if (thisparent->class_id == a->class_id) | |
| { resultobj = thisparent; | |
| break; | |
| } | |
| else thisparent = thisparent->parent; | |
| } | |
| } | |
| else /* inheritance type 1: parent data are members of child object. */ | |
| { /* since the right side is derived from left, and since we enforce | |
| * a rule that derived objects must append instance data to the end | |
| * of the parent object, we can simply copy memory from the source | |
| * to the target, limited to the known shorter length of the target; | |
| * and then restore the individual fields which should be retained. | |
| * this scheme of course depends on object constructors populating | |
| * obj->length with sizeof(instantiated struct), in all cases. */ | |
| /* save off items we need to restore post-copy */ | |
| objmask save, *saved = &save; | |
| memcpy(saved, a, sizeof(objmask)); | |
| /* mark target shell immutable so destructor will not free it */ | |
| set_etchobj_static_shell(a); | |
| /* invoke target destructor to free any content owned by target */ | |
| a->destroy(a); | |
| /* copy source to target */ | |
| memcpy(a, b, a->length); | |
| /* mark target as a copy, its content not owned by it */ | |
| a->is_copy = TRUE; | |
| clear_etchobj_static_shell(a); | |
| set_etchobj_static_content(a); | |
| /* finally restore the retained target fields */ | |
| a->obj_type = saved->obj_type; | |
| a->class_id = saved->class_id; | |
| a->refcount = saved->refcount; | |
| a->result = saved->result; | |
| a->destroy = saved->destroy; | |
| a->clone = saved->clone; | |
| a->get_hashkey = saved->get_hashkey; | |
| if (is_etchobj_static_shell(saved)) | |
| set_etchobj_static_shell(a); | |
| } | |
| return resultobj; | |
| } | |
| /** | |
| * etch_addref() | |
| * increment object's reference count. if previously zero, begins reference | |
| * counting of an object. obj.refcount > 0 indicates that the object is | |
| * reference counted. conversely, if obj.refcount is zero, an object is not | |
| * reference counted. if an object is refcounted, then invocation of its | |
| * destructor implies decrementing the refcount, destroying the object only | |
| * if the refcount is now zero. | |
| */ | |
| unsigned etch_addref(objmask* thisobj) | |
| { | |
| return ++thisobj->refcount; | |
| } | |
| /** | |
| * etch_release() | |
| * decrement object's reference count and destroy the object if now zero. | |
| * returns new reference count. zero indicates the object was destroyed. | |
| * use of this method is optional, since invocation of a refcounted object's | |
| * destructor does an implicit decrement of the refcount. however it is | |
| * included since coding destroy() might be misleading to the reader. | |
| */ | |
| unsigned etch_release(objmask* thisobj) | |
| { | |
| return destroy_object(thisobj) == 0? 0: thisobj->refcount; | |
| } | |
| /** | |
| * etch_release_wrapper() | |
| * see comments above at etch_release() | |
| */ | |
| unsigned etch_release_wrapper(etch_object* thisobj) | |
| { | |
| return destroy_etch_object(thisobj) == 0? 0: thisobj->refcount; | |
| } | |
| /** | |
| * destroy_string() | |
| */ | |
| int destroy_string(etch_string* thisp) | |
| { | |
| if (thisp->refcount > 0 && --thisp->refcount > 0) return -1; | |
| if (!is_etchobj_static_content(thisp)) | |
| etch_free(thisp->v.value); /* OK if null */ | |
| destroy_objectex((objmask*)thisp); | |
| return 0; | |
| } | |
| /** | |
| * default virtual copy constructor for etch object. | |
| */ | |
| objmask* clone_object(objmask* pthis) | |
| { | |
| void* object_value = NULL; | |
| void* pnew = NULL; | |
| unsigned objsize = pthis->length; | |
| if (objsize < sizeof(objmask)) objsize = sizeof(objmask); | |
| pnew = etch_malloc (objsize, pthis->obj_type); | |
| memcpy(pnew, pthis, objsize); | |
| return (objmask*) pnew; | |
| } | |
| /** | |
| * default virtual copy constructor for objects requiring deep copy | |
| */ | |
| objmask* clone_null(objmask* pthis) | |
| { | |
| return NULL; | |
| } | |
| /** | |
| * clone_string() | |
| */ | |
| etch_string* clone_string(etch_string* thisp) | |
| { | |
| etch_string* newobj = (etch_string*) clone_object((objmask*)thisp); | |
| newobj->v.value = thisp->encoding == ETCH_ENCODING_UTF16? | |
| (void*) new_wchar(thisp->v.valw): (void*) new_char (thisp->v.valc); | |
| return newobj; | |
| } | |
| /** | |
| * new_etchresult() | |
| * generate etchobject result object | |
| */ | |
| etchresult* new_etchresult(const int result, const int reason) | |
| { | |
| etchresult* newresult = etch_malloc(sizeof(etchresult), ETCHTYPEB_RESULT); | |
| memset(newresult, 0, sizeof(etchresult)); | |
| newresult->resultcode = result; | |
| newresult->reasoncode = reason; | |
| return newresult; | |
| } | |
| /** | |
| * defgethashkey | |
| * default hashkey computation for an etch object | |
| */ | |
| unsigned defgethashkey(objmask *obj) | |
| { | |
| void* hashitem = obj; /* uses the object address as hash source */ | |
| if (NULL == hashitem) return 0; | |
| return obj->hashkey = etchhash((char*)&hashitem, sizeof(void*), 0); | |
| } | |
| /** | |
| * new_object() | |
| * basic object constructor | |
| * sets type, class, size, default destructor, and copy constructor. | |
| */ | |
| objmask* new_object(const int objsize, const unsigned short obj_type, const unsigned short class_id) | |
| { | |
| objmask* newobj = etch_malloc(objsize, obj_type); | |
| memset(newobj, 0, objsize); | |
| newobj->obj_type = obj_type; | |
| newobj->class_id = class_id; | |
| newobj->length = objsize; | |
| newobj->destroy = destroy_object; | |
| newobj->clone = clone_object; | |
| newobj->get_hashkey = defgethashkey; | |
| newobj->get_hashkey(newobj); | |
| return newobj; | |
| } | |
| /** | |
| * clone_etch_object() | |
| * clone_etch_object copy constructor | |
| */ | |
| objmask* clone_etch_object(objmask* thatobj) | |
| { | |
| objmask* newobj = clone_object(thatobj); | |
| ((etch_object*) newobj)->is_value_owned = FALSE; | |
| newobj->is_copy = TRUE; | |
| return newobj; | |
| } | |
| /** | |
| * new_etch_object() | |
| * wrapper object constructor | |
| */ | |
| etch_object* new_etch_object(const unsigned short class_id, objmask* childobj) | |
| { | |
| etch_object* newobj = (etch_object*) new_object | |
| (sizeof(etch_object), ETCHTYPEB_ETCHOBJECT, class_id); | |
| newobj->is_value_object = FALSE; /* when TRUE dtor calls destroy() */ | |
| newobj->is_value_owned = TRUE; /* when TRUE dtor frees content */ | |
| newobj->value = childobj; | |
| newobj->destroy = destroy_etch_object; | |
| newobj->clone = clone_etch_object; | |
| return newobj; | |
| } | |
| /** | |
| * short_type() | |
| * return a 16-bit type code of 2 8-bit parts | |
| */ | |
| short short_type(unsigned i, unsigned j) | |
| { | |
| return (short) ( ( ((byte)i) << 16 ) | ((byte)j) ); | |
| } | |
| /** | |
| * clear_lastresult() | |
| * clear the global lastresult object | |
| */ | |
| void clear_lastresult() | |
| { | |
| (lastresobj = &_lastresobj)->result = &lastresult; | |
| if (lastresult.exception) destroy_exception(lastresult.exception); | |
| memset(&lastresult, 0, sizeof(struct etchresult)); | |
| } | |
| /** | |
| * verify_object() | |
| * verify that the object passed is of the specified type and class. | |
| * zero is a match for either so pass zero to not validate either. | |
| */ | |
| int verify_object(objmask* obj, const unsigned short type, const unsigned short id, void** out) | |
| { | |
| if (obj == NULL) return -1; | |
| if (type != 0 && obj->obj_type != type) return -1; | |
| if (id != 0 && obj->class_id != id) return -1; | |
| return 0; | |
| } | |
| /** | |
| * get_base_vtable() | |
| * walks a vtable chain returning the final vtab in the chain | |
| */ | |
| void* get_base_vtable(objmask* obj) | |
| { | |
| vtabmask* basevtab = obj->vtab; | |
| while (basevtab && basevtab->vtab) basevtab = basevtab->vtab; | |
| return basevtab; | |
| } | |
| /** | |
| * destroy_vtable() | |
| */ | |
| int destroy_vtable(objmask* vtab) | |
| { | |
| etchparentinfo* inheritlist = ((vtabmask*)vtab)->inherits_from; | |
| if (inheritlist && !is_etchobj_static_content(vtab)) | |
| free(inheritlist); /* vtables not tracked */ | |
| if (!is_etchobj_static_shell(vtab)) | |
| free(vtab); /* vtables not tracked */ | |
| return 0; | |
| } | |
| /** | |
| * get_class_cachekey() | |
| * get the unique value used for keying the indicated class in a class cache. | |
| */ | |
| unsigned get_class_cachekey(unsigned short obj_type, unsigned short class_id) | |
| { | |
| unsigned key = (obj_type << 16) | class_id; | |
| return etchhash(&key, 4, 0); | |
| } | |
| /** | |
| * get_vtable_cachehkey() | |
| * calculates ad returns the *cache* key for specified vtable object | |
| * the hashkey on the vtable object is not an object key, it is a class key, | |
| * since vtables are cached by class. | |
| */ | |
| unsigned get_vtable_cachehkey(unsigned short class_id) | |
| { | |
| return get_class_cachekey(ETCHTYPEB_VTABLE, class_id); | |
| } | |
| /** | |
| * get_vtabobj_hashkey() | |
| * sets and gets the *cache* key for specified vtable object | |
| * the hashkey on the vtable object is not an object key, it is a class key, | |
| * since vtables are cached by class. | |
| */ | |
| unsigned get_vtabobj_hashkey(objmask* vtabobj) | |
| { | |
| return vtabobj->hashkey = get_vtable_cachehkey(vtabobj->class_id); | |
| } | |
| /** | |
| * new_vtable() | |
| * instantiate a new virtual function table of the specified type, | |
| * defaulting all methods to those of specified parent if requested | |
| */ | |
| void* new_vtable(const void* parentvtab, const size_t size, const short classid) | |
| { | |
| objmask* newvtab = malloc(size); /* vtable memory is not tracked */ | |
| if (parentvtab) | |
| memcpy(newvtab, parentvtab, size); | |
| else memset(newvtab, 0, size); | |
| newvtab->obj_type = ETCHTYPEB_VTABLE; | |
| newvtab->class_id = classid; | |
| newvtab->length = (unsigned) size; | |
| newvtab->destroy = destroy_vtable; | |
| newvtab->clone = clone_object; | |
| newvtab->get_hashkey = get_vtabobj_hashkey; | |
| newvtab->get_hashkey(newvtab); | |
| return newvtab; | |
| } | |
| /** | |
| * get_vtab_inheritance_list() | |
| * add an inheritance list to the specified object, or fetch existing list. | |
| * an inheritance list exists in the vtable since we don't need to duplicate it | |
| * for every instance. if there is no vtable in the specified object, a place- | |
| * holder vtable is instantiated in the object. recall that vtables are freed | |
| * when the global cache is cleared. the first entry in an inheritance list | |
| * contains the list attributes, therefore the list is one-based. | |
| * if an appropriately-sized list is already cached, the cached list is | |
| * returned. if a shorter list exists it is resized, copied, and returned. | |
| * @param size total number of entries to be allocated | |
| * @param count number of populated entries | |
| */ | |
| etchparentinfo* get_vtab_inheritance_list(objmask* obj, | |
| const short size, const short count, const short vtabclass) | |
| { | |
| etchparentinfo *oldlist = NULL, *newlist = NULL; | |
| /* if such a list is already cached, return it now */ | |
| vtabmask* vtab = obj->vtab? obj->vtab: cache_find(get_vtable_cachehkey(vtabclass), 0); | |
| oldlist = vtab? vtab->inherits_from: NULL; | |
| if (oldlist && oldlist->list_size >= size) | |
| { | |
| oldlist[0].list_count = count; | |
| obj->vtab = vtab; | |
| return oldlist; | |
| } | |
| newlist = new_etch_inheritance_list(size, count, oldlist); | |
| if (newlist == NULL) return NULL; | |
| /* note that we are creating a placeholder vtable here. we could not add | |
| * virtuals to such a vtable, since this vtable consists of the vtable | |
| * header only, per sizeof(vtabmask), following. | |
| */ | |
| if (vtab == NULL) | |
| { vtab = new_vtable(NULL, sizeof(vtabmask), vtabclass); | |
| vtab->inherits_from = newlist; | |
| cache_insert(vtab->hashkey, vtab, FALSE); | |
| obj->vtab = vtab; | |
| } | |
| vtab->inherits_from = newlist; | |
| return newlist; | |
| } | |
| /** | |
| * new_etch_inheritance_list() | |
| * allocate and return an inheritance list of the specified size | |
| * @param size total number of entries to be allocated | |
| * @param count number of populated entries | |
| */ | |
| etchparentinfo* new_etch_inheritance_list(const short size, const short count, | |
| etchparentinfo* oldlist) | |
| { | |
| etchparentinfo *newlist = NULL; | |
| const int newbytes = size * sizeof(etchparentinfo), MAXPARENTS = 15; | |
| if (count < 0 || count > MAXPARENTS || size < 0 || count > size) | |
| return NULL; | |
| newlist = malloc(newbytes); /* vtables not tracked */ | |
| memset(newlist, 0, newbytes); | |
| if (oldlist) /* we may be expanding an existing list, copy into new list */ | |
| { const int oldbytes = oldlist->list_size * sizeof(etchparentinfo); | |
| memcpy(newlist, oldlist, oldbytes); | |
| free(oldlist); /* vtables not tracked */ | |
| oldlist = NULL; | |
| } | |
| else newlist[0].list_count = count; | |
| newlist[0].list_size = size; /* list attributes are in first entry */ | |
| return newlist; | |
| } | |
| /** | |
| * is_derives_from_object() | |
| * determine if specified object derives from object. | |
| * all objmask-masked objects are etch c objects by definition; however it is | |
| * here that we would artificially specify that certain wrapped objects are not | |
| * derived from object in the logical etch sense, if such a need arises. | |
| */ | |
| int is_derives_from_object(objmask* obj) | |
| { | |
| return obj? is_derives_from_object_class(obj->class_id): FALSE; | |
| } | |
| /** | |
| * is_derives_from_object_class() | |
| * see comments at is_derives_from_object() | |
| */ | |
| int is_derives_from_object_class(const unsigned short class_id) | |
| { | |
| return TRUE; | |
| } | |
| /** | |
| * get_next_etch_parent() | |
| * see comments at get_next_etch_parentex() below | |
| */ | |
| etchparentinfo* get_next_etch_parent(objmask* obj, int current_index) | |
| { | |
| etchparentinfo* inherit_list = obj && obj->vtab? obj->vtab->inherits_from: NULL; | |
| return get_next_etch_parentex(obj->class_id, inherit_list, current_index); | |
| } | |
| /** | |
| * get_next_etch_parentex() | |
| * returns a non-disposable reference to etchparentinfo struct containing the | |
| * class of next parent in this object's inheritance hierarchy, relative to the | |
| * specified index. if specified object does in fact inherit from other than | |
| * object, its inheritance list must have been previously instantiated via | |
| * get_vtab_inheritance_list(), above, and populated accordingly, presumably | |
| * int the object's constructor. the inheritance list implicitly ends with | |
| * object, if the specified object's class derives from object; however object | |
| * does not explicitly appear in the list and in fact must not be so populated. | |
| * @param obj the etch object for which a parent is requested. | |
| * @param current_index the index of the currently requested parent. on the | |
| * first call specify zero, on subsequent calls increment current_index. | |
| * @return etchparentinfo as described above, or NULL if no more parents. | |
| * the returned reference is valid while its containing inheritance list remains | |
| * instantiated, which ordinarily is while its associated vtable exists, which | |
| * ordinarily is until service teardown. | |
| */ | |
| etchparentinfo* get_next_etch_parentex | |
| (const unsigned short class_id, etchparentinfo* inherit_list, int current_index) | |
| { | |
| static etchparentinfo object_parent = { ETCHTYPEB_ETCHOBJECT, CLASSID_OBJECT }; | |
| etchparentinfo* nextparent = NULL; | |
| if (NULL == inherit_list && current_index > 0); | |
| else | |
| if ((NULL == inherit_list) || (current_index == inherit_list[0].list_count)) | |
| if (is_derives_from_object_class(class_id)) | |
| nextparent = &object_parent; | |
| else; | |
| else | |
| if (current_index < inherit_list[0].list_count) | |
| nextparent = &inherit_list[++current_index]; /* list is one-based */ | |
| return nextparent; | |
| } | |
| /** | |
| * new_primitive() | |
| * allocate, initialize and return a primitive object | |
| */ | |
| objmask* new_primitive(const unsigned obj_len, const unsigned short class_id) | |
| { | |
| objmask* newobj = new_object(obj_len, ETCHTYPEB_PRIMITIVE, class_id); | |
| newobj->destroy = destroy_object; | |
| newobj->clone = clone_object; | |
| newobj->get_hashkey = etch_number_get_hashkey; | |
| return newobj; | |
| } | |
| /** | |
| * new_wchar() | |
| * wide character string clone | |
| */ | |
| wchar_t* new_wchar(const wchar_t* s) | |
| { | |
| #pragma warning(disable:4996) /* wcscpy unsafe warning */ | |
| unsigned bytelen; | |
| wchar_t* clone; | |
| if (NULL == s) return NULL; | |
| bytelen = (unsigned)(wcslen(s) + 1) * sizeof(wchar_t); | |
| clone = etch_malloc(bytelen, ETCHTYPEB_STRING); | |
| wcscpy(clone, s); | |
| return clone; | |
| } | |
| /** | |
| * new_char() | |
| * narrow character string clone | |
| */ | |
| char* new_char(const char* s) | |
| { | |
| char* clone; | |
| if (NULL == s) return NULL; | |
| clone = etch_malloc(strlen(s) + 1, ETCHTYPEB_STRING); | |
| return strcpy(clone, s); | |
| } | |
| /** | |
| * new_byte() | |
| */ | |
| etch_byte* new_byte(const signed char v) | |
| { | |
| etch_byte* newobj = (etch_byte*) new_primitive | |
| (sizeof(struct etch_byte), CLASSID_PRIMITIVE_BYTE); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_boolean() | |
| */ | |
| etch_boolean* new_boolean(boolean v) | |
| { | |
| etch_boolean* newobj = (etch_boolean*) new_primitive | |
| (sizeof(struct etch_boolean), CLASSID_PRIMITIVE_BOOL); | |
| newobj->value = v? TRUE: FALSE; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_int8() | |
| */ | |
| etch_int8* new_int8(signed char v) | |
| { | |
| etch_int8* newobj = (etch_int8*) new_primitive | |
| (sizeof(struct etch_int8), CLASSID_PRIMITIVE_INT8); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_int16() | |
| */ | |
| etch_int16* new_int16(short v) | |
| { | |
| etch_int16* newobj = (etch_int16*) new_primitive | |
| (sizeof(struct etch_int16), CLASSID_PRIMITIVE_INT16); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_int32() | |
| */ | |
| etch_int32* new_int32(int v) | |
| { | |
| etch_int32* newobj = (etch_int32*) new_primitive | |
| (sizeof(struct etch_int32), CLASSID_PRIMITIVE_INT32); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_int64() | |
| */ | |
| etch_int64* new_int64(int64 v) | |
| { | |
| etch_int64* newobj = (etch_int64*) new_primitive | |
| (sizeof(struct etch_int64), CLASSID_PRIMITIVE_INT64); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_float() | |
| */ | |
| etch_float* new_float(float v) | |
| { | |
| etch_float* newobj = (etch_float*) new_primitive | |
| (sizeof(struct etch_float), CLASSID_PRIMITIVE_FLOAT); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_double() | |
| */ | |
| etch_double* new_double(double v) | |
| { | |
| etch_double* newobj = (etch_double*) new_primitive | |
| (sizeof(struct etch_double), CLASSID_PRIMITIVE_DOUBLE); | |
| newobj->value = v; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * etch_string_get_hashkey | |
| * hashkey computation override for an etch_string. | |
| * hash key is computed using the raw string as hash source. | |
| */ | |
| unsigned etch_string_get_hashkey(objmask* etchobj) | |
| { | |
| etch_string* sobj = (etch_string*) etchobj; | |
| sobj->hashkey = ETCH_ENCODING_UTF16 == sobj->encoding? | |
| etch_get_wchar_hashkey(sobj->v.valw): | |
| etch_get_char_hashkey(sobj->v.valc); | |
| return sobj->hashkey; | |
| } | |
| /** | |
| * etch_string_init() | |
| * private constructor for opaque string | |
| */ | |
| etch_string* etch_string_init(const void* s, const unsigned char encoding) | |
| { | |
| etch_string* newobj = (etch_string*) | |
| new_primitive(sizeof(struct etch_string), CLASSID_STRING); | |
| switch(encoding) | |
| { case ETCH_ENCODING_UTF16: | |
| case ETCH_ENCODING_ASCII: | |
| case ETCH_ENCODING_UTF8: | |
| newobj->encoding = encoding; | |
| break; | |
| default: newobj->encoding = ETCH_ENCODING_DEFAULT; | |
| } | |
| if (s) | |
| { switch(encoding) | |
| { case ETCH_ENCODING_UTF16: | |
| newobj->char_count = (unsigned) wcslen((wchar_t*)s); | |
| newobj->byte_count = (newobj->char_count + 1) * sizeof(wchar_t); | |
| break; | |
| default: | |
| newobj->char_count = (unsigned) strlen((char*)s); | |
| newobj->byte_count = (newobj->char_count + 1) * sizeof(char); | |
| } | |
| } | |
| else newobj->is_null = TRUE; | |
| newobj->destroy = destroy_string; | |
| newobj->clone = clone_string; | |
| return newobj; | |
| } | |
| /** | |
| * new_string() | |
| * clones supplied string | |
| * @param s a raw string to be assigned to the new string object. | |
| * caller retains ownership of s. | |
| */ | |
| etch_string* new_string(const void* s, const unsigned char encoding) | |
| { | |
| etch_string* newobj = etch_string_init(s, encoding); | |
| if (s) | |
| { switch(encoding) | |
| { case ETCH_ENCODING_UTF16: | |
| newobj->v.valw = etch_malloc(newobj->byte_count, ETCHTYPEB_STRING); | |
| wcscpy(newobj->v.valw, (wchar_t*)s); | |
| break; | |
| default: | |
| newobj->v.valc = etch_malloc(newobj->byte_count, ETCHTYPEB_STRING); | |
| strcpy(newobj->v.valc, (char*)s); | |
| } | |
| } | |
| newobj->get_hashkey = etch_string_get_hashkey; | |
| newobj->get_hashkey((objmask*)newobj); | |
| return newobj; | |
| } | |
| /** | |
| * new_stringw() | |
| * convenience constructor for string type ETCH_ENCODING_UTF16; | |
| * @param s a raw string to be assigned to the new string object. | |
| * caller retains ownership of s. | |
| */ | |
| etch_string* new_stringw(const void* s) | |
| { | |
| return new_string(s, ETCH_ENCODING_UTF16); | |
| } | |
| /** | |
| * new_stringa() | |
| * convenience constructor for string type ETCH_ENCODING_UTF8); | |
| * @param s a raw string to be assigned to the new string object. | |
| * caller retains ownership of s. | |
| */ | |
| etch_string* new_stringa(const void* s) | |
| { | |
| return new_string(s, ETCH_ENCODING_UTF8); | |
| } | |
| /** | |
| * new_string_from() | |
| * does not clone supplied string | |
| * @param s a disposable raw string to be assigned to the new string object. | |
| * caller relinquishes ownership of s. | |
| */ | |
| etch_string* new_string_from(const void* s, const unsigned char encoding) | |
| { | |
| etch_string* newobj = etch_string_init(s, encoding); | |
| newobj->v.value = (void*) s; | |
| return newobj; | |
| } | |
| /** | |
| * new_etch_event() | |
| */ | |
| etch_event* new_etch_event(const unsigned short class_id, const int value) | |
| { | |
| etch_event* newobj = (etch_event*) new_int32(value); | |
| if (class_id) newobj->class_id = class_id; | |
| return newobj; | |
| } | |
| /** | |
| * new_etch_query() | |
| */ | |
| etch_query* new_etch_query(const unsigned short class_id, const int value) | |
| { | |
| etch_query* newobj = (etch_query*) new_int32(value); | |
| if (class_id) newobj->class_id = class_id; | |
| return newobj; | |
| } | |
| /** | |
| * new_etch_control() | |
| */ | |
| etch_control* new_etch_control(const unsigned short class_id, const int value) | |
| { | |
| etch_control* newobj = (etch_control*) new_int32(value); | |
| if (class_id) newobj->class_id = class_id; | |
| return newobj; | |
| } | |
| /** | |
| * new_date() | |
| */ | |
| etch_date* new_date() | |
| { | |
| etch_date* newobj = (etch_date*) | |
| new_primitive(sizeof(struct etch_date), CLASSID_DATE); | |
| time (&newobj->value); | |
| newobj->ticks = clock(); | |
| return newobj; | |
| } | |
| /** | |
| * new_who() | |
| * a who is an etch_object wrapper around some etch object type, its purpose | |
| * to be a disposable object which opaquely specifies the object which is the | |
| * sender or receiver component of a method. | |
| * @param whoobj the object which is the actual source or destination. | |
| * if this object is a nondisposable refrerence, of course it must be assured | |
| * that the object is not destroyed prior to destruction of the etch_who | |
| * which references it. | |
| * @param is_owned if TRUE the etch_who destructor will destroy the whoobj. | |
| */ | |
| etch_who* new_who(void* whoobj, const int is_owned) | |
| { | |
| etch_who* newobj = (etch_who*) new_etch_object(CLASSID_WHO, whoobj); | |
| newobj->is_value_object = TRUE; | |
| newobj->is_value_owned = is_owned != FALSE; | |
| return newobj; | |
| } | |
| /** | |
| * new_nullobj() | |
| * instantiate and return a logically null object | |
| */ | |
| objmask* new_nullobj() | |
| { | |
| objmask* obj = (objmask*) new_etch_object(CLASSID_NONE, NULL); | |
| obj->is_null = TRUE; | |
| return obj; | |
| } | |
| /** | |
| * etch_get_char_hashkey | |
| * hashkey computation using a narrow string as source | |
| */ | |
| unsigned etch_get_char_hashkey(const char* s) | |
| { | |
| unsigned keybytelen = 0, hashkey = 0; | |
| if (NULL != s) | |
| keybytelen = (unsigned) strlen(s); | |
| if (keybytelen) | |
| hashkey = etchhash(s, keybytelen, 0); | |
| return hashkey; | |
| } | |
| /** | |
| * etch_get_wchar_hashkey | |
| * hashkey computation using a unicode string as source | |
| */ | |
| unsigned etch_get_wchar_hashkey(const wchar_t* s) | |
| { | |
| unsigned keybytelen = 0, hashkey = 0; | |
| if (NULL != s) | |
| keybytelen = (unsigned) (wcslen(s) * sizeof(wchar_t)); | |
| if (keybytelen) | |
| hashkey = etchhash(s, keybytelen, 0); | |
| return hashkey; | |
| } | |
| /** | |
| * etch_number_get_hashkey | |
| * hashkey computation override for an etch wrapped primitive number. | |
| * hash key is computed using the numeric value as hash source. | |
| */ | |
| unsigned etch_number_get_hashkey(objmask* etchobj) | |
| { | |
| unsigned bytelength, hashkey; | |
| switch(etchobj->class_id) | |
| { case CLASSID_PRIMITIVE_INT32: case CLASSID_PRIMITIVE_FLOAT: bytelength = 4; break; | |
| case CLASSID_PRIMITIVE_INT64: case CLASSID_PRIMITIVE_DOUBLE: case CLASSID_DATE: bytelength = 8; break; | |
| case CLASSID_PRIMITIVE_INT16: bytelength = 2; break; | |
| default: bytelength = 1; | |
| } | |
| hashkey = etchhash(&((etch_int64*)etchobj)->value, bytelength, 0); | |
| return etchobj->hashkey = hashkey; | |
| } |