bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
889 lines (765 loc) • 25.4 kB
Markdown
This sample demonstrates implementing generic method patterns for type-safe, reusable operations in AL.
```al
// Generic interface for comparable operations
interface IComparable
{
procedure CompareTo(Other: Interface IComparable): Integer;
}
// Generic interface for collection operations
interface IGenericCollection
{
procedure Add(Item: Variant);
procedure Remove(Item: Variant): Boolean;
procedure Contains(Item: Variant): Boolean;
procedure Count(): Integer;
procedure Clear();
procedure ToArray(): Array of [Variant];
}
```
```al
// Generic list implementation with type safety
codeunit 50800 "Generic List Manager"
{
var
Items: List of [Variant];
ItemType: Text[50];
procedure Initialize(ExpectedType: Text[50])
begin
Clear(Items);
ItemType := ExpectedType;
end;
procedure Add(Item: Variant): Boolean
begin
if not ValidateItemType(Item) then
exit(false);
Items.Add(Item);
exit(true);
end;
procedure AddRange(NewItems: List of [Variant]): Integer
var
Item: Variant;
AddedCount: Integer;
begin
AddedCount := 0;
foreach Item in NewItems do begin
if Add(Item) then
AddedCount += 1;
end;
exit(AddedCount);
end;
procedure Get(Index: Integer): Variant
var
EmptyVariant: Variant;
begin
if (Index < 1) or (Index > Items.Count) then
exit(EmptyVariant);
exit(Items.Get(Index));
end;
procedure Set(Index: Integer; Item: Variant): Boolean
begin
if not ValidateItemType(Item) then
exit(false);
if (Index < 1) or (Index > Items.Count) then
exit(false);
Items.Set(Index, Item);
exit(true);
end;
procedure Remove(Item: Variant): Boolean
var
Index: Integer;
begin
Index := IndexOf(Item);
if Index > 0 then begin
Items.RemoveAt(Index);
exit(true);
end;
exit(false);
end;
procedure RemoveAt(Index: Integer): Boolean
begin
if (Index < 1) or (Index > Items.Count) then
exit(false);
Items.RemoveAt(Index);
exit(true);
end;
procedure IndexOf(Item: Variant): Integer
var
i: Integer;
CurrentItem: Variant;
begin
for i := 1 to Items.Count do begin
CurrentItem := Items.Get(i);
if AreEqual(CurrentItem, Item) then
exit(i);
end;
exit(0);
end;
procedure Contains(Item: Variant): Boolean
begin
exit(IndexOf(Item) > 0);
end;
procedure Count(): Integer
begin
exit(Items.Count);
end;
procedure Clear()
begin
Items.Clear();
end;
procedure ToArray(): List of [Variant]
begin
exit(Items);
end;
procedure Filter(FilterFunction: Text): List of [Variant]
var
FilteredItems: List of [Variant];
Item: Variant;
begin
foreach Item in Items do begin
if ApplyFilter(Item, FilterFunction) then
FilteredItems.Add(Item);
end;
exit(FilteredItems);
end;
procedure Map(MapFunction: Text): List of [Variant]
var
MappedItems: List of [Variant];
Item: Variant;
MappedItem: Variant;
begin
foreach Item in Items do begin
MappedItem := ApplyMap(Item, MapFunction);
MappedItems.Add(MappedItem);
end;
exit(MappedItems);
end;
procedure Sort(): Boolean
begin
exit(SortItems(Items));
end;
procedure SortBy(CompareFunction: Text): Boolean
begin
exit(SortItemsBy(Items, CompareFunction));
end;
local procedure ValidateItemType(Item: Variant): Boolean
var
ActualType: Text[50];
begin
if ItemType = '' then
exit(true); // No type constraint
ActualType := GetVariantType(Item);
exit(ActualType = ItemType);
end;
local procedure GetVariantType(Item: Variant): Text[50]
begin
case true of
Item.IsText:
exit('Text');
Item.IsInteger:
exit('Integer');
Item.IsDecimal:
exit('Decimal');
Item.IsBoolean:
exit('Boolean');
Item.IsDate:
exit('Date');
Item.IsTime:
exit('Time');
Item.IsDateTime:
exit('DateTime');
Item.IsCode:
exit('Code');
else
exit('Unknown');
end;
end;
local procedure AreEqual(Item1: Variant; Item2: Variant): Boolean
begin
// Implement type-specific equality comparison
if GetVariantType(Item1) <> GetVariantType(Item2) then
exit(false);
exit(Format(Item1) = Format(Item2));
end;
local procedure ApplyFilter(Item: Variant; FilterFunction: Text): Boolean
begin
// Simplified filter implementation
case FilterFunction of
'NOT_EMPTY':
exit(Format(Item) <> '');
'POSITIVE':
begin
if Item.IsInteger then
exit(Item > 0);
if Item.IsDecimal then
exit(Item > 0);
exit(false);
end;
'NOT_ZERO':
begin
if Item.IsInteger then
exit(Item <> 0);
if Item.IsDecimal then
exit(Item <> 0);
exit(true);
end;
else
exit(true);
end;
end;
local procedure ApplyMap(Item: Variant; MapFunction: Text): Variant
var
MappedValue: Variant;
TextValue: Text;
IntValue: Integer;
DecValue: Decimal;
begin
case MapFunction of
'TO_UPPER':
begin
if Item.IsText or Item.IsCode then begin
TextValue := UpperCase(Format(Item));
MappedValue := TextValue;
end else
MappedValue := Item;
end;
'TO_LOWER':
begin
if Item.IsText or Item.IsCode then begin
TextValue := LowerCase(Format(Item));
MappedValue := TextValue;
end else
MappedValue := Item;
end;
'DOUBLE':
begin
if Item.IsInteger then begin
IntValue := Item * 2;
MappedValue := IntValue;
end else if Item.IsDecimal then begin
DecValue := Item * 2;
MappedValue := DecValue;
end else
MappedValue := Item;
end;
else
MappedValue := Item;
end;
exit(MappedValue);
end;
local procedure SortItems(var ItemList: List of [Variant]): Boolean
var
i, j: Integer;
temp: Variant;
swapped: Boolean;
begin
try
// Bubble sort implementation
for i := 1 to ItemList.Count - 1 do begin
swapped := false;
for j := 1 to ItemList.Count - i do begin
if CompareVariants(ItemList.Get(j), ItemList.Get(j + 1)) > 0 then begin
temp := ItemList.Get(j);
ItemList.Set(j, ItemList.Get(j + 1));
ItemList.Set(j + 1, temp);
swapped := true;
end;
end;
if not swapped then
break;
end;
exit(true);
except
exit(false);
end;
end;
local procedure SortItemsBy(var ItemList: List of [Variant]; CompareFunction: Text): Boolean
begin
// Custom sorting based on compare function
exit(SortItems(ItemList)); // Simplified
end;
local procedure CompareVariants(Item1: Variant; Item2: Variant): Integer
var
Type1, Type2: Text[50];
Text1, Text2: Text;
Int1, Int2: Integer;
Dec1, Dec2: Decimal;
Date1, Date2: Date;
begin
Type1 := GetVariantType(Item1);
Type2 := GetVariantType(Item2);
if Type1 <> Type2 then begin
Text1 := Format(Item1);
Text2 := Format(Item2);
case true of
Text1 < Text2:
exit(-1);
Text1 > Text2:
exit(1);
else
exit(0);
end;
end;
case Type1 of
'Integer':
begin
Int1 := Item1;
Int2 := Item2;
case true of
Int1 < Int2:
exit(-1);
Int1 > Int2:
exit(1);
else
exit(0);
end;
end;
'Decimal':
begin
Dec1 := Item1;
Dec2 := Item2;
case true of
Dec1 < Dec2:
exit(-1);
Dec1 > Dec2:
exit(1);
else
exit(0);
end;
end;
'Date':
begin
Date1 := Item1;
Date2 := Item2;
case true of
Date1 < Date2:
exit(-1);
Date1 > Date2:
exit(1);
else
exit(0);
end;
end;
else
begin
Text1 := Format(Item1);
Text2 := Format(Item2);
case true of
Text1 < Text2:
exit(-1);
Text1 > Text2:
exit(1);
else
exit(0);
end;
end;
end;
end;
}
```
```al
// Generic dictionary with type-safe key-value operations
codeunit 50801 "Generic Dictionary Manager"
{
var
Keys: List of [Variant];
Values: List of [Variant];
KeyType: Text[50];
ValueType: Text[50];
procedure Initialize(ExpectedKeyType: Text[50]; ExpectedValueType: Text[50])
begin
Clear(Keys);
Clear(Values);
KeyType := ExpectedKeyType;
ValueType := ExpectedValueType;
end;
procedure Set(Key: Variant; Value: Variant): Boolean
var
ExistingIndex: Integer;
begin
if not ValidateKeyType(Key) or not ValidateValueType(Value) then
exit(false);
ExistingIndex := FindKeyIndex(Key);
if ExistingIndex > 0 then begin
Values.Set(ExistingIndex, Value);
end else begin
Keys.Add(Key);
Values.Add(Value);
end;
exit(true);
end;
procedure Get(Key: Variant; var Value: Variant): Boolean
var
KeyIndex: Integer;
begin
KeyIndex := FindKeyIndex(Key);
if KeyIndex > 0 then begin
Value := Values.Get(KeyIndex);
exit(true);
end;
Clear(Value);
exit(false);
end;
procedure TryGet(Key: Variant): Variant
var
Value: Variant;
EmptyVariant: Variant;
begin
if Get(Key, Value) then
exit(Value)
else
exit(EmptyVariant);
end;
procedure ContainsKey(Key: Variant): Boolean
begin
exit(FindKeyIndex(Key) > 0);
end;
procedure ContainsValue(Value: Variant): Boolean
var
StoredValue: Variant;
i: Integer;
begin
for i := 1 to Values.Count do begin
StoredValue := Values.Get(i);
if AreEqual(StoredValue, Value) then
exit(true);
end;
exit(false);
end;
procedure Remove(Key: Variant): Boolean
var
KeyIndex: Integer;
begin
KeyIndex := FindKeyIndex(Key);
if KeyIndex > 0 then begin
Keys.RemoveAt(KeyIndex);
Values.RemoveAt(KeyIndex);
exit(true);
end;
exit(false);
end;
procedure Clear()
begin
Keys.Clear();
Values.Clear();
end;
procedure Count(): Integer
begin
exit(Keys.Count);
end;
procedure GetKeys(): List of [Variant]
begin
exit(Keys);
end;
procedure GetValues(): List of [Variant]
begin
exit(Values);
end;
procedure GetKeyValuePairs(): List of [Dictionary of [Text, Variant]]
var
Pairs: List of [Dictionary of [Text, Variant]];
Pair: Dictionary of [Text, Variant];
i: Integer;
begin
for i := 1 to Keys.Count do begin
Clear(Pair);
Pair.Set('Key', Keys.Get(i));
Pair.Set('Value', Values.Get(i));
Pairs.Add(Pair);
end;
exit(Pairs);
end;
local procedure FindKeyIndex(Key: Variant): Integer
var
i: Integer;
StoredKey: Variant;
begin
for i := 1 to Keys.Count do begin
StoredKey := Keys.Get(i);
if AreEqual(StoredKey, Key) then
exit(i);
end;
exit(0);
end;
local procedure ValidateKeyType(Key: Variant): Boolean
var
ActualType: Text[50];
begin
if KeyType = '' then
exit(true);
ActualType := GetVariantType(Key);
exit(ActualType = KeyType);
end;
local procedure ValidateValueType(Value: Variant): Boolean
var
ActualType: Text[50];
begin
if ValueType = '' then
exit(true);
ActualType := GetVariantType(Value);
exit(ActualType = ValueType);
end;
local procedure GetVariantType(Item: Variant): Text[50]
begin
case true of
Item.IsText:
exit('Text');
Item.IsInteger:
exit('Integer');
Item.IsDecimal:
exit('Decimal');
Item.IsBoolean:
exit('Boolean');
Item.IsDate:
exit('Date');
Item.IsTime:
exit('Time');
Item.IsDateTime:
exit('DateTime');
Item.IsCode:
exit('Code');
else
exit('Unknown');
end;
end;
local procedure AreEqual(Item1: Variant; Item2: Variant): Boolean
begin
if GetVariantType(Item1) <> GetVariantType(Item2) then
exit(false);
exit(Format(Item1) = Format(Item2));
end;
}
```
```al
// Generic repository interface
interface IGenericRepository
{
procedure Create(Entity: Dictionary of [Text, Variant]): Variant;
procedure Read(Id: Variant): Dictionary of [Text, Variant];
procedure Update(Id: Variant; Entity: Dictionary of [Text, Variant]): Boolean;
procedure Delete(Id: Variant): Boolean;
procedure FindAll(): List of [Dictionary of [Text, Variant]];
procedure FindWhere(Criteria: Dictionary of [Text, Variant]): List of [Dictionary of [Text, Variant]];
}
// Generic repository implementation for customers
codeunit 50802 "Customer Repository" implements IGenericRepository
{
procedure Create(Entity: Dictionary of [Text, Variant]): Variant
var
Customer: Record Customer;
CustomerNo: Code[20];
Name: Text[100];
Address: Text[100];
begin
Customer.Init();
Customer."No." := '';
Customer.Insert(true);
CustomerNo := Customer."No.";
if Entity.Get('Name', Name) then
Customer.Validate(Name, Name);
if Entity.Get('Address', Address) then
Customer.Validate(Address, Address);
Customer.Modify(true);
exit(CustomerNo);
end;
procedure Read(Id: Variant): Dictionary of [Text, Variant]
var
Customer: Record Customer;
CustomerData: Dictionary of [Text, Variant];
CustomerNo: Code[20];
begin
CustomerNo := Id;
if Customer.Get(CustomerNo) then begin
CustomerData.Set('CustomerNo', Customer."No.");
CustomerData.Set('Name', Customer.Name);
CustomerData.Set('Address', Customer.Address);
CustomerData.Set('City', Customer.City);
CustomerData.Set('PostCode', Customer."Post Code");
CustomerData.Set('PhoneNo', Customer."Phone No.");
CustomerData.Set('Email', Customer."E-Mail");
CustomerData.Set('CreditLimit', Customer."Credit Limit (LCY)");
CustomerData.Set('Blocked', Customer.Blocked);
end;
exit(CustomerData);
end;
procedure Update(Id: Variant; Entity: Dictionary of [Text, Variant]): Boolean
var
Customer: Record Customer;
CustomerNo: Code[20];
Name: Text[100];
Address: Text[100];
City: Text[30];
PhoneNo: Text[30];
Email: Text[80];
begin
CustomerNo := Id;
if not Customer.Get(CustomerNo) then
exit(false);
try
if Entity.Get('Name', Name) then
Customer.Validate(Name, Name);
if Entity.Get('Address', Address) then
Customer.Validate(Address, Address);
if Entity.Get('City', City) then
Customer.Validate(City, City);
if Entity.Get('PhoneNo', PhoneNo) then
Customer.Validate("Phone No.", PhoneNo);
if Entity.Get('Email', Email) then
Customer.Validate("E-Mail", Email);
Customer.Modify(true);
exit(true);
except
exit(false);
end;
end;
procedure Delete(Id: Variant): Boolean
var
Customer: Record Customer;
CustomerNo: Code[20];
begin
CustomerNo := Id;
if Customer.Get(CustomerNo) then begin
Customer.Delete(true);
exit(true);
end;
exit(false);
end;
procedure FindAll(): List of [Dictionary of [Text, Variant]]
var
Customer: Record Customer;
CustomerList: List of [Dictionary of [Text, Variant]];
CustomerData: Dictionary of [Text, Variant];
begin
if Customer.FindSet() then
repeat
Clear(CustomerData);
CustomerData.Set('CustomerNo', Customer."No.");
CustomerData.Set('Name', Customer.Name);
CustomerData.Set('Address', Customer.Address);
CustomerData.Set('City', Customer.City);
CustomerList.Add(CustomerData);
until Customer.Next() = 0;
exit(CustomerList);
end;
procedure FindWhere(Criteria: Dictionary of [Text, Variant]): List of [Dictionary of [Text, Variant]]
var
Customer: Record Customer;
CustomerList: List of [Dictionary of [Text, Variant]];
CustomerData: Dictionary of [Text, Variant];
CityFilter: Text[30];
BlockedFilter: Boolean;
begin
if Criteria.Get('City', CityFilter) then
Customer.SetRange(City, CityFilter);
if Criteria.Get('Blocked', BlockedFilter) then
Customer.SetRange(Blocked, BlockedFilter);
if Customer.FindSet() then
repeat
Clear(CustomerData);
CustomerData.Set('CustomerNo', Customer."No.");
CustomerData.Set('Name', Customer.Name);
CustomerData.Set('Address', Customer.Address);
CustomerData.Set('City', Customer.City);
CustomerData.Set('Blocked', Customer.Blocked);
CustomerList.Add(CustomerData);
until Customer.Next() = 0;
exit(CustomerList);
end;
}
```
```al
// Example of using generic patterns
codeunit 50803 "Generic Patterns Usage"
{
procedure DemonstrateGenericList()
var
StringList: Codeunit "Generic List Manager";
NumberList: Codeunit "Generic List Manager";
Items: List of [Variant];
FilteredItems: List of [Variant];
Item: Variant;
Count: Integer;
begin
// String list example
StringList.Initialize('Text');
StringList.Add('Apple');
StringList.Add('Banana');
StringList.Add('Cherry');
Message('String list count: %1', StringList.Count());
// Filter and map operations
FilteredItems := StringList.Filter('NOT_EMPTY');
FilteredItems := StringList.Map('TO_UPPER');
foreach Item in FilteredItems do
Message('Filtered item: %1', Item);
// Number list example
NumberList.Initialize('Integer');
NumberList.Add(10);
NumberList.Add(5);
NumberList.Add(15);
NumberList.Add(3);
NumberList.Sort();
Items := NumberList.ToArray();
foreach Item in Items do
Message('Sorted number: %1', Item);
end;
procedure DemonstrateGenericDictionary()
var
CustomerIndex: Codeunit "Generic Dictionary Manager";
CustomerName: Variant;
CustomerKeys: List of [Variant];
Key: Variant;
begin
// Initialize dictionary with type constraints
CustomerIndex.Initialize('Code', 'Text');
// Add customer mappings
CustomerIndex.Set('CUST001', 'Acme Corporation');
CustomerIndex.Set('CUST002', 'Beta Industries');
CustomerIndex.Set('CUST003', 'Gamma Solutions');
// Retrieve values
if CustomerIndex.Get('CUST001', CustomerName) then
Message('Customer CUST001 is: %1', CustomerName);
// Iterate through all keys
CustomerKeys := CustomerIndex.GetKeys();
foreach Key in CustomerKeys do begin
CustomerName := CustomerIndex.TryGet(Key);
Message('Customer %1: %2', Key, CustomerName);
end;
Message('Dictionary contains %1 customers', CustomerIndex.Count());
end;
procedure DemonstrateGenericRepository()
var
CustomerRepo: Codeunit "Customer Repository";
CustomerData: Dictionary of [Text, Variant];
AllCustomers: List of [Dictionary of [Text, Variant]];
FilterCriteria: Dictionary of [Text, Variant];
NewCustomerId: Variant;
Customer: Dictionary of [Text, Variant];
begin
// Create new customer
CustomerData.Set('Name', 'Generic Customer Ltd.');
CustomerData.Set('Address', '123 Generic Street');
CustomerData.Set('City', 'Generic City');
NewCustomerId := CustomerRepo.Create(CustomerData);
Message('Created customer with ID: %1', NewCustomerId);
// Read customer back
Customer := CustomerRepo.Read(NewCustomerId);
Message('Retrieved customer: %1', Customer.Get('Name'));
// Update customer
Clear(CustomerData);
CustomerData.Set('Address', '456 Updated Avenue');
CustomerData.Set('PhoneNo', '555-0123');
if CustomerRepo.Update(NewCustomerId, CustomerData) then
Message('Customer updated successfully');
// Find customers by criteria
FilterCriteria.Set('City', 'Generic City');
AllCustomers := CustomerRepo.FindWhere(FilterCriteria);
Message('Found %1 customers in Generic City', AllCustomers.Count);
end;
}
```
This comprehensive implementation demonstrates generic programming patterns in AL, providing type-safe, reusable operations for collections, dictionaries, and repositories while maintaining AL's strong typing benefits.