unit FilerHelper;
//=== File Prolog ============================================================
//    This code was developed by RiverSoftAVG.
//
//--- Notes ------------------------------------------------------------------
//
//--- Development History  ---------------------------------------------------
//
//      11/2015 T. Grubb
//              Initial version.
//
//      File Contents:
//           TReaderHelper          Helper class for TReader
//           TWriterHelper          Helper class for TWriter
//
//--- Warning ----------------------------------------------------------------
//  This software is property of RiverSoftAVG. Unauthorized use or
//  duplication of this software is strictly prohibited. Authorized users
//  are subject to the following restrictions:
//    * RiverSoftAVG is not responsible for
//      any consequence of the use of this software.
//    * The origin of this software must not be misrepresented either by
//      explicit claim or by omission.
//    * Altered versions of this software must be plainly marked as such.
//    * This notice may not be removed or altered.
//
//      © 2015-2016, Thomas G. Grubb
//
//=== End File Prolog ========================================================

interface

uses
  Classes, SysUtils;

resourcestring
  SClassnameMismatch = 'Stream read error: %s does not match expected %s class name';

type
  /// <summary>
  ///   Helper class for TReader component
  /// </summary>
  /// <seealso cref="ReadRootPersistent" />
  /// <seealso cref="ReadPersistent" />
  /// <seealso cref="TWriterHelper" />
  TReaderHelper = class helper for TReader
  private
    { private declarations }
  protected
    { protected declarations }
  public
    { public declarations }
    /// <summary>
    ///   <para>
    ///     Reads the TPersistent object specified by Instance from a stream
    ///   </para>
    ///   <para>
    ///     Use the ReadPersistent method when you want to read a defined
    ///     property (specified by TFiler.DefineProperties method) that is a
    ///     TPersistent from a stream <br />
    ///   </para>
    /// </summary>
    /// <example>
    ///   <para>
    ///     To create a defined TPersistent property for your component,
    ///     include the FilerHelper unit in your uses statement, and then
    ///     override the protected DefineProperties method:
    ///   </para>
    ///   <code lang="Delphi">TMyComponent = class(TComponent)
    /// private
    ///   { private declarations }
    /// protected
    ///   { protected declarations }
    ///   procedure ReadMyProp(Reader: TReader);
    ///   procedure WriteMyProp(Writer: TWriter);
    ///   procedure DefineProperties(Filer: TFiler); override;
    /// public
    ///   { public declarations }
    ///   property MyProp: TPersistent read FMyProp write SetMyProp;
    /// published
    ///   { published declarations }
    /// end;
    /// [...]
    /// procedure TMyComponent.DefineProperties(Filer: TFiler);
    /// begin
    ///   inherited DefineProperties(Filer);
    ///   Filer.DefineProperty('MyProp', ReadMyProp, WriteMyProp, True);
    /// end;</code>
    ///   <para>
    ///     Then, create your reader procedure and writer procedures:
    ///   </para>
    ///   <code lang="Delphi">procedure TMyComponent.ReadMyProp(Reader: TReader);
    /// begin
    ///   Reader.ReadPersistent(FMyProp);
    /// end;
    /// procedure TMyComponent.WriteMyProp(Writer: TWriter);
    /// begin
    ///   Writer.WritePersistent(FMyProp);
    /// end;</code>
    /// </example>
    /// <seealso cref="TWriterHelper.WritePersistent" />
    procedure ReadPersistent( Instance: TPersistent );
    /// <summary>
    ///   Reads the TPersistent object specified by Instance from a stream as
    ///   the <b>root</b> object
    ///   <note type="warning">
    ///     Do <b>not</b> use this method when you are trying to write a
    ///     TPersistent object out as part of another component. This method
    ///     is intended for when you want to save and load the Instance
    ///     object as the top-level object of a stream. For example, as a
    ///     SaveToStream/LoadFromStream method pair for the Instance
    ///   </note>
    /// </summary>
    /// <example>
    ///   <code lang="Delphi">procedure TMyPersistent.LoadFromStream(const Stream: TStream);
    /// var
    ///   Reader: TReader;
    /// begin
    ///   Reader := TReader.Create(Stream, 4096);
    ///   try
    ///     Reader.ReadRootPersistent(Self);
    ///   finally
    ///     Reader.Free;
    ///   end;
    /// end;
    ///
    /// procedure TMyPersistent.SaveToStream(const Stream: TStream);
    /// var
    ///   Writer: TWriter;
    /// begin
    ///   Writer := TWriter.Create(Stream, 4096);
    ///   try
    ///     Writer.WriteRootPersistent(Self);
    ///   finally
    ///     Writer.Free;
    ///   end;
    /// end;</code>
    /// </example>
    /// <seealso cref="TWriterHelper.WriteRootPersistent" />
    procedure ReadRootPersistent( Instance: TPersistent );
  end;

  /// <summary>
  ///   Helper class for TWriter component
  /// </summary>
  /// <seealso cref="WriteRootPersistent" />
  /// <seealso cref="WritePersistent" />
  /// <seealso cref="TReaderHelper" />
  TWriterHelper = class helper for TWriter
  private
    { private declarations }
  protected
    { protected declarations }
  public
    { public declarations }
    /// <summary>
    ///   <para>
    ///     Writes the TPersistent object specified by Instance to a stream
    ///   </para>
    ///   <para>
    ///     Use the WritePersistent method when you want to write a defined
    ///     property (specified by TFiler.DefineProperties method) that is a
    ///     TPersistent to a stream <br />
    ///   </para>
    /// </summary>
    /// <example>
    ///   <para>
    ///     To create a defined TPersistent property for your component,
    ///     include the FilerHelper unit in your uses statement, and then
    ///     override the protected DefineProperties method:
    ///   </para>
    ///   <code lang="Delphi">TMyComponent = class(TComponent)
    /// private
    ///   { private declarations }
    /// protected
    ///   { protected declarations }
    ///   procedure ReadMyProp(Reader: TReader);
    ///   procedure WriteMyProp(Writer: TWriter);
    ///   procedure DefineProperties(Filer: TFiler); override;
    /// public
    ///   { public declarations }
    ///   property MyProp: TPersistent read FMyProp write SetMyProp;
    /// published
    ///   { published declarations }
    /// end;
    /// [...]
    /// procedure TMyComponent.DefineProperties(Filer: TFiler);
    /// begin
    ///   inherited DefineProperties(Filer);
    ///   Filer.DefineProperty('MyProp', ReadMyProp, WriteMyProp, True);
    /// end;</code>
    ///   <para>
    ///     Then, create your reader procedure and writer procedures:
    ///   </para>
    ///   <code lang="Delphi">procedure TMyComponent.ReadMyProp(Reader: TReader);
    /// begin
    ///   Reader.ReadPersistent(FMyProp);
    /// end;
    /// procedure TMyComponent.WriteMyProp(Writer: TWriter);
    /// begin
    ///   Writer.WritePersistent(FMyProp);
    /// end;</code>
    /// </example>
    /// <seealso cref="TReaderHelper.ReadPersistent" />
    procedure WritePersistent( Instance: TPersistent );
    /// <summary>
    ///   Writes the TPersistent object specified by Instance to a stream as
    ///   the <b>root</b> object
    ///   <note type="warning">
    ///     Do <b>not</b> use this method when you are trying to write a
    ///     TPersistent object out as part of another component. This method
    ///     is intended for when you want to save the Instance object as the
    ///     top-level object of a stream. For example, as a
    ///     SaveToStream/LoadFromStream method pair for the Instance
    ///   </note>
    /// </summary>
    /// <example>
    ///   <code lang="Delphi">procedure TMyPersistent.LoadFromStream(const Stream: TStream);
    /// var
    ///   Reader: TReader;
    /// begin
    ///   Reader := TReader.Create(Stream, 4096);
    ///   try
    ///     Reader.ReadRootPersistent(Self);
    ///   finally
    ///     Reader.Free;
    ///   end;
    /// end;
    ///
    /// procedure TMyPersistent.SaveToStream(const Stream: TStream);
    /// var
    ///   Writer: TWriter;
    /// begin
    ///   Writer := TWriter.Create(Stream, 4096);
    ///   try
    ///     Writer.WriteRootPersistent(Self);
    ///   finally
    ///     Writer.Free;
    ///   end;
    /// end;</code>
    /// </example>
    /// <seealso cref="TReaderHelper.ReadRootPersistent" />
    procedure WriteRootPersistent( Instance: TPersistent );
    procedure WritePropertiesOnly( Instance: TPersistent );
  end;

implementation

uses
  TypInfo;

type
  TPersistentCrack = class(TPersistent);

{ TReaderHelper }

procedure TReaderHelper.ReadPersistent(Instance: TPersistent);
begin
  // read vaCollection
  CheckValue(vaCollection);
  // read list of items, in this case, only one, the top-level instance
  ReadListBegin;
  while not EndOfList do
    ReadProperty(Instance);
  ReadListEnd;
  ReadListEnd;
end;

procedure TReaderHelper.ReadRootPersistent(Instance: TPersistent);
var
  aClassName: String;
begin
  // read signature
  ReadSignature;
  // read class
  aClassName := ReadStr;
  if aClassName <> Instance.ClassName then
    raise EReadError.CreateFmt(SClassnameMismatch, [aClassName, Instance.ClassName]);
  // read name (none)
  ReadStr;
  // read list of properties
  while not EndOfList do
    ReadProperty(Instance);
  ReadListEnd;
  ReadListEnd;
end;

{ TWriterHelper }

procedure TWriterHelper.WritePersistent(Instance: TPersistent);
begin
  // write out the Instance properties as a collection.  This forces the writer
  // to zero out the property path
  WriteValue(vaCollection);
  // collection is a list of items
  // we are writing out a collection of one item, the Instance
  WriteListBegin;
  WriteProperties(Instance);
  WriteListEnd;
  WriteListEnd; // match vaCollection
end;

procedure TWriterHelper.WritePropertiesOnly(Instance: TPersistent);
var
  I, Count: Integer;
  PropInfo: PPropInfo;
  PropList: PPropList;
begin
  Count := GetTypeData(Instance.ClassInfo)^.PropCount;
  if Count > 0 then
  begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
      GetPropInfos(Instance.ClassInfo, PropList);
      for I := 0 to Count - 1 do
      begin
        PropInfo := PropList^[I];
        if PropInfo = nil then
          Break;
        if (PropInfo^.PropType^.Kind in tkProperties) and IsStoredProp(Instance, PropInfo) then
          WriteProperty(Instance, PropInfo);
      end;
    finally
      FreeMem(PropList, Count * SizeOf(Pointer));
    end;
  end;
  TPersistentCrack(Instance).DefineProperties(Self);
end;

procedure TWriterHelper.WriteRootPersistent(Instance: TPersistent);
begin
  // mark this as a delphi stream
  WriteSignature;
  // class to write
  WriteUTF8Str(Instance.ClassName);
  // name of class
  WriteUTF8Str('');
  // write properties
  WritePropertiesOnly(Instance);
  WriteListEnd;
  WriteListEnd;
end;

end.
