Generic Routine to Select Bold Elements

The SelectBoldElement function provides a reusable and flexible way to select objects or lists.

function SelectBoldElement( Root: TBoldElement; sOCL: string; Params: array of const;

                            EClass: ExceptClass; ErrMsg: string;

                            Container:TBoldElement=nil;

                            InPS: Boolean = False;

                            Vars: TBoldExternalVariableList = nil ): TBoldElement;

const

   cLastElement = $FFFF;

var

   ocl : string;

   id : TBoldIndirectElement;

   ReturnListElementX : Boolean;

   Element : Integer;

begin

   result := nil;

   Element := 0;

   ReturnListElementX := False;



   if Root = nil then

      Root := TBoldSystem.DefaultSystem;



   Assert( not (Container is TBoldObject), 'SelectBoldElement cannot return BoldObjects in the container parameter' );

   Assert( Assigned( Root ), 'Attempt to Select without an open system' );



   id := TBoldIndirectElement.Create;

   try



      ocl := Format( sOCL, Params );



      // This code allows this routine to transparently select a 'first' or 'last' object in the PS

      if (UpperCase(Copy( ocl, Length(ocl)-6, 8)) = '->FIRST') and (InPS) then begin

         SetLength( ocl, Length(ocl)-7);

         ReturnListElementX := true;

      end;

      if (UpperCase(Copy( ocl, Length(ocl)-5, 8)) = '->LAST') and (InPS) then begin

         SetLength( ocl, Length(ocl)-6);

         ReturnListElementX := true;

         Element := cLastElement;

      end;



      Root.EvaluateExpression( ocl, id, InPS, Vars );



      // if nothing was selected, get out, either by raising the user-supplied error, or quietly

      if (not Assigned( id.Value )) or

         ((id.Value is TBoldList) and (TBoldList(id.Value).Count = 0)) then

         if Assigned(EClass) then

            raise EClass.Create( ErrMsg )

         else

            Exit; // Nothing to return, get out



      // Owned values can't be returned in result, so must have a container, unless this is an InPS selection (ReturnListElementX)

      if id.OwnsValue and

         (not Assigned( Container )) and

         (not ReturnListElementX) then

         // You may want to replace this error class with something more specific to make it easier to trap

         raise Exception.CreateFmt( 'SelectBoldElement cannot return selected type of %s', [id.Value.ClassName] );



      if id.Value is TBoldObject then begin

         // BoldObjects are owned by the system and can only go back in result

         result := id.Value;

      end else if id.Value is TBoldObjectReference then begin

         // SingleLinks hold owned objects that go back in result

         result := (id.Value as TBoldObjectReference).BoldObject;

      end else if (id.Value is TBoldList) then begin

         // Some lists are owned and can go back in result


         if not id.OwnsValue then

            result := id.Value;



         // An InPS selection always returns a list, if the caller wanted the first element, this will be set

         if ReturnListElementX then begin

            Assert( TBoldList(id.Value).Count > 0, 'Logic failure, SelectBoldElement' );

            if Element = cLastElement then

               result := TBoldList(id.Value)[TBoldList(id.Value).Count-1]

            else

               result := TBoldList(id.Value)[0]; // The case of zero count was already taken care of

         end else

            // If the user has passed a container for the result, put it there

            if (Container is TBoldList) then

               TBoldList(Container).AddList( TBoldList(id.Value) );

      end else if (id.Value is TBoldAttribute) then begin

         if Assigned( Container ) and (Container is TBoldAttribute) then

            // this depends on the caller to get the classes right, will raise an exception if incorrect

            TBoldAttribute( Container ).Assign( id.Value );



         result := id.Value;

      end;

   finally

      id.Free;

   end;

end;



SelectBoldElement can be used directly, but it is useful to create wrappers to simplify calling it. For instance, if one often needed to retrieve integer values from a simple select, a function like the following would be useful:



function SelectInteger( sOCL: string; Default:Integer=0 ): Integer;

var

   anInt : TBAInteger;

begin

   anInt := TBAInteger.Create;

   try

      try

         SelectBoldElement( nil, sOCL, [], Exception, '', anInt );

         result := anInt.AsInteger;

      except

         result := Default;

      end;

   finally

      anInt.Free;

   end;

end;



// This example searches a product database by UPC and PLU, demonstrating

// the use of the EClass parameter.

class function TProduct.GetProduct( PLU, UPC :string; ):TProduct;

const

   cPLUOCL = 'Product.allInstances->select( plu =''%s'')->first';

   cUPCOCL = 'Product.allInstances->select( upc =''%s'')->first';

begin

   // attempt to locate by UPC first

   result := SelectBoldElement( nil, cUPCOCL, [ UPC ],

                                nil, '') as TProduct;



   if not Assigned( result ) then

      // Didn't find the UPC, try the PLU

      result := SelectBoldElement( nil, cPLUOCL, [ PLU],

                                   EProductNotFound, 'Product not found') as TProduct;

      // This one includes an exception class, so if the product still isn't found,

      // the EProductNotFound error will be raised and handled elsewhere

end;







// This silly method of handling permissions demonstrates selection using

// a root other than the system.

function TEmployee.GetPermissions( Category: string ): TPermissionList;

const

   cOCL = 'permissions->select( category = ''%s'' )';

begin

   SelectBoldElement( self, cOCL, [Category], nil, '', result );

end;

 

Share this article!

Follow us!

Find more helpful articles: