2009-04-10

Prism AOP - 3

I am playing with Prism’s new Aspect Oriented Programming feature. As a learning exercise I am implementing some of the features ECO requires on a class. If all goes well I will end up with aspects I can apply to plain classes and have them run in ECO. One of those features is the ILoopBack2 interface.

In order to tackle this interface a small piece at a time I have created my own ILoopBack2 interface, so far with only one method.

ILoopBack2 = public interface
  function GetValueByIndex(I: Integer): System.Object;
end;


This method allows the ECO to evaluate OCL (Object Constraint Language) expressions such as "self.FirstName" and have them route via the class’s property, just in case there is any logic in the getter. By implementing an interface which uses an index to identify the property to read it is possible to avoid reflection.

So, getting on with it, here is my class definition

type
  [aspect: EcoAspects.BusinessClass(’DomainClasses.Package1’)]
  Person = public class
  private
    FFirstName: String;
    FLastName: String;
  protected
  public
    property FirstName : String read FFirstName write FFirstName;
    property LastName : String read FLastName write FLastName;
  end;


As you can see this is a very plain class, no magic at all except for the aspect I have applied to it. And now some code which uses the class

P := new Person();
P.FirstName := ’Peter’;
P.LastName := ’Morris’;
Console.WriteLine((P as ILoopBack2).GetValueByIndex(0).ToString());
Console.WriteLine((P as ILoopBack2).GetValueByIndex(1).ToString());


Output
  Peter
  Morris



From the example code it is evident that an instance of the Person class is castable to ILoopBack2 and also implements the GetValueByIndex method.

To achieve this the my needs to

1: Add the interface to the class
2: Generate a method
3: Implement the method.

The aspect class is a System.Attribute which implements ITypeInterfaceDecorator.

type
  BusinessClassAttribute = public class(System.Attribute, ITypeInterfaceDecorator)
    ...
  end;

procedure BusinessClassAttribute.HandleInterface(
  Services: RemObjects.Oxygene.Cirrus.IServices;
  aType: RemObjects.Oxygene.Cirrus.ITypeDefinition);
begin
  AddClassToPackage(Services, aType);
  AddILoopBack2Interface(Services, aType);
end;


The implementation for AddILoopBack2Interface consists of the following


procedure BusinessClassAttribute.AddILoopBack2Interface(Services: IServices; aType: ITypeDefinition);
var
  //The type which represents ILoopBack2
  ILoopBack2Type: IType;

  //The type which represents the method GetValueByIndex on ILoopBack2
  ILoopBack2_GetValueByIndex: IMethod;

  //The new method I will create on the Person class
  GetValueByIndexMethod: IMethodDefinition;

  //A case statement I will use in the method
  IndexCaseStatement: CaseStatement;
begin
  //Find the type for ILoopBack2
  ILoopBack2Type := Services.FindType(’EcoSupport.ILoopBack2’);

  //If not found then we are missing a reference, report a compiler error
  if (ILoopBack2Type = nil) then
  begin
    Services.EmitError(’EcoSupport.ILoopBack2 not found, are you missing an assembly reference?’);
    exit;
  end;

  //Get a method reference to ILoopBack2.GetValueByIndex
  ILoopBack2_GetValueByIndex := ILoopBack2Type.GetMethods(’GetValueByIndex’)[0];



This initial code just does some basic checking to ensure the assembly with the interface is referenced by the target assembly (the assembly which owns the Person class). To hide the implementation of GetValueByIndex I want to make the implementing method protected, In Prism you have to explicitly link the interface method to the implementation method if it is not public. For example


type
  IMyInterface = public interface
    procedure Method1();
    procedure Method2();
  end;

  SomeClass = class(System.Object, IMyInterface)
  private
    //Private, so much be explicit about what it implements
    procedure Method1; implements IMyInterface.Method1;
  public
    //Public, the link to the interface method is inferred
    procedure Method2;
  end;



Anyway, back to the important code. Here is the next part of the AddILoopBack2Interface method.

  //Add the interface to Person
  aType.AddInterface(ILoopBack2Type);

  //Create a method which returns a System.Object
  GetValueByIndexMethod := aType.AddMethod(’GetValueByIndex’, Services.GetType(’System.Object’), false);

  //Add a parameter "I: Integer"
  GetValueByIndexMethod.AddParameter(’I’, ParameterModifier.In, Services.GetType(’System.Int32’));

  //Make the method protected + virtual
  GetValueByIndexMethod.Virtual := VirtualMode.Virtual;
  GetValueByIndexMethod.Visibility := Visibility.Protected;

  //Explicitly link the method to ILoopBack2.GetValueByIndex, because the method is not public
  aType.AddImplements(GetValueByIndexMethod, ILoopBack2Type, ILoopBack2_GetValueByIndex);



The implementation of this method will basically be

  case I of
    0: exit FirstName;
    1: exit LastName;
  end;


To do this we need a case statement.

  //Build case statement
  IndexCaseStatement := new CaseStatement();
  IndexCaseStatement.What := GetValueByIndexMethod.GetParameter(’I’);


Next we need to loop through each property on Person and create the case item for it.

  //The case index.  We wont include array properties so this keeps
  //track of how many properties we have added to the case statement
  var CaseIndex: Integer := 0;

  //Loop through each property declared on the type Person
  for PropertyIndex : Integer := 0 to aType.PropertyCount - 1 do
  begin
    //Get a reference to the property
    var Prop : IProperty := aType.GetProperty(PropertyIndex);

    //Only continue if the property has no parameters (is not an array property)
    //and only if it is possible to read this property
    if (Prop.ParameterCount = 0) and (Prop.ReadMethod <> nil) then
    begin
      //Get the method which reads the property value
      var ReadProperty : ProcValue := new ProcValue(new SelfValue(), Prop.ReadMethod);

      //Construct an "exit" statement which returns the value of the property
      var ExitMethod : ExitStatement := new ExitStatement(ReadProperty);

      //Construct a "case item" which executes the exit statement for the
      //given index
      var IndexCaseItem : CaseItem := new CaseItem(ExitMethod, CaseIndex);

      //Add the case item to the case statement
      IndexCaseStatement.Items.Add(IndexCaseItem);

      //Increment the case index for the next property added
      CaseIndex := CaseIndex + 1;
    end;
  end;


Now I have a case statement which I have constructed dynamically I need to assign it as the body of the method which implements ILoopBack2.GetValueByIndex

  GetValueByIndexMethod.SetBody(Services, 
    method begin
      unquote(IndexCaseStatement);
    end);


The "unquote" statement tells the compiler to expand the statement into "real" code.

That’s the lot. This effectively take the following code

type
  [aspect: EcoAspects.BusinessClass(’DomainClasses.Package1’)]
  Person = public class
  private
    FFirstName: String;
    FLastName: String;
  protected
  public
    property FirstName : String read FFirstName write FFirstName;
    property LastName : String read FLastName write FLastName;
  end;


and compiles it as if I had written

type
  Person = public class(System.Object, ILoopBack2)
  private
    FFirstName: String;
    FLastName: String;
  protected
    function GetValueByIndex(I: Integer) : System.Object; virtual;
  public
    property FirstName : String read FFirstName write FFirstName;
    property LastName : String read FLastName write FLastName;
  end;

implementation

function Person.GetValueByIndex(I: Integer) : System.Object;
begin
  case Index of
    0 : exit FirstName;
    1 : exit LastName;
  end;
end;



I hope like me you see the benefits of this!

No comments: