Добрый день!
Стоит задача написать обобщенный класс-помощник по работе со множествами. Задачи следующие: определение количества элементов множества, определение первого элемента множества, определение следующего в порядке перечисления элемента множества. Написал следующий код, но сомневаюсь в его рациональности. Уважаемые гуру, подскажите, как можно этот код написать красивее и рациональнее?
unit HelpSet;
interface
uses
System.SysUtils, System.TypInfo, Math;
type
EHelpSetError = class(Exception)
const
SI_VALUE_TYPE_UNKNOWN = 'Информация об указанном типе отсутствует';
SI_VALUE_TYPE_NOT_SET = 'Указанный тип "%s" не является множеством';
SI_VALUE_TYPE_NOT_ENUM = 'Указанный тип "%s" не является перечислением';
SI_SET_NOT_ENUM_COMPATIBLE = 'Указанные типы множества "%s" и перечисления "%s" не совместимы';
SI_SET_NOT_CONTENT_ITEMS = 'Указанное множество не содержит ни одного элемента';
class procedure ValueTypeUnknown();
class procedure ValueTypeNotSet(SetName: string);
class procedure ValueTypeNotEnum(EnumName: string);
class procedure SetNotEnumCompatible(SetName, EnumName: string);
class procedure SetNotContentItems();
end;
type
THelpSet = class
strict private
class function GetTypeInfo<T>(): PTypeInfo;
class function GetSetInfo<S>(): PTypeInfo;
class function GetEnumInfo<E>(): PTypeInfo;
class procedure CheckTypes<S, E>();
class function GetEnumData(SetInfo: PTypeInfo): PTypeData;
class function GetMask<T>(Value: T): Longword;
class function SetMask<T>(Value: Longword): T;
class procedure CheckMask(mask: Longword);
public
class function Convert<S>(Flags: array of boolean): S;
class function ActiveCount<S>(Value: S): Integer;
class function FirstActive<S, E>(Value: S): E;
class function NextActive<S, E>(Value: S; Current: E): E;
end;
implementation
class procedure EHelpSetError.ValueTypeUnknown();
begin
raise Self.Create(SI_VALUE_TYPE_UNKNOWN);
end;
class procedure EHelpSetError.ValueTypeNotSet(SetName: string);
begin
raise Self.Create(Format(SI_VALUE_TYPE_NOT_SET, [SetName]));
end;
class procedure EHelpSetError.ValueTypeNotEnum(EnumName: string);
begin
raise Self.Create(Format(SI_VALUE_TYPE_NOT_ENUM, [EnumName]));
end;
class procedure EHelpSetError.SetNotEnumCompatible(SetName, EnumName: string);
begin
raise Self.Create(Format(SI_SET_NOT_ENUM_COMPATIBLE, [SetName, EnumName]));
end;
class procedure EHelpSetError.SetNotContentItems();
begin
raise Self.Create(SI_SET_NOT_CONTENT_ITEMS);
end;
class function THelpSet.GetTypeInfo<T>: PTypeInfo;
begin
Result := PTypeInfo(TypeInfo(T));
if not Assigned(Result) then EHelpSetError.ValueTypeUnknown();
end;
class function THelpSet.GetSetInfo<S>(): PTypeInfo;
begin
Result := GetTypeInfo<S>();
if Result.Kind <> tkSet then
EHelpSetError.ValueTypeNotSet(Result.Name);
end;
class function THelpSet.GetEnumInfo<E>(): PTypeInfo;
begin
Result := GetTypeInfo<E>();
if Result.Kind <> tkEnumeration then
EHelpSetError.ValueTypeNotEnum(Result.Name);
end;
class procedure THelpSet.CheckTypes<S, E>();
var
info_set : PTypeInfo;
info_comp : PTypeInfo;
info_enum : PTypeInfo;
begin
info_set := GetSetInfo <S>();
info_enum := GetEnumInfo<E>();
info_comp := GetTypeData(info_set)^.CompType^;
if info_enum <> info_comp then
EHelpSetError.SetNotEnumCompatible(info_Set.Name, info_enum.Name);
end;
class function THelpSet.GetEnumData(SetInfo: PTypeInfo): PTypeData;
begin
Result := GetTypeData(SetInfo);
Result := GetTypeData(Result^.CompType^);
end;
class function THelpSet.GetMask<T>(Value: T): Longword;
begin
case SizeOf(T) of
1: Result := pByte (@Value)^;
2: Result := pWord (@Value)^;
4: Result := pLongword(@Value)^;
else Result := 0;
end;
end;
class function THelpSet.SetMask<T>(Value: Longword): T;
begin
case SizeOf(T) of
1: pByte (@Result)^ := Value;
2: pWord (@Result)^ := Value;
4: pLongWord(@Result)^ := Value;
else Result := Default(T);
end;
end;
class procedure THelpSet.CheckMask(mask: Longword);
begin
if mask = 0 then EHelpSetError.SetNotContentItems();
end;
class function THelpSet.Convert<S>(Flags: array of boolean): S;
var
I : Integer;
mask : Longword;
info : PTypeInfo;
begin
info := GetSetInfo<S>;
mask := 0;
for I := Low(Flags) to Min(High(Flags), GetEnumData(info).MaxValue) do
mask := (Longword(Flags[I]) shl I) or mask;
Result := S(Pointer(@mask)^);
end;
class function THelpSet.ActiveCount<S>(Value: S): Integer;
var
mask : Longword;
info : PTypeInfo;
begin
Result := 0;
Info := GetSetInfo<S>();
mask := GetMask(Value);
while mask <> 0 do begin
Inc(Result, mask and 1);
mask := mask shr 1;
end;
end;
class function THelpSet.FirstActive<S, E>(Value: S): E;
var
mask : Longword;
flag : Longword;
item : Byte;
begin
CheckTypes<S, E>();
mask := GetMask(Value);
CheckMask(mask);
flag := 1;
item := 0;
while (mask and flag) <> flag do begin
Inc(item);
flag := flag shl 1;
end;
Result := SetMask<E>(item);
end;
class function THelpSet.NextActive<S, E>(Value: S; Current: E): E;
var
mask : Longword;
flag : Longword;
item : Byte;
max_item : Byte;
begin
CheckTypes<S, E>();
mask := GetMask<S>(Value);
item := GetMask<E>(Current);
CheckMask(mask);
flag := 1 shl item;
max_item := SizeOf(S) * 8 - 1;
repeat
if item < max_item then begin
flag := flag shl 1;
item := item + 1;
end else begin
flag := flag shr max_item;
item := 0;
end;
until (mask and flag) = flag;
Result := SetMask<E>(item);
end;
end.