unit uRODLSplitableConverter; {----------------------------------------------------------------------------} { RemObjects SDK Library - CodeGen2 } { } { compiler: Delphi 5 and up, Kylix 2 and up } { platform: Win32, Linux } { } { (c)opyright RemObjects Software. all rights reserved. } { } { Using this code requires a valid license of the RemObjects SDK } { which can be obtained at http://www.remobjects.com. } {----------------------------------------------------------------------------} {$IFDEF LINUX} {$I ../RemObjects.inc} {$ELSE} {$I ..\RemObjects.inc} {$ENDIF LINUX} interface uses Classes, IniFiles, Contnrs, uRODL, uROClasses, uRODLLineStream, uRODLTemplateBasedConverter; type TRODLSplitableConverterFileList = class; TRODLSplitableConverter = class (TRODLTemplateBasedConverter) private FTargetEntities: TStringList; FOutputDir: string; protected FWrapperTemplateName: string; FSplit: Boolean; FFiles: TRODLSplitableConverterFileList; procedure ProcessTemplate(const aLibrary: TRODLLibrary; template: TLineStream); override; procedure SetupFiles(const aLibrary: TRODLLibrary); virtual; abstract; procedure WriteWrapper(TypesList: TStringList; UsesList: TStringList); virtual; function GetSplitFilesSuffix: string; virtual; abstract; procedure AddEntityTypes(Types: {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}; const AEntity: TRODLEntity; const unitName: string); virtual; procedure ResolveRequiredUnits; public constructor Create(const aLibrary: TRODLLibrary; const aTemplateFileName: string; const aUnitName: string; const ASplit: Boolean = False; const AOutputDir: string = ''; const AWrapperTemplateName: string = ''; const aTargetEntities: string = ''); reintroduce; virtual; destructor Destroy; override; property Split: Boolean read FSplit; property SplitFilesSuffix: string read GetSplitFilesSuffix; property OutputDir: string read FOutputDir; end; TRODLSplitableConverterFile = class private FEnums: TObjectList; FArrays: TObjectList; FStructs: TObjectList; FEventSinks: TObjectList; FExceptions: TObjectList; FUnitName: string; FServices: TObjectList; FLibrary: TRODLLibrary; FActiveList: TObjectList; FLibraryList: IROStrings; FConverter: TRODLSplitableConverter; FRequiredUnits: TStringList; function GetServices: TObjectList; function GetUnitName: string; function GetExceptions: TObjectList; function GetEventSinks: TObjectList; function GetStructs: TObjectList; function GetArrays: TObjectList; function GetEnums: TObjectList; procedure SetupLists; public constructor Create(const aLibrary: TRODLLibrary; const aConverter: TRODLSplitableConverter); destructor Destroy; override; function GetActiveList: TObjectList; function GetLibraryList: IROStrings; property Services: TObjectList read GetServices; property Enums: TObjectList read GetEnums; property Arrays: TObjectList read GetArrays; property Structs: TObjectList read GetStructs; property EventSinks: TObjectList read GetEventSinks; property Exceptions: TObjectList read GetExceptions; property UnitName: string read GetUnitName write FUnitName; property RequiredUnits: TStringList read FRequiredUnits write FRequiredUnits; end; TRODLSplitableConverterFileList = class (TObjectList) private function GetItem(Index: Integer): TRODLSplitableConverterFile; procedure SetItem(Index: Integer; const Value: TRODLSplitableConverterFile); public property Items[Index: Integer]: TRODLSplitableConverterFile read GetItem write SetItem; default; end; implementation uses SysUtils, uRODLTemplateBasedConverterUtils; { TRODLSplitableConverter } procedure TRODLSplitableConverter.AddEntityTypes(Types: {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}; const AEntity: TRODLEntity; const unitName: string); begin Types.Values[AEntity.Name] := unitName; end; constructor TRODLSplitableConverter.Create(const aLibrary: TRODLLibrary; const aTemplateFileName, aUnitName: string; const ASplit: Boolean; const AOutputDir, AWrapperTemplateName, aTargetEntities: string); begin // Call inherited with nil so that convert is not called, allowing us // to setup the optional sections. Setting up the optional sections before // calling the inherited Create is not an option, the section list would // not have been created inherited Create(nil, aTemplateFileName, aUnitName, ''); FSplit := ASplit; FFiles := TRODLSplitableConverterFileList.Create(True); FOutputDir := AOutputDir; if Length(FOutputDir) = 0 then FOutputDir := '.'; FWrapperTemplateName := AWrapperTemplateName; FTargetEntities := TStringList.Create; FTargetEntities.Sorted := True; FTargetEntities.Delimiter := ','; FTargetEntities.DelimitedText := aTargetEntities; // Setup the optional sections // Now that everything is done, call convert if need be. if (aLibrary <> nil) then Convert(aLibrary); end; destructor TRODLSplitableConverter.Destroy; begin FTargetEntities.Free; FFiles.Free; inherited Destroy; end; procedure TRODLSplitableConverter.ProcessTemplate(const aLibrary: TRODLLibrary; template: TLineStream); var I: Integer; curFile: TRODLSplitableConverterFile; J: Integer; UsesList: TStringList; TypesList: {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}; curEntity: TRODLEntity; originalUnitName: string; originalRequiredUnits: string; oneEntitySelected: Boolean; begin if Split then begin SetupFiles(aLibrary); ResolveRequiredUnits; UsesList := TStringList.Create; TypesList := {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}.Create; originalUnitName := UnitName; originalRequiredUnits := RequiredUnits; try for I := 0 to FFiles.Count - 1 do begin curFile := FFiles[I]; UsesList.Add(curFile.UnitName); FOrderedServices.Clear; FOrderedArrays.Clear; FOrderedStructs.Clear; FOrderedEnums.Clear; FOrderedExceptions.Clear; FOrderedEventSinks.Clear; oneEntitySelected := False; for J := 0 to curFile.GetActiveList.Count - 1 do begin curEntity := curFile.GetActiveList[J] as TRODLEntity; curFile.GetLibraryList.AddObject(curEntity.Name, curEntity); AddEntityTypes(TypesList, curEntity, curFile.UnitName); oneEntitySelected := oneEntitySelected or (FTargetEntities.IndexOf(curEntity.Name) > -1); end; // Only generate if no entities were indicated or if at least one // of the entities in the current file was found earlier in the list. if (FTargetEntities.Count = 0) or oneEntitySelected then begin Buffer.Clear; template.Position := 0; UnitName := curFile.UnitName; RequiredUnits := originalRequiredUnits; if curFile.RequiredUnits.Count > 0 then begin if Length(RequiredUnits) > 0 then RequiredUnits := RequiredUnits + ','; RequiredUnits := RequiredUnits + curFile.RequiredUnits.DelimitedText; end; inherited ProcessTemplate(aLibrary, template); Buffer.SaveToFile(StrEnsureSuffix('\', OutputDir)+ curFile.UnitName + FileExtension); end; end; UnitName := originalUnitName; RequiredUnits := originalRequiredUnits; WriteWrapper(TypesList, UsesList); finally UsesList.Free; TypesList.Free; end; end else begin inherited ProcessTemplate(aLibrary, template); end; end; procedure TRODLSplitableConverter.ResolveRequiredUnits; var curFileIndex: Integer; curFile: TRODLSplitableConverterFile; curEntityIndex: Integer; curEntity: TRODLEntity; curService: TRODLBaseService; curArray: TRODLArray; curStruct: TRODLBaseStruct; typesCache: {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}; curFieldIndex: Integer; curOpIndex: Integer; curOp: TRODLOperation; curParamIndex: Integer; notFounds: TStringList; procedure AddRequiredUnits(const typeName: string); var resultIndex: Integer; begin resultIndex := notFounds.IndexOf(typeName); if resultIndex = -1 then begin unitName := typesCache.Values[typeName]; if Length(unitName) > 0 then begin if unitName <> curFile.UnitName then curFile.RequiredUnits.Add(unitName); end else begin notFounds.Add(typeName); end; end; end; begin // In order to find out if a splitted file requires other files, we need // to know which types are where. So we fill in the typesCache list with // Name/Value pairs, where the name is the type and the value the unit // where it is located. // Once this is done, we go look at all the types in all the files, and // we look for them in the cache. If the type is found, then the unit it // resides in is added to the list of required units. If it is not found // it is added to the notFounds list. This second list allows to speed // the process a lot because looking through the typesCache list is slow // and many regular types (Integer, String...) are used a lot but there are // not many of them. Hence the second list allows to know very fast for these // that they were not found in any unit. typesCache := {$IFDEF FPC}TStringList{$ELSE}THashedStringList{$ENDIF}.Create; notFounds := TStringList.Create; try notFounds.Sorted := True; for curFileIndex := 0 to FFiles.Count - 1 do begin curFile := FFiles[curFileIndex]; for curEntityIndex := 0 to curFile.GetActiveList.Count - 1 do begin curEntity := curFile.GetActiveList[curEntityIndex] as TRODLEntity; typesCache.Values[curEntity.Name] := curFile.UnitName; end; end; for curFileIndex := 0 to FFiles.Count - 1 do begin curFile := FFiles[curFileIndex]; curFile.RequiredUnits.Clear; for curEntityIndex := 0 to curFile.GetActiveList.Count - 1 do begin curEntity := curFile.GetActiveList[curEntityIndex] as TRODLEntity; if curEntity is TRODLBaseService then begin curService := curEntity as TRODLBaseService; AddRequiredUnits(curService.Ancestor); for curOpIndex := 0 to curService.Default.Count - 1 do begin curOp := curService.Default.Items[curOpIndex]; if Assigned(curOp.Result) then AddRequiredUnits(curOp.Result.DataType); for curParamIndex := 0 to curOp.Count - 1 do AddRequiredUnits(curOp.Items[curParamIndex].DataType); end; end else if curEntity is TRODLArray then begin curArray := curEntity as TRODLArray; AddRequiredUnits(curArray.ElementType); end else if curEntity is TRODLBaseStruct then begin curStruct := curEntity as TRODLBaseStruct; AddRequiredUnits(curStruct.Ancestor); for curFieldIndex := 0 to curStruct.Count - 1 do AddRequiredUnits(curStruct.Items[curFieldIndex].DataType); end; end; curFile.RequiredUnits.Delimiter := ','; end; finally notFounds.Free; typesCache.Free; end; end; procedure TRODLSplitableConverter.WriteWrapper(TypesList, UsesList: TStringList); var template: TLineStream; content: TStringList; curLine: string; sectionContent: string; I: Integer; typeUnitName: string; begin template := TLineStream.Create(''); try content := TStringList.Create; try if FileExists(FWrapperTemplateName) then content.LoadFromFile(FWrapperTemplateName) else LoadTemplateFromResource(content, FWrapperTemplateName); template.WriteString(content.Text); template.Position := 0; finally content.Free; end; Buffer.Clear; while not template.Eof do begin curLine := template.ReadLine; MacroReplace(curLine, '%', ['UNIT_NAME', FLibrary.Name + SplitFilesSuffix ]); if SectionStart(curLine, 'USES') then begin sectionContent := FinishSection('USES', template); for I := 0 to UsesList.Count - 1 do begin curLine := sectionContent; MacroReplace(curLine, '%', ['USED_UNIT_NAME', UsesList[I] ]); if I = UsesList.Count-1 then curLine := StrEnsureNoSuffix(',', curLine); Write(curLine); end; end else if SectionStart(curLine, 'TYPES') then begin sectionContent := FinishSection('TYPES', template); for I := 0 to TypesList.Count - 1 do begin curLine := sectionContent; typeUnitName := TypesList[I]; typeUnitName := Copy(typeUnitName, Pos('=', typeUnitName)+1, MAXINT); MacroReplace(curLine, '%', ['TYPE_UNIT_NAME', typeUnitName, 'TYPE_NAME', TypesList.Names[I] ]); Write(curLine); end; end else begin Write(curLine); end; end; finally template.Free; end; end; { TRODLIntfConverterFileList } function TRODLSplitableConverterFileList.GetItem( Index: Integer): TRODLSplitableConverterFile; begin Result := inherited Items[Index] as TRODLSplitableConverterFile; end; procedure TRODLSplitableConverterFileList.SetItem(Index: Integer; const Value: TRODLSplitableConverterFile); begin inherited Items[Index] := Value; end; { TRODLIntfConverterFile } constructor TRODLSplitableConverterFile.Create(const aLibrary: TRODLLibrary; const aConverter: TRODLSplitableConverter); begin inherited Create; FLibrary := aLibrary; FConverter := aConverter; FRequiredUnits := TStringList.Create; FRequiredUnits.Sorted := True; FRequiredUnits.Duplicates := dupIgnore; end; destructor TRODLSplitableConverterFile.Destroy; begin FRequiredUnits.Free; FEnums.Free; FArrays.Free; FStructs.Free; FEventSinks.Free; FExceptions.Free; FServices.Free; inherited Destroy; end; function TRODLSplitableConverterFile.GetActiveList: TObjectList; begin if FActiveList = nil then begin SetupLists; end; Result := FActiveList; end; procedure TRODLSplitableConverterFile.SetupLists; begin if Services.Count > 0 then begin FActiveList := Services; FLibraryList := FConverter.OrderedServices; end else if Arrays.Count > 0 then begin FActiveList := Arrays; FLibraryList := FConverter.OrderedArrays; end else if Structs.Count > 0 then begin FActiveList := Structs; FLibraryList := FConverter.OrderedStructs; end else if Enums.Count > 0 then begin FActiveList := Enums; FLibraryList := FConverter.OrderedEnums; end else if EventSinks.Count > 0 then begin FActiveList := EventSinks; FLibraryList := FConverter.OrderedEventSinks; end else if Exceptions.Count > 0 then begin FActiveList := Exceptions; FLibraryList := FConverter.OrderedExceptions; end; end; function TRODLSplitableConverterFile.GetArrays: TObjectList; begin if not Assigned(FArrays) then FArrays := TObjectList.Create(False); Result := FArrays; end; function TRODLSplitableConverterFile.GetEnums: TObjectList; begin if not Assigned(FEnums) then FEnums := TObjectList.Create(False); Result := FEnums; end; function TRODLSplitableConverterFile.GetEventSinks: TObjectList; begin if not Assigned(FEventSinks) then FEventSinks := TObjectList.Create(False); Result := FEventSinks; end; function TRODLSplitableConverterFile.GetExceptions: TObjectList; begin if not Assigned(FExceptions) then FExceptions := TObjectList.Create(False); Result := FExceptions; end; function TRODLSplitableConverterFile.GetLibraryList: IROStrings; begin if FLibraryList = nil then begin SetupLists; end; Result := FLibraryList; end; function TRODLSplitableConverterFile.GetServices: TObjectList; begin if not Assigned(FServices) then FServices := TObjectList.Create(False); Result := FServices; end; function TRODLSplitableConverterFile.GetStructs: TObjectList; begin if not Assigned(FStructs) then FStructs := TObjectList.Create(False); Result := FStructs; end; function TRODLSplitableConverterFile.GetUnitName: string; var list: TObjectList; I: Integer; begin if FUnitName = '' then begin list := GetActiveList; for I := 0 to list.Count - 1 do FUnitName := FUnitName + (list[i] as TRODLEntity).Name + '_'; FUnitName := FLibrary.Name + '_' + Copy(FUnitName, 1, Length(FUnitName)-1) + FConverter.SplitFilesSuffix; end; Result := FUnitName; end; end.