/***********************************************************************/ /* Open Visualization Data Explorer */ /* (C) Copyright IBM Corp. 1989,1999 */ /* ALL RIGHTS RESERVED */ /* This code licensed under the */ /* "IBM PUBLIC LICENSE - Open Visualization Data Explorer" */ /***********************************************************************/ #include /* * Process Parts * John E. Allain * Apply a user specified function to each part of a group. * assemble groups with the same structure as the input. */ #include #include #define PartsDebugChar "P" #define ErrorGotoPlus1(e,s,a) {DXSetError(e,s,a); goto error;} #define MessageGotoPlus1(s,a) {DXAddMessage(s,a); goto error;} /* * Running debate: * The structure of _recurse is convolved because the convention * 'copy' passes fields uncopied to the process_part worker. * If it were copied first, and the worker modified components in * place, then it could be cleaner. This would however require the * worker to clean out beaucoup components when it really wants to * build a new field. * * Note significant changes: * DXEmptyField check is no longer performed. * The DXEndField call is now expected of the worker routine. * * Function naming convention: * * Mixed case lettering, leading underscore: * public (extern) routine, undocumented. * Mixed case lettering, no underscores: * public (extern) routine, documented. * Lower case lettering, with underscores: * private (static) routine, undocumented. * * Cleanup convention: * out_object is to be considered deletable at all times. */ /*-----------------------------------*\ | Repackage of the input for AddTask \*-----------------------------------*/ typedef struct { /* User supplied items */ Field (*process_part) ( Field, char *, int ); Object self; Pointer args; int size; /* internal states */ int parallel; int copy; int preserve; Object parent; char *member_name; int group_member_position; float series_FP_value; } part_arg_type; static part_arg_type part_arg_initializer = { NULL, NULL, NULL, 0, 1, 1, 0, NULL, NULL, 0, 0.0 }; static /* * Set a child for an existing parent as found in arg->parent. * Specifically, arg->parent will refer to child, using the * referencing mechanism appropriate to that object. * Note that on first call with a given parent/child combination * that the child is is not so much replaced as placed. * * The caller is expected to set 'child' to null following this call, * for the purposes of not deleting it twice during error cleanup. */ Error _replace_child ( part_arg_type *arg, Object child ) { DXASSERTGOTO ( arg != NULL ); DXASSERTGOTO ( child != NULL ); DXASSERTGOTO ( arg->copy == 1 ); DXASSERTGOTO ( arg->parent != NULL ); /* Trace */ if ( DXQueryDebug ( PartsDebugChar ) ) if ( ( DXGetObjectClass ( child ) == CLASS_FIELD ) && DXEmptyField ( (Field) child ) ) DXDebug ( PartsDebugChar, "_replace_child with DXEmptyField" ); else DXDebug ( PartsDebugChar, "_replace_child" ); switch ( DXGetObjectClass ( arg->parent ) ) { case CLASS_GROUP: switch ( DXGetGroupClass ( (Group)arg->parent ) ) { case CLASS_SERIES: if ( !DXSetSeriesMember ( (Series)arg->parent, arg->group_member_position, arg->series_FP_value, child ) ) goto error; break; default: /* member name association can preserve order */ if ( arg->preserve ) { if ( arg->member_name == NULL ) { if ( !DXSetEnumeratedMember ( (Group)arg->parent, arg->group_member_position, child ) ) goto error; } else { if ( !DXSetMember ( (Group)arg->parent, arg->member_name, child ) ) goto error; } } else if ( !DXSetMember ( (Group)arg->parent, arg->member_name, child ) ) goto error; } break; case CLASS_XFORM: if ( !DXSetXformObject ( (Xform)arg->parent, child ) ) goto error; break; case CLASS_SCREEN: if ( !DXSetScreenObject ( (Screen)arg->parent, child ) ) goto error; break; case CLASS_CLIPPED: if ( !DXSetClippedObjects ( (Clipped)arg->parent, child, NULL ) ) goto error; break; default: DXErrorGoto ( ERROR_ASSERTION, "#11530" /* ProcessParts: Impossible parent */ ); } return OK; error: ASSERT ( DXGetError() != ERROR_NONE ); return ERROR; } /*-----------------------------------------*\ | The Leaf Processor called by AddTask \*-----------------------------------------*/ static /* * Call the process_part 'worker'. * Do so in a way acceptable to DXAddTask. * Check the return states. */ Error _call_process_part ( Pointer p ) { part_arg_type *arg = (part_arg_type *) p; Object out_object = NULL; DXASSERTGOTO ( arg != NULL ); out_object = (Object) arg->process_part ( (Field)arg->self, (char *)arg->args, arg->size ); /* * Check error states and trivial return cases. */ if ( out_object == NULL ) { if ( DXGetError() != ERROR_NONE ) goto error; else { /* * If preserve case or series groups, DXEmptyField's have already * been put in as placeholders. * Else, no need to use result. Either way OK */ arg->self = NULL; /* * But... * Ensure that DXEmptyField is returned if a single-level hierarchy. * (if parent is not NULL then a placeholder is there if need be) */ if ( ( arg->copy ) && ( arg->parent == NULL ) ) { if ( NULL == ( arg->self = (Object) DXNewField() ) ) goto error; } return OK; } } else if ( DXGetError() != ERROR_NONE ) MessageGotoPlus1 ( "#8000", /* %s set the error code, but did not return error */ "'process_part' worker" ) /* * Let's see if a valid object. * And if so, use it. */ if ( arg->copy ) { if ( arg->self == out_object ) DXErrorGoto ( ERROR_ASSERTION, "#11500" /* ProcessParts: worker output can't equal input when called with copy */ ); if ( arg->parent == NULL ) arg->self = out_object; else { if ( !_replace_child ( arg, out_object ) ) goto error; arg->self = out_object; out_object = NULL; } } else if ( arg->self != out_object ) DXErrorGoto ( ERROR_ASSERTION, "#11510" /* ProcessParts: worker output can't equal input when called with nocopy */ ); return OK; error: if ( ( out_object != NULL ) && ( out_object != arg->self ) ) DXDelete ( out_object ); ASSERT ( DXGetError() != ERROR_NONE ); return ERROR; } /*--------------------------------*\ | The Group Processor [Recursive] \*--------------------------------*/ static Error _recurse ( part_arg_type *arg, Field placeholder ) { part_arg_type call_arg; Object out_object = NULL; Class class; /* * Determine if a Field or Non-Field, and act accordingly. */ if ( ( class = DXGetObjectClass ( arg->self ) ) == CLASS_FIELD ) { if ( arg->parallel == 0 ) { if ( !_call_process_part ( (Pointer)arg ) ) goto error; out_object = arg->self; } else { /* * (insure that there is a place to put result during parallelism) */ DXASSERTGOTO ( arg->parent != NULL ); /* * Recall that DXAddTask will copy its input when * it is passed in as non-zero length. */ if ( !DXAddTask ( _call_process_part, (Pointer)arg, sizeof( part_arg_type ), (double)1.0 ) ) goto error; /* * Install a place-holder if called for; And that is: * copy and preserve mode * or copy mode with a series group parent * The actual output will be placed after ExectueTaskGroup() * is called. */ if ( arg->copy ) if ( arg->preserve || ( ( DXGetObjectClass ( arg->parent ) == CLASS_GROUP ) && ( DXGetGroupClass ( (Group)arg->parent ) == CLASS_SERIES ) ) ) if ( !_replace_child ( arg, (Object)placeholder ) ) goto error; } } else /* class != CLASS_FIELD */ { call_arg = part_arg_initializer; call_arg.size = arg->size; call_arg.args = arg->args; call_arg.process_part = arg->process_part; call_arg.parallel = arg->parallel; call_arg.copy = arg->copy; call_arg.preserve = arg->preserve; if ( arg->copy == 0 ) call_arg.parent = arg->self; else { /* * XXX * It is not entirely known how this DXCopy will behave across * the different object types. */ if ( !( out_object = DXCopy ( arg->self, COPY_ATTRIBUTES ) ) ) goto error; call_arg.parent = out_object; } switch ( class ) { case CLASS_GROUP: switch ( class = DXGetGroupClass ( (Group) arg->self ) ) { case CLASS_SERIES: call_arg.member_name = NULL; for ( call_arg.group_member_position = 0; call_arg.self = DXGetSeriesMember ( (Series)arg->self, call_arg.group_member_position, &call_arg.series_FP_value ); call_arg.group_member_position++ ) if ( !_recurse ( &call_arg, placeholder ) ) goto error; break; default: call_arg.series_FP_value = 0.0; for ( call_arg.group_member_position = 0; call_arg.self = DXGetEnumeratedMember ( (Group)arg->self, call_arg.group_member_position, &call_arg.member_name ); call_arg.group_member_position++ ) if ( ! _recurse ( &call_arg, placeholder ) ) goto error; /* XXX above */ } break; case CLASS_XFORM: if ( !DXGetXformInfo ( (Xform)arg->self, &call_arg.self, NULL ) || !_recurse ( &call_arg, placeholder ) ) goto error; break; case CLASS_SCREEN: if ( !DXGetScreenInfo ( (Screen)arg->self, &call_arg.self, NULL,NULL ) || !_recurse ( &call_arg, placeholder ) ) goto error; break; case CLASS_CLIPPED: if ( !DXGetClippedInfo ( (Clipped)arg->self, &call_arg.self, NULL ) || !_recurse ( &call_arg, placeholder ) ) goto error; break; default: DXASSERTGOTO ( class != CLASS_FIELD ); DXDebug ( PartsDebugChar, "funny class: %d", class ); } /* * Place the computed output in the new hierarchy; */ if ( arg->copy ) { if ( arg->parent == NULL ) arg->self = out_object; else { if ( !_replace_child ( arg, out_object ) ) goto error; arg->self = out_object; out_object = NULL; } } } return OK; error: if ( ( out_object != NULL ) && ( out_object != arg->self ) ) DXDelete ( out_object ); ASSERT ( DXGetError() != ERROR_NONE ); return ERROR; } static int _protected_parallel_switch = 1; extern Object _dxfProcessPartsNP ( Object object, Field (*process_part) ( Field, char *, int ), Pointer args, int size, int copy, int preserve ) { Object out_object; _protected_parallel_switch = 0; out_object = DXProcessParts ( object, process_part, args, size, copy, preserve ); _protected_parallel_switch = 1; return out_object; } extern Object DXProcessParts ( Object object, Field (*process_part) ( Field, char *, int ), Pointer args, int size, int copy, int preserve ) { /** prototype new argument **/ int parallel = _protected_parallel_switch; /**/ part_arg_type call_arg; Pointer args_copy_global = NULL; Field placeholder = NULL; call_arg = part_arg_initializer; if (object == NULL) { DXSetError (ERROR_MISSING_DATA, "#10000", "Object"); return NULL; } call_arg.self = object; call_arg.args = args; call_arg.size = size; call_arg.process_part = process_part; call_arg.parallel = parallel; call_arg.copy = copy; call_arg.preserve = preserve; /* * Note: the assumption currently is made that sizeof(args) = size */ if (size > 0 && args != NULL) { /* * Assume worst case that the argument 'args' is a pointer to * local memory. For this reason, 'args' is copied to global * memory to ensure validity across all processors accessed by * DXAddTask. */ if ((args_copy_global = DXAllocate (size)) == ERROR) goto error; memcpy (args_copy_global, args, size); call_arg.args = args_copy_global; } /* * When not copying, then order is preserved as a byproduct of * the modify in place operation. Preserve setting can be ignored. */ switch (DXGetObjectClass (call_arg.self)) { case CLASS_FIELD: call_arg.parallel = 0; case CLASS_GROUP: case CLASS_XFORM: case CLASS_SCREEN: case CLASS_CLIPPED: if ((call_arg.copy == 1) && (call_arg.parallel == 1)) /* * placeholder will be needed in cases of explicit preserve * order, or for series groups, which imply this. */ if (((placeholder = DXNewField()) == NULL) || !DXReference ((Object)placeholder)) goto error; if (call_arg.parallel == 1) { if (!DXCreateTaskGroup() || ! _recurse (&call_arg, placeholder) || !DXExecuteTaskGroup()) goto error; } else if (! _recurse (&call_arg, placeholder)) goto error; DXDelete ((Object)placeholder); break; default: DXSetError (ERROR_BAD_CLASS, "#10190", "Object"); goto error; } if (args_copy_global && !DXFree (args_copy_global)) goto error; else args_copy_global = NULL; return (call_arg.self); error: if ( copy && ( call_arg.self != object ) ) DXDelete ( call_arg.self ); if ( args_copy_global ) DXFree ( args_copy_global ); DXDelete ( (Object)placeholder ); return ERROR; } extern Object _dxfProcessPartsG ( Object object, Group (*process_part) ( Group, char *, int ), Pointer args, int size, int copy, int preserve ) { Object child = NULL; Group result = NULL; int i; /* Parallel !!! */ #if 0 "#11500" /* ProcessParts: worker output cannot equal input when called with copy */ "#11520" /* ProcessParts: args is NULL but size is nonzero */ #endif if ( ( copy != 0 ) || ( preserve != 1 ) ) DXErrorGoto ( ERROR_NOT_IMPLEMENTED, "DXProcessPartsG must be called with copy = 0, preserve = 1" ); switch ( DXGetObjectClass ( object ) ) { case CLASS_FIELD: case CLASS_ARRAY: default: #if 0 DXErrorGoto ( ERROR_BAD_CLASS, "encountered in object traversal" ); #endif break; case CLASS_GROUP: switch ( DXGetGroupClass ( (Group) object ) ) { case CLASS_GROUP: default: for (i = 0; ( NULL != ( child = DXGetEnumeratedMember ( (Group)object, i, NULL ) ) ); i++ ) if ( !_dxfProcessPartsG ( child, process_part, args, size, copy, preserve ) ) goto error; #if 0 DXSetEnumeratedMember((Group)o, i, n); #endif case CLASS_COMPOSITEFIELD: case CLASS_SERIES: if ( ERROR == ( result = process_part ( (Group)object, (char*)args, size ) ) ) goto error; if ( ( copy == 0 ) && ( result != (Group)object ) ) DXErrorGoto ( ERROR_ASSERTION, "#11510" /* ProcessParts: worker output must equal input when called with nocopy */ ); } break; case CLASS_XFORM: if ( !DXGetXformInfo ( (Xform)object, &child, NULL ) ) goto error; if ( !_dxfProcessPartsG ( child, process_part, args, size, copy, preserve ) ) goto error; break; #if 0 DXSetXformObject((Xform)o,n); #endif case CLASS_SCREEN: if ( !DXGetScreenInfo((Screen)object, &child, NULL, NULL ) ) goto error; if ( !_dxfProcessPartsG ( child, process_part, args, size, copy, preserve ) ) goto error; break; #if 0 DXSetScreenObject((Screen)o,n); #endif case CLASS_CLIPPED: if ( !DXGetClippedInfo((Clipped)object, &child, NULL ) ) goto error; if ( !_dxfProcessPartsG ( child, process_part, args, size, copy, preserve ) ) goto error; break; #if 0 DXSetClippedObjects((Clipped)o,n,NULL); #endif } return object; error: return ERROR; }