(*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *)
unit Thrift.Serializer;

{$I Thrift.Defines.inc}

interface

uses
  {$IFDEF OLD_UNIT_NAMES}
  Classes, Windows, SysUtils,
  {$ELSE}
  System.Classes, Winapi.Windows, System.SysUtils,
  {$ENDIF}
  Thrift.Configuration,
  Thrift.Protocol,
  Thrift.Transport,
  Thrift.Stream;


type
  // Generic utility for easily serializing objects into a byte array or Stream.
  TSerializer = class
  strict private
    FStream    : TThriftMemoryStream;
    FTransport : ITransport;
    FProtocol  : IProtocol;

  public
    constructor Create( const aProtFact  : IProtocolFactory = nil;    // defaults to TBinaryProtocol
                        const aTransFact : ITransportFactory = nil;
                        const aConfig   : IThriftConfiguration = nil);

    // DTOR
    destructor Destroy;  override;

    // Serialize the Thrift object.
    function  Serialize( const input : IBase) : TBytes;  overload;
    procedure Serialize( const input : IBase; const aStm : TStream);  overload;
  end;


  // Generic utility for easily deserializing objects from byte array or Stream.
  TDeserializer = class
  strict private
    FStream    : TThriftMemoryStream;
    FTransport : ITransport;
    FProtocol  : IProtocol;

  public
    constructor Create( const aProtFact  : IProtocolFactory = nil;    // defaults to TBinaryProtocol
                        const aTransFact : ITransportFactory = nil;
                        const aConfig    : IThriftConfiguration = nil);

    // DTOR
    destructor Destroy;  override;

    // Deserialize the Thrift object data.
    procedure Deserialize( const input : TBytes; const target : IBase);  overload;
    procedure Deserialize( const input : TStream; const target : IBase);  overload;

    // helper
    property  Protocol  : IProtocol read FProtocol;
    property  Transport : ITransport read FTransport;
    property  Stream    : TThriftMemoryStream read FStream;
  end;



implementation


{ TSerializer }


constructor TSerializer.Create( const aProtFact  : IProtocolFactory;
                                const aTransFact : ITransportFactory;
                                const aConfig   : IThriftConfiguration);
var adapter : IThriftStream;
    protfact : IProtocolFactory;
begin
  inherited Create;

  FStream    := TThriftMemoryStream.Create;
  adapter    := TThriftStreamAdapterDelphi.Create( FStream, FALSE);

  FTransport := TStreamTransportImpl.Create( nil, adapter, aConfig);
  if aTransfact <> nil then FTransport := aTransfact.GetTransport( FTransport);

  if aProtFact <> nil
  then protfact := aProtFact
  else protfact := TBinaryProtocolImpl.TFactory.Create;
  FProtocol := protfact.GetProtocol( FTransport);

  if not FTransport.IsOpen
  then FTransport.Open;
end;


destructor TSerializer.Destroy;
begin
  try
    FProtocol  := nil;
    FTransport := nil;
    FreeAndNil( FStream);
  finally
    inherited Destroy;
  end;
end;


function TSerializer.Serialize( const input : IBase) : TBytes;
// Serialize the Thrift object into a byte array. The process is simple,
// just clear the byte array output, write the object into it, and grab the
// raw bytes.
var iBytes : Int64;
begin
  try
    FStream.Size := 0;
    input.Write( FProtocol);
    FTransport.Flush;

    SetLength( result, FStream.Size);
    iBytes := Length(result);
    if iBytes > 0
    then Move( FStream.Memory^, result[0], iBytes);
  finally
    FStream.Size := 0;  // free any allocated memory
  end;
end;


procedure TSerializer.Serialize( const input : IBase; const aStm : TStream);
// Serialize the Thrift object into a byte array. The process is simple,
// just clear the byte array output, write the object into it, and grab the
// raw bytes.
const COPY_ENTIRE_STREAM = 0;
begin
  try
    FStream.Size := 0;
    input.Write( FProtocol);
    FTransport.Flush;

    aStm.CopyFrom( FStream, COPY_ENTIRE_STREAM);
  finally
    FStream.Size := 0;  // free any allocated memory
  end;
end;


{ TDeserializer }


constructor TDeserializer.Create( const aProtFact  : IProtocolFactory;
                                  const aTransFact : ITransportFactory;
                                  const aConfig    : IThriftConfiguration);
var adapter : IThriftStream;
    protfact : IProtocolFactory;
begin
  inherited Create;

  FStream := TThriftMemoryStream.Create;
  adapter := TThriftStreamAdapterDelphi.Create( FStream, FALSE);

  FTransport := TStreamTransportImpl.Create( adapter, nil, aConfig);
  if aTransfact <> nil then FTransport := aTransfact.GetTransport( FTransport);

  if aProtFact <> nil
  then protfact := aProtFact
  else protfact := TBinaryProtocolImpl.TFactory.Create;
  FProtocol := protfact.GetProtocol( FTransport);

  if not FTransport.IsOpen
  then FTransport.Open;
end;


destructor TDeserializer.Destroy;
begin
  try
    FProtocol  := nil;
    FTransport := nil;
    FreeAndNil( FStream);
  finally
    inherited Destroy;
  end;
end;


procedure TDeserializer.Deserialize( const input : TBytes; const target : IBase);
// Deserialize the Thrift object data from the byte array.
var iBytes : Int64;
begin
  try
    iBytes := Length(input);
    FStream.Size := iBytes;
    if iBytes > 0 then begin
      Move( input[0], FStream.Memory^, iBytes);
      Transport.ResetMessageSizeAndConsumedBytes();  // size has changed
      Transport.UpdateKnownMessageSize(iBytes);
    end;

    target.Read( FProtocol);
  finally
    FStream.Size := 0;  // free any allocated memory
  end;
end;


procedure TDeserializer.Deserialize( const input : TStream; const target : IBase);
// Deserialize the Thrift object data from the byte array.
const COPY_ENTIRE_STREAM = 0;
var before : Int64;
begin
  try
    if Assigned(input) then begin
      before := FStream.Position;
      ASSERT( before = 0);
      FStream.CopyFrom( input, COPY_ENTIRE_STREAM);
      FStream.Position := before;
      Transport.ResetMessageSizeAndConsumedBytes();  // size has changed
      Transport.UpdateKnownMessageSize(FStream.Size);
    end;

    target.Read( FProtocol);
  finally
    FStream.Size := 0;  // free any allocated memory
  end;
end;


end.

