Práce s velkými generickými kolekcemi v Lotusscriptu

Lotusscript není přísně typový jazyk, což ho samo o sobě dost diskvalifikuje. Navíc nemá příliš velké množství vlastních nástrojů pro zpracování většího množství dat. Pokud chce člověk psát čistý kód, musí si připravit řadu vlastních znovupoužitelných nástrojů.

Ukážu, jak si připravit třídy pro zpracování větších kolekcí dat, včetně třídění. Použití je určeno třeba pro různé importy z relačních databází, synchronizace apod.

Jediný datový typ, který se na ukládání větších objemů dat hodí, je List. Array je limitován svým indexem typu Integer, tedy velikostí 32kB. Dále je potřeba říci, že pokud chceme pracovat s generickými objekty, nezbývá nám, než použít Variant. Tedy budeme pracovat s List As Variant.

Další upozornění na úvod je, že i rekurze má v Lotusscriptu svá omezení, a tím je zásobník s kapacitou také 32kB. Budeme si tedy muset napsat vlastní zásobník.

List má tu výhodu, že je limitován jen pamětí. Při testech jsem do něj narval přes dvě stě tisíc stringů o velikosti UNID a v task manageru se to projevilo jen malým schůdkem v používané paměti. Domino je obecně známo tím, že sežerou všechno, co mu systém dá. Tedy je nutno i v tomto případě plánovat.

Takže máme rozhodnuto, že budeme vytvářet kolekce libovolných custom objektů. Proč ne Notes objektů? Protože ty nejsou kontrolovatelné a jsou to low-level objekty, které fungují trochu jinak. Protože nelze Notes objekty dědit, je nutno je obalit custom objektem. To se udělá snadno.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Class MyDocument
    Private oDoc as NotesDocument
    Public Sub New(doc as NotesDocument)
        Set Me.oDoc = doc
    End Sub
    Public Property Get Document As NotesDocument
        Set Document = Me.oDoc
    End Property
    Public Property UniversalID As String
        If Not Me.oDoc Is Nothing Then
            Let UniversalID = Me.oDoc.UniversalID
        End If
    End Property
    Public Property IsNewNote As Variant
        If Not Me.oDoc Is Nothing Then
            Let IsNewNote = Me.oDoc.IsNewNote
        End If
    End Property
    REM etc... other methods and properties
End Class

Abychom zajistili integritu dat, určíme si, že náš custom objekt musí implementovat nějaké rozhraní. Protože rozhraní nelze v Lotusscriptu definovat jinak než třídou, napíšeme si třídu. Náš objekt pak bude z této třídy dědit. Definujme si jednoduché pravidlo, že bázový objekt bude mít pouze property ID.


CollectionItem
——————————————————————————–
<Property> ID: String

Máme tedy rozhraní pro budoucí členy kolekce, je na čase, pustit se do samotné kolekce. Kolekce by měla implementovat nějaké rozhraní s metodami pro vkládání, vybírání dat, či pohyb v kolekci.


Collection
——————————————————————————–
<Property> UpperBound: Long
<Property> Count: Long
<Property> IsEmpty: Boolean
<Property> IndexOf: Long
<Property> Contains: Boolean
Add(CollectionItem) : void
Insert(CollectionItem, Long) : void
Remove(String) : void
Get(String) : void
MoveNext() : Boolean
Reset() : void
Clear() : void
ToArray() : Array
ToString() : String

Teď je otázka, jak do toho zaimplementovat třídění. Já jsem si udělal dvě nové třídy, které rozšiřují tyto bázové – SortableCollectionItem a SortableCollection. Chtěl jsem, aby to bylo co nejjednodušší, tudíž mi přišlo správné to rozdělit. V čem je ten problém?

Abychom mohli třídit kolekce, musíme vědět, podle čeho třídit. C# to například řeší pomocí delegátů. Zavoláte metodu Sort v nějaké takové kolekci a předáte jí delegáta na metodu, která zajišťuje porovnání, popř. předáte tím delegátem rovnou anonymní metodu. Parametry té metody jsou dva – jde o prvky, které se v dané kolekci vždy porovnávají, přičemž je na vývojáři, jakým způsobem si to porovnání implementuje.

Delegáty tu nemáme, notebene anonymní metody – musíme to vyřešit jinak. Já se rozhodl jít cestou nejmenšího odporu a ve třídě SortableCollectionItem, která rozšiřuje CollectionItem jsem zavedl novou veřejnou metodu CompareTo, která očekává jako parametr objekt stejného typu, jako je ona sama. Vzhledem k tomu, že plnění kolekce zajišťuji pouze přes veřejné rozhraní metodami Add a Insert, mám v nich ošetřeno, aby do kolekce vstupovali vždy potomci třídy CollectionItem. Vzhledem k netypovosti LS si tam vyzvedávám hodnotu v property ID a odchytávám případnou chybu, kterou vyhodí vstupující objekt bez této property. Ovšem právě díky té netypovosti už nemůžu ošetřit, zda je vstupující objekt potomkem třídy SortableCollectionItem. Tudíž to je taková malá úlitba tomu jazyku a je na vývojáři, aby si zajistil konzistenci těch členů.

Takže v potomkovi typu SortableCollectionItem jsem zavel metodu CompareTo, kterou musí vývojář v každém dalším potomkovi implementovat. Samotné třídění pak zajišťuje metoda Sort třídy SortableCollection. A to právě porovnáním s pomocí CompareTo.

Výsledný testovací kód pak vypadá takto.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Public Class TestItemInt As SortableCollectionItem
    Private oI As Integer
    Public Property Set I As Integer
        Let Me.oI = I
    End Property
    Public Property Get I As Integer
        Let I = Me.oI
    End Property
    Public Function CompareTo(collectionItem As Variant) As Boolean
        If collectionItem.I > Me.I Then
            Let CompareTo = True
        End If
    End Function
    Public Function ToString As String
        Let ToString = CStr(Me.oI)
    End Function
End Class
Sub Initialize
    Dim item As TestItemInt
    Dim col As New SortableCollection
    Dim i As Integer
    For i = 1 To 10
        Set item = New TestItemInt
        Let item.I = Rnd() * 100
        Call col.Add(item)
    Next
    Call col.Sort
    While col.MoveNext
        Print col.Current.ToString
    Wend
End Sub

Třídění je zajištěno standardním Quicksortem, ovšem nerekurzívním. Jak jsem uvedl, rekurze jsou v Lotusscriptu limitovány zásobníkem s kapacitou 32kB, tudíž jsem si musel napsat zásobník vlastní.

Zásobník je poměrně jednoduchý objekt s metodami Push, Pop a Top a vnitřně je realizován opět pomocí datového typu List As Variant.

Jen pro srovnání – tady jsou dvě ukázky Quicksortu v Lotusscriptu – rekurzivní a nerekurzivní varianta. Realizováno jen pro obyčejný Array.

Jak vidíte, práce s velkými kolekcemi není ani v lotusscriptu žádná věda. Na závěr celé zdrojáky (Collection, SortableCollection a Stack)

Komentování je uzavřeno.