Recent

Author Topic: Filer.DefineProperty being skipped  (Read 247 times)

lazypas

  • Newbie
  • Posts: 1
Filer.DefineProperty being skipped
« on: October 18, 2019, 02:33:27 am »
Hello everyone and thank for taking the time to read my post.

I have a generally followed the following link (https://wiki.lazarus.freepascal.org/TCollection#Streaming) when setting up my classes.

Streaming has been working well until I've decided to depreciate some properties.

Presently, I'm struggling with depreciating one published property that is a class derived from TCollectionItem.

I'm trying to depreciate the published property by overriding DefineProperties(Filer: TFiler) and adding an appropriate Filer.DefineProperty function in DefineProperties.

This approach has been working with other properties being depreciated but not with this one.

When I step through the calling of the DefineProperties procedure (which is automatically called by ReadComponentFromBinaryStream), the program will enter the other Filer.DefineProperty procedures for the other depreciated properties but skips the problem one.

On exiting the DefineProperties procedure, the program throws a 'EReadError' error with the message, 'Unknown property: "pdfDocument"'

The DefineProperty in DefineProperties that is being skipped over reads,

Code: Pascal  [Select]
  1. Filer.DefineProperty('pdfDocument', @readPdfDocument, nil, False);

Here is my overrided DefineProperties procedure

Code: Pascal  [Select]
  1. procedure TPdfDocumentItem.DefineProperties(Filer: TFiler);
  2. begin
  3.   ShowMessage('DefineProperties is triggered');
  4.   inherited;
  5.   ShowMessage('Finished inherited');
  6.   Filer.DefineProperty('paragraphList', @readParagraphList, nil, False);
  7.   TReader(Filer).OnPropertyNotFound:=@describedPropertyNotFound;  {New code, see UPDATE below }
  8.   Filer.DefineProperty('pdfDocument', @readPdfDocument, nil, False);
  9.   ShowMessage('Finished DefineProperty calls');
  10. end;


Code: Pascal  [Select]
  1. procedure TPdfDocumentItem.describedPropertyNotFound( Reader: TReader; Instance: TPersistent; var PropName: string; IsPath: Boolean; var Handled: Boolean;  var Skip: Boolean );
  2. begin
  3.   {
  4.   ShowMessage(Reader.ClassName);
  5.   ShowMessage('"' + PropName + '"');
  6.   ShowMessage(Instance.ClassName);
  7.   ShowMessage(BoolToStr(IsPath));
  8.   ShowMessage(BoolToStr(Handled));
  9.   ShowMessage(BoolToStr(Skip));
  10.   }
  11.  
  12.   { Manually handle call }
  13.   case PropName of
  14.      'pdfDocument':
  15.        begin;
  16.          { readFinalPdfDocument(Reader); } { It will call if uncommented but I'm not sure how to manually handle the reading of a class derived from CollectionItem }
  17.          Skip := True; { Skip until problem fixed }
  18.        end;
  19.   end;
  20. end;

Any suggestions would be really appreciated.


UPDATE (18/10/2019)

In DefineProperties, I've modified the OnPropertyNotFound property of Filer to call procedure for debugging purposes. The code above has been modified to reflect such change. I've also added the procedure describedPropertyNotFound for such purposes.

SECOND UPDATE (18/10/2019) (Possible solution?)

I think I have solved the problem. Subject to someone confirming this, I will mark the question as solved.

First, it appears that the problem is being caused by the problematic property being a class derived from the TCollectionItem class. In other words, there is a class derived from the TCollectionItem class that has a published property that is also derived from the TCollectionItem class. In hindsight, the latter published property should have been a TCollection so that TReader.ReadCollection could be called.

Second, because of that problem, it appears that the work around proposed above does not always work. Oddly, the DefineProperties procedure in another TCollectionItem will need to handle it (or discard it) [and this takes some debugging]. Plus you will need to revert to the original OnPropertyNotFound property once you have dealt with the problematic published property.

The solution I have come up is as follows-

1. Declare a TMigrationHelper class

Code: Pascal  [Select]
  1. TMigrationHelper = class
  2.     public
  3.       originalPropertyNotFoundEventHandler: TPropertyNotFoundEvent;
  4.       lastInstanceInspectedWhenPropertyNotFound: string;
  5.       procedure describedPropertyNotFound( Reader: TReader; Instance: TPersistent; var PropName: string; IsPath: Boolean; var Handled: Boolean;  var Skip: Boolean );
  6.   end;

2. Declare the migrationHelper as a global variable (just to make life easier and to avoid worrying about scope).

Code: Pascal  [Select]
  1. var
  2.    migrationHelper: TMigrationHelper;

3. Immediately before you start your read with ReadComponentFromBinaryStream, initialise TMigrationHelper. Also Free it after you've finished reading.

Code: Pascal  [Select]
  1. migrationHelper := TMigrationHelper.Create;
  2. ReadComponentFromBinaryStream(......);
  3. [code=pascal]migrationHelper.Free;

4. In the relevant DefineProperties procedures, change the OnPropertyNotFound property of the Filer object after you had read the other published properties without issue. For example,

Code: Pascal  [Select]
  1. procedure TCustomClass.DefineProperties(Filer: TFiler);
  2. begin
  3.  inherited;
  4.  Filer.DefineProperty('documentTitle', @readTitleString, nil, False);
  5.  
  6.  migrationHelper.originalPropertyNotFoundEventHandler:=TReader(Filer).OnPropertyNotFound;
  7.  TReader(Filer).OnPropertyNotFound:=@migrationHelper.describedPropertyNotFound;
  8. end

5. Implement TMigrationHelper.describedPropertyNotFound. I've only limited the example below to two classes and two properties, but you should get the idea.
Code: Pascal  [Select]
  1. procedure TMigrationHelper.describedPropertyNotFound( Reader: TReader; Instance: TPersistent; var PropName: string; IsPath: Boolean; var Handled: Boolean;  var Skip: Boolean );
  2. begin
  3.    { Check that the same class is not being dealt with }
  4.    if ( lastInstanceInspectedWhenPropertyNotFound = Instance.ClassName ) then
  5.    begin
  6.       ShowMessage('ERROR FIX NOW File: ' +
  7.                   {$I %FILE%}
  8.                   +', line: ' +
  9.                   {$I %LINE%}
  10.                   +' Not implemented.');
  11.       ShowMessage('Error! Unhandled property => "' + PropName + '" in ' + Instance.ClassName);
  12.    end
  13. else begin
  14.    case Instance.ClassName of
  15.      'TCustomClass':
  16.    begin
  17.       case PropName of
  18.         'pdfDocument':
  19.       begin
  20.          Handled := True;
  21.          Skip := True; { Skip after reading }
  22.       end
  23.       else
  24.       begin
  25.          { Restore original property handler }
  26.          Handled := False;
  27.          Skip:= False;
  28.          Reader.OnPropertyNotFound:=originalPropertyNotFoundEventHandler;
  29.          { Re-entry back into original handler }
  30.          lastInstanceInspectedWhenPropertyNotFound := Instance.ClassName;
  31.          TSubmissionsItem(Instance).DefineProperties(TFiler(Reader));
  32.       end;
  33.       end;
  34.    end;
  35.      'TCustomClassTwo':
  36.    begin
  37.       case PropName of
  38.         'secondPdfDocument':
  39.       begin
  40.          Handled := True;
  41.          Skip := True; { Skip after reading }
  42.       end
  43.       else
  44.       begin
  45.          { Restore original property handler }
  46.          Handled := False;
  47.          Skip:= False;
  48.          Reader.OnPropertyNotFound:=originalPropertyNotFoundEventHandler;
  49.          { Re-entry back into original handler }
  50.          lastInstanceInspectedWhenPropertyNotFound := Instance.ClassName;
  51.          TCrossExaminationItem(Instance).DefineProperties(TFiler(Reader));
  52.       end;
  53.       end;
  54.    end;
  55.    end;
  56. end;
  57. end;

I hope this helps someone who encounters the same problem.

PS I don't think this is a bug. It only happened because of misuse of TCollection and TCollectionItem and I needed to work around it because I had existing data.
« Last Edit: October 18, 2019, 11:13:15 am by lazypas »