{
    pas2jni - JNI bridge generator for Pascal.

    Copyright (c) 2013 by Yury Sidorov.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 ****************************************************************************}

unit def;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, contnrs;

type
  TDefType = (dtNone, dtUnit, dtClass, dtProc, dtField, dtProp, dtParam, dtVar,
              dtType, dtConst, dtProcType, dtEnum, dtSet, dtPointer, dtArray,
              dtJniObject, dtJniEnv, dtClassRef);

  TDefClass = class of TDef;
  { TDef }

  TDef = class
  private
    FAliasName: string;
    FRefCnt: integer;
    FItems: TObjectList;
    FInSetUsed: boolean;
    procedure CheckItems;
    function GetAliasName: string;
    function GetCount: integer;
    function GetIsUsed: boolean;
    function GetItem(Index: Integer): TDef;
    procedure SetItem(Index: Integer; const AValue: TDef);
  protected
    procedure SetIsUsed(const AValue: boolean); virtual;
    function ResolveDef(d: TDef; ExpectedClass: TDefClass = nil): TDef;
    procedure AddRef;
    procedure DecRef;
    procedure SetExtUsed(ExtDef: TDef; AUsed: boolean; var HasRef: boolean);
    function ShouldUseChild(d: TDef): boolean; virtual;
  public
    DefType: TDefType;
    DefId: integer;
    SymId: integer;
    Name: string;
    Parent: TDef;
    Tag: integer;
    IsPrivate: boolean;

    constructor Create; virtual; overload;
    constructor Create(AParent: TDef; AType: TDefType); virtual; overload;
    destructor Destroy; override;
    function Add(ADef: TDef): integer;
    function Insert(Index: integer; ADef: TDef): integer;
    function FindDef(ADefId: integer; Recursive: boolean = True): TDef;
    procedure ResolveDefs; virtual;
    procedure SetNotUsed;
    function GetRefDef: TDef; virtual;
    function GetRefDef2: TDef; virtual;
    property Items[Index: Integer]: TDef read GetItem write SetItem; default;
    property Count: integer read GetCount;
    property IsUsed: boolean read GetIsUsed write SetIsUsed;
    property RefCnt: integer read FRefCnt;
    property AliasName: string read GetAliasName write FAliasName;
  end;

  TClassType = (ctClass, ctInterface, ctObject, ctRecord);

  { TClassDef }

  TClassDef = class(TDef)
  private
    FHasClassRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
    function ShouldUseChild(d: TDef): boolean; override;
  public
    CType: TClassType;
    AncestorClass: TClassDef;
    HasAbstractMethods: boolean;
    HasReplacedItems: boolean;
    ImplementsReplacedItems: boolean;
    Size: integer;
    IID: string;
    procedure ResolveDefs; override;
    function GetRefDef: TDef; override;
  end;

  TBasicType = (btVoid, btByte, btShortInt, btWord, btSmallInt, btLongWord, btLongInt, btInt64,
                btSingle, btDouble, btString, btWideString, btBoolean, btChar, btWideChar, btEnum,
                btGuid);

  { TTypeDef }

  TTypeDef = class(TDef)
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    BasicType: TBasicType;
  end;

  { TPointerDef }

  TPointerDef = class(TDef)
  private
    FHasPtrRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    PtrType: TDef;
    procedure ResolveDefs; override;
    function IsObjPtr: boolean;
    function GetRefDef: TDef; override;
  end;

  { TReplDef }

  TReplDef = class(TDef)
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    IsReplaced: boolean;
    IsReplImpl: boolean;
    ReplacedItem: TReplDef;

    function CanReplaced: boolean; virtual;
    function IsReplacedBy(d: TReplDef): boolean; virtual;
    procedure CheckReplaced;
  end;

  TVarOption = (voRead, voWrite, voConst, voVar, voOut);
  TVarOptions = set of TVarOption;

  { TVarDef }

  TVarDef = class(TReplDef)
  private
    FHasTypeRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    VarOpt: TVarOptions;
    VarType: TDef;
    constructor Create; override;
    procedure ResolveDefs; override;
    function IsReplacedBy(d: TReplDef): boolean; override;
    function CanReplaced: boolean; override;
    function GetRefDef: TDef; override;
  end;

  TProcType = (ptProcedure, ptFunction, ptConstructor, ptDestructor);
  TProcOption = (poOverride, poOverload, poMethodPtr, poPrivate, poProtected, poClassMethod);
  TProcOptions = set of TProcOption;

  { TProcDef }

  TProcDef = class(TReplDef)
  private
    FHasRetTypeRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
    function ShouldUseChild(d: TDef): boolean; override;
  public
    ProcType: TProcType;
    ReturnType: TDef;
    ProcOpt: TProcOptions;
    procedure ResolveDefs; override;
    function IsReplacedBy(d: TReplDef): boolean; override;
    function CanReplaced: boolean; override;
    function GetRefDef: TDef; override;
  end;

  TUnitDef = class(TDef)
  public
    OS: string;
    CPU: string;
    IntfCRC: string;
    PPUVer: integer;
    UsedUnits: array of TUnitDef;
    Processed: boolean;
    IsUnitUsed: boolean;
  end;

  TConstDef = class(TVarDef)
  public
    Value: string;
  end;

  { TSetDef }

  TSetDef = class(TDef)
  private
    FHasElTypeRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    Size: integer;
    Base: integer;
    ElMax: integer;
    ElType: TTypeDef;
    function GetRefDef: TDef; override;
  end;

  { TArrayDef }

  TArrayDef = class(TDef)
  private
    FHasElTypeRef: boolean;
    FHasRTypeRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    ElType: TDef;
    RangeType: TDef;
    RangeLow, RangeHigh: integer;
    function GetRefDef: TDef; override;
    function GetRefDef2: TDef; override;
  end;

  { TClassRefDef }

  TClassRefDef = class(TDef)
  private
    FHasClassRef: boolean;
  protected
    procedure SetIsUsed(const AValue: boolean); override;
  public
    ClassRef: TDef;
    procedure ResolveDefs; override;
    function GetRefDef: TDef; override;
  end;


const
  ReplDefs  = [dtField, dtProp, dtProc];

var
  OnCanUseDef: function (def, refdef: TDef): boolean;

implementation

function IsSameType(t1, t2: TDef): boolean;
begin
  Result:=t1 = t2;
  if Result then
    exit;
  if (t1 = nil) or (t2 = nil) or (t1.DefType <> t2.DefType) then
    exit;
  if t1.DefType <> dtType then
    exit;
  Result:=TTypeDef(t1).BasicType = TTypeDef(t2).BasicType;
end;

{ TClassRefDef }

procedure TClassRefDef.SetIsUsed(const AValue: boolean);
begin
  inherited SetIsUsed(AValue);
  SetExtUsed(ClassRef, AValue, FHasClassRef);
end;

procedure TClassRefDef.ResolveDefs;
begin
  inherited ResolveDefs;
  ClassRef:=ResolveDef(ClassRef);
end;

function TClassRefDef.GetRefDef: TDef;
begin
  Result:=ClassRef;
end;

{ TArrayDef }

procedure TArrayDef.SetIsUsed(const AValue: boolean);
begin
  inherited SetIsUsed(AValue);
  SetExtUsed(ElType, AValue, FHasElTypeRef);
  SetExtUsed(RangeType, AValue, FHasRTypeRef);
end;

function TArrayDef.GetRefDef: TDef;
begin
  Result:=ElType;
end;

function TArrayDef.GetRefDef2: TDef;
begin
  Result:=RangeType;
end;

{ TPointerDef }

procedure TPointerDef.SetIsUsed(const AValue: boolean);
begin
  if IsObjPtr then begin
    inherited SetIsUsed(AValue);
    SetExtUsed(PtrType, AValue, FHasPtrRef);
  end
  else
    if AValue then
      AddRef
    else
      DecRef;
end;

procedure TPointerDef.ResolveDefs;
begin
  inherited ResolveDefs;
  PtrType:=ResolveDef(PtrType);
end;

function TPointerDef.IsObjPtr: boolean;
begin
  Result:=(PtrType <> nil) and (PtrType.DefType in [dtClass]);
end;

function TPointerDef.GetRefDef: TDef;
begin
  Result:=PtrType;
end;

{ TReplDef }

procedure TReplDef.SetIsUsed(const AValue: boolean);
var
  i: integer;
begin
  i:=RefCnt;
  inherited SetIsUsed(AValue);
  if (i = 0) and (RefCnt > 0) then
    CheckReplaced;
end;

function TReplDef.CanReplaced: boolean;
begin
  Result:=not (IsPrivate or (Parent = nil) or (Parent.DefType <> dtClass));
end;

function TReplDef.IsReplacedBy(d: TReplDef): boolean;
begin
  Result:=d.CanReplaced and (CompareText(Name, d.Name) = 0);
end;

procedure TReplDef.CheckReplaced;

  function _Scan(cls: TClassDef): boolean;
  var
    i: integer;
    d: TReplDef;
    c: TClassDef;
  begin
    Result:=False;
    c:=cls.AncestorClass;
    if c = nil then
      exit;
    for i:=0 to c.Count - 1 do begin
      d:=TReplDef(c[i]);
      if (d.DefType in ReplDefs) and IsReplacedBy(d) then begin
        d.IsReplaced:=True;
        ReplacedItem:=d;
        Result:=True;
        break;
      end;
    end;
    if not Result then
      Result:=_Scan(c);
    if Result then begin
      cls.ImplementsReplacedItems:=True;
      c.HasReplacedItems:=True;
    end;
  end;

begin
  if not CanReplaced then
    exit;
  if _Scan(TClassDef(Parent)) then
    IsReplImpl:=True;
end;

{ TSetDef }

procedure TSetDef.SetIsUsed(const AValue: boolean);
begin
  inherited SetIsUsed(AValue);
  SetExtUsed(ElType, AValue, FHasElTypeRef);
end;

function TSetDef.GetRefDef: TDef;
begin
  Result:=ElType;
end;

{ TTypeDef }

procedure TTypeDef.SetIsUsed(const AValue: boolean);
begin
  if BasicType in [btEnum] then
    inherited SetIsUsed(AValue)
  else
    if AValue then
      AddRef
    else
      DecRef;
end;

{ TProcDef }

procedure TProcDef.SetIsUsed(const AValue: boolean);
var
  i: integer;
begin
  if IsPrivate then
    exit;
  if AValue and (RefCnt = 0) then begin
    for i:=0 to Count - 1 do
      if (Items[i].DefType = dtParam) and (TVarDef(Items[i]).VarType = nil) then
        exit; // If procedure has unsupported parameters, don't use it
  end;
  inherited SetIsUsed(AValue);
  if ReturnType <> Parent then
    SetExtUsed(ReturnType, AValue, FHasRetTypeRef);
end;

function TProcDef.ShouldUseChild(d: TDef): boolean;
begin
  Result:=d.DefType in [dtParam];
end;

procedure TProcDef.ResolveDefs;
begin
  inherited ResolveDefs;
  ReturnType:=ResolveDef(ReturnType);
end;

function TProcDef.IsReplacedBy(d: TReplDef): boolean;
var
  i: integer;
  p: TProcDef;
begin
  Result:=False;
  if d.DefType <> dtProc then
    exit;
  p:=TProcDef(d);
  if (Count = p.Count) and inherited IsReplacedBy(p) then begin
    // Check parameter types
    for i:=0 to Count - 1 do
      if not IsSameType(TVarDef(Items[i]).VarType, TVarDef(p.Items[i]).VarType) then
        exit;
    Result:=True;
  end;
end;

function TProcDef.CanReplaced: boolean;
begin
  Result:=inherited CanReplaced and (ProcType = ptFunction);
end;

function TProcDef.GetRefDef: TDef;
begin
  Result:=ReturnType;
end;

{ TClassDef }

procedure TClassDef.SetIsUsed(const AValue: boolean);
begin
  inherited SetIsUsed(AValue);
  SetExtUsed(AncestorClass, AValue, FHasClassRef);
end;

function TClassDef.ShouldUseChild(d: TDef): boolean;
begin
  Result:=d.DefType in [dtProc, dtField, dtProp];
end;

procedure TClassDef.ResolveDefs;
begin
  inherited ResolveDefs;
  AncestorClass:=TClassDef(ResolveDef(AncestorClass, TClassDef));
end;

function TClassDef.GetRefDef: TDef;
begin
  Result:=AncestorClass;
end;

{ TVarDef }

procedure TVarDef.SetIsUsed(const AValue: boolean);
var
  ptr, d: TDef;
begin
  if IsPrivate then
    exit;
  inherited SetIsUsed(AValue);
  // Detect circular pointers
  if (VarType <> nil) and (VarType.DefType = dtPointer) and (VarType.RefCnt > 0) then begin
    ptr:=TPointerDef(VarType).PtrType;
    if ptr <> nil then begin
      d:=Self;
      while d <> nil do begin
        if d = ptr then
          exit;
        d:=d.Parent;;
      end;
    end;
  end;
  SetExtUsed(VarType, AValue, FHasTypeRef);
end;

procedure TVarDef.ResolveDefs;
begin
  inherited ResolveDefs;
  VarType:=ResolveDef(VarType);
end;

function TVarDef.IsReplacedBy(d: TReplDef): boolean;
begin
  Result:=(d.DefType in [dtProp, dtField]) and not IsSameType(VarType, TVarDef(d).VarType) and inherited IsReplacedBy(d);
end;

function TVarDef.CanReplaced: boolean;
begin
  Result:=(voRead in VarOpt) and inherited CanReplaced;
end;

function TVarDef.GetRefDef: TDef;
begin
  Result:=VarType;
end;

constructor TVarDef.Create;
begin
  inherited Create;
  VarOpt:=[voRead, voWrite];
end;

{ TDef }

procedure TDef.CheckItems;
begin
  if FItems = nil then
    FItems:=TObjectList.Create(True);
end;

function TDef.GetAliasName: string;
begin
  if FAliasName <> '' then
    Result:=FAliasName
  else
    Result:=Name;
end;

function TDef.GetCount: integer;
begin
  if FItems = nil then
    Result:=0
  else begin
    CheckItems;
    Result:=FItems.Count;
  end;
end;

function TDef.GetIsUsed: boolean;
begin
  Result:=FRefCnt > 0;
end;

function TDef.GetItem(Index: Integer): TDef;
begin
  CheckItems;
  Result:=TDef(FItems[Index]);
end;

procedure TDef.SetIsUsed(const AValue: boolean);
var
  i: integer;
  f: boolean;
  d: TDef;
begin
  if FInSetUsed or (DefType = dtNone) or IsPrivate then
    exit;
  if AValue then begin
    if Assigned(OnCanUseDef) and not OnCanUseDef(Self, Parent) then
      exit;
    AddRef;
    f:=FRefCnt = 1;
  end
  else begin
    if FRefCnt = 0 then
      exit;
    DecRef;
    f:=FRefCnt = 0;
  end;
  if f then begin
    // Update used mark of children only once
    FInSetUsed:=True;
    try
      for i:=0 to Count - 1 do begin
        d:=Items[i];
        if ShouldUseChild(d) then
          d.IsUsed:=AValue;
      end;
    finally
      FInSetUsed:=False;
    end;
    // Update parent's used mark
    if (Parent <> nil) and (Parent.DefType = dtUnit) then
      if AValue then
        Parent.AddRef
      else
        Parent.DecRef;
  end;
end;

function TDef.ResolveDef(d: TDef; ExpectedClass: TDefClass): TDef;
begin
  if (d = nil) or (d.DefType <> dtNone) then
    Result:=d
  else begin
    Result:=d.Parent.FindDef(d.DefId);
    if (ExpectedClass <> nil) and (Result <> nil) then
      if not (Result is ExpectedClass) then
        raise Exception.CreateFmt('Unexpected class. Expected: %s, got: %s', [ExpectedClass.ClassName, Result.ClassName]);

  end;
end;

procedure TDef.AddRef;
begin
  Inc(FRefCnt);
end;

procedure TDef.DecRef;
begin
  if FRefCnt > 0 then
    Dec(FRefCnt);
end;

procedure TDef.SetExtUsed(ExtDef: TDef; AUsed: boolean; var HasRef: boolean);
var
  OldRefCnt: integer;
begin
  if ExtDef = nil then
    exit;
  if AUsed then begin
    if HasRef then
      exit;
    if Assigned(OnCanUseDef) and not OnCanUseDef(ExtDef, Self) then
      exit;
    OldRefCnt:=ExtDef.RefCnt;
    ExtDef.IsUsed:=True;
    HasRef:=OldRefCnt <> ExtDef.RefCnt;
  end
  else
    if HasRef and not IsUsed then begin
      ExtDef.IsUsed:=False;
      HasRef:=False;
    end;
end;

function TDef.ShouldUseChild(d: TDef): boolean;
begin
  Result:=True;
end;

procedure TDef.SetItem(Index: Integer; const AValue: TDef);
begin
  CheckItems;
  FItems[Index]:=AValue;
end;

constructor TDef.Create;
begin
  DefId:=-1;
  DefType:=dtNone;
end;

constructor TDef.Create(AParent: TDef; AType: TDefType);
begin
  Create;
  if AParent <> nil then
    AParent.Add(Self);
  DefType:=AType;
end;

destructor TDef.Destroy;
begin
  FreeAndNil(FItems);
  if (Parent <> nil) and (Parent.FItems <> nil) then begin
    Parent.FItems.OwnsObjects:=False;
    try
      Parent.FItems.Remove(Self);
    finally
      Parent.FItems.OwnsObjects:=True;
    end;
  end;
  inherited Destroy;
end;

function TDef.Add(ADef: TDef): integer;
begin
  Result:=Insert(Count, ADef);
end;

function TDef.Insert(Index: integer; ADef: TDef): integer;
begin
  CheckItems;
  Result:=Index;
  FItems.Insert(Result, ADef);
  ADef.Parent:=Self;
end;

function TDef.FindDef(ADefId: integer; Recursive: boolean): TDef;

  function _Find(d: TDef): TDef;
  var
    i: integer;
  begin
    Result:=nil;
    for i:=0 to d.Count - 1 do
      with d[i] do begin
        if (DefType <> dtNone) and (DefId = ADefId) then begin
          Result:=d[i];
          break;
        end;
        if Recursive and (Count > 0) then begin
          Result:=_Find(d[i]);
          if Result <> nil then
            break;
        end;
      end;
  end;

begin
  Result:=_Find(Self);
end;

procedure TDef.ResolveDefs;
var
  i: integer;
begin
  for i:=0 to Count - 1 do
    Items[i].ResolveDefs;
end;

procedure TDef.SetNotUsed;
begin
  if FRefCnt = 0 then
    exit;
  FRefCnt:=1;
  IsUsed:=False;
end;

function TDef.GetRefDef: TDef;
begin
  Result:=nil;
end;

function TDef.GetRefDef2: TDef;
begin
  Result:=nil;
end;

end.

