1 // Written in the D programming language.
2 
3 /**
4 
5 This module implements object collection.
6 
7 Wrapper around array of objects, providing a set of useful operations, and handling of object ownership.
8 
9 Optionally can be owner of its items if instanciated with ownItems=true - will destroy removed items.
10 
11 
12 Synopsis:
13 
14 ----
15 import dlangui.core.collections;
16 
17 // add
18 Collection!Widget widgets;
19 widgets ~= new Widget("id1");
20 widgets ~= new Widget("id2");
21 Widget w3 = new Widget("id3");
22 widgets ~= w3;
23 
24 // remove by index
25 widgets.remove(1);
26 
27 // foreach
28 foreach(w; widgets)
29     writeln("widget: ", w.id);
30 
31 // remove by value
32 widgets -= w3;
33 writeln(widgets[0].id);
34 ----
35 
36 
37 Copyright: Vadim Lopatin, 2014
38 License:   Boost License 1.0
39 Authors:   Vadim Lopatin, coolreader.org@gmail.com
40 */
41 module dlangui.core.collections;
42 
43 import std.algorithm;
44 
45 /** 
46     Array based collection of items.
47 
48     Retains item order when during add/remove operations.
49 
50     Optionally destroys removed objects when instanciated with ownItems = true.
51 */
52 struct Collection(T, bool ownItems = false) {
53     private T[] _items;
54     private size_t _len;
55     /// returns true if there are no items in collection
56     @property bool empty() { return _len == 0; }
57     /// returns number of items in collection
58     @property size_t length() { return _len; }
59     /// returns currently allocated capacity (may be more than length)
60     @property size_t size() { return _items.length; }
61     /// change capacity (e.g. to reserve big space to avoid multiple reallocations)
62     @property void size(size_t newSize) {
63         if (_len > newSize)
64             length = newSize; // shrink
65         _items.length = newSize; 
66     }
67     /// returns number of items in collection
68     @property void length(size_t newSize) { 
69         if (newSize < _len) {
70             // shrink
71             static if (is(T == class) || is(T == struct)) {
72                 // clear items
73                 for (size_t i = newSize; i < _len; i++) {
74                     static if (ownItems)
75                         destroy(_items[i]);
76                     _items[i] = T.init;
77                 }
78             }
79         } else if (newSize > _len) {
80             // expand
81             if (_items.length < newSize)
82                 _items.length = newSize;
83         }
84         _len = newSize;
85     }
86     /// access item by index
87     ref T opIndex(size_t index) {
88         assert(index < _len);
89         return _items[index];
90     }
91     /// insert new item in specified position
92     void add(T item, size_t index = size_t.max) {
93         if (index > _len)
94             index = _len;
95         if (_items.length <= _len) {
96             if (_items.length < 4)
97                 _items.length = 4;
98             else
99                 _items.length = _items.length * 2;
100         }
101         if (index < _len) {
102             for (size_t i = _len; i > index; i--)
103                 _items[i] = _items[i - 1];
104         }
105         _items[index] = item;
106         _len++;
107     }
108     /// add all items from other collection
109     void addAll(ref Collection!(T, ownItems) v) {
110         for (int i = 0; i < v.length; i++)
111             add(v[i]);
112     }
113     /// support for appending (~=, +=) and removing by value (-=)
114     ref Collection opOpAssign(string op)(T item) {
115         static if (op.equal("~") || op.equal("+")) {
116             // append item to end of collection
117             add(item);
118             return this;
119         } else if (op.equal("-")) {
120             // remove item from collection, if present
121             removeValue(item);
122             return this;
123         } else {
124             assert(false, "not supported opOpAssign " ~ op);
125         }
126     }
127     /// returns index of first occurence of item, size_t.max if not found
128     size_t indexOf(T item) {
129         for (size_t i = 0; i < _len; i++)
130             if (_items[i] == item)
131                 return i;
132         return size_t.max;
133     }
134     /// remove single item, returning removed item
135     T remove(size_t index) {
136         assert(index < _len);
137         T result = _items[index];
138         for (size_t i = index; i + 1 < _len; i++)
139             _items[i] = _items[i + 1];
140         _len--;
141         _items[_len] = T.init;
142         return result;
143     }
144     /// remove single item by value - if present in collection, returning true if item was found and removed
145     bool removeValue(T value) {
146         size_t index = indexOf(value);
147         if (index == size_t.max)
148             return false;
149         T res = remove(index);
150         static if (ownItems)
151             destroy(res);
152         return true;
153     }
154     /// support of foreach with reference
155     int opApply(int delegate(ref T param) op) {
156         int result = 0;
157         for (size_t i = 0; i < _len; i++) {
158             result = op(_items[i]);
159             if (result)
160                 break;
161         }
162         return result;
163     }
164     /// remove all items
165     void clear() {
166         static if (is(T == class) || is(T == struct)) {
167             /// clear references
168             for(size_t i = 0; i < _len; i++) {
169                 static if (ownItems)
170                     destroy(_items[i]);
171                 _items[i] = T.init;
172             }
173         }
174         _len = 0;
175         _items = null;
176     }
177 
178     //===================================
179     // stack/queue-like ops
180 
181     /// remove first item
182     @property T popFront() {
183         if (empty)
184             return T.init; // no items
185         return remove(0);
186     }
187 
188     /// peek first item
189     @property T peekFront() {
190         if (empty)
191             return T.init; // no items
192         return _items[0];
193     }
194 
195     /// insert item at beginning of collection
196     void pushFront(T item) {
197         add(item, 0);
198     }
199 
200     /// remove last item
201     @property T popBack() {
202         if (empty)
203             return T.init; // no items
204         return remove(length - 1);
205     }
206 
207     /// peek last item
208     @property T peekBack() {
209         if (empty)
210             return T.init; // no items
211         return _items[length - 1];
212     }
213 
214     /// insert item at end of collection
215     void pushBack(T item) {
216         add(item);
217     }
218 
219     /// peek first item
220     @property T front() {
221         if (empty)
222             return T.init; // no items
223         return _items[0];
224     }
225 
226     /// peek last item
227     @property T back() {
228         if (empty)
229             return T.init; // no items
230         return _items[_len - 1];
231     }
232     /// removes all items on destroy
233     ~this() {
234         clear();
235     }
236 }
237 
238 
239 /** object list holder, owning its objects - on destroy of holder, all own objects will be destroyed */
240 struct ObjectList(T) {
241     protected T[] _list;
242     protected int _count;
243     /** returns count of items */
244     @property int count() const { return _count; }
245     /** get item by index */
246     T get(int index) {
247         assert(index >= 0 && index < _count, "child index out of range");
248         return _list[index];
249     }
250     /** get const item by index */
251     const(T) get(int index)  const {
252         assert(index >= 0 && index < _count, "child index out of range");
253         return _list[index];
254     }
255     /// get item by index
256     T opIndex(int index) {
257         return get(index);
258     }
259     /** add item to list */
260     T add(T item) {
261         if (_list.length <= _count) // resize
262             _list.length = _list.length < 4 ? 4 : _list.length * 2;
263         _list[_count++] = item;
264         return item;
265     }
266     /** add item to list */
267     T insert(T item, int index = -1) {
268         if (index > _count || index < 0)
269             index = _count;
270         if (_list.length <= _count) // resize
271             _list.length = _list.length < 4 ? 4 : _list.length * 2;
272         for (int i = _count; i > index; i--)
273             _list[i] = _list[i - 1];
274         _list[index] = item;
275         _count++;
276         return item;
277     }
278     /** find child index for item, return -1 if not found */
279     int indexOf(T item) {
280         if (item is null)
281             return -1;
282         for (int i = 0; i < _count; i++)
283             if (_list[i] == item)
284                 return i;
285         return -1;
286     }
287     /** find child index for item by id, return -1 if not found */
288     static if (__traits(hasMember, T, "compareId")) {
289         int indexOf(string id) {
290             for (int i = 0; i < _count; i++)
291                 if (_list[i].compareId(id))
292                     return i;
293             return -1;
294         }
295     }
296     /** remove item from list, return removed item */
297     T remove(int index) {
298         assert(index >= 0 && index < _count, "child index out of range");
299         T item = _list[index];
300         for (int i = index; i < _count - 1; i++)
301             _list[i] = _list[i + 1];
302         _count--;
303         return item;
304     }
305     /** Replace item with another value, destroy old value. */
306     void replace(T item, int index) {
307         T old = _list[index];
308         _list[index] = item;
309         destroy(old);
310     }
311     /** Replace item with another value, destroy old value. */
312     void replace(T newItem, T oldItem) {
313         int idx = indexOf(oldItem);
314         if (newItem is null) {
315             if (idx >= 0) {
316                 T item = remove(idx);
317                 destroy(item);
318             }
319         } else {
320             if (idx >= 0)
321                 replace(newItem, idx);
322             else
323                 add(newItem);
324         }
325     }
326     /** remove and destroy all items */
327 	void clear(bool destroyObj = true) {
328         for (int i = 0; i < _count; i++) {
329 			if(destroyObj) {
330 				destroy(_list[i]);
331             }
332             _list[i] = null;
333         }
334         _count = 0;
335     }
336     /// Support foreach
337     int opApply(int delegate(ref T) callback) {
338         int res = 0;
339         for(int i = 0; i < _count; i++) {
340             res = callback(_list[i]);
341             if (res)
342                 break;
343         }
344         return res;
345     }
346     ~this() {
347         clear();
348     }
349 }
350