1/*
2* Copyright (c) 2009-2013 Yorba Foundation
3*
4* This program is free software; you can redistribute it and/or
5* modify it under the terms of the GNU Lesser General Public
6* License as published by the Free Software Foundation; either
7* version 2.1 of the License, or (at your option) any later version.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12* General Public License for more details.
13*
14* You should have received a copy of the GNU General Public
15* License along with this program; if not, write to the
16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17* Boston, MA 02110-1301 USA
18*/
19
20public class DataCollection {
21    public const int64 INVALID_OBJECT_ORDINAL = -1;
22
23    private class MarkerImpl : Object, Marker {
24        public DataCollection owner;
25        public Gee.HashSet<DataObject> marked = new Gee.HashSet<DataObject> ();
26        public int freeze_count = 0;
27
28        public MarkerImpl (DataCollection owner) {
29            this.owner = owner;
30
31            // if items are removed from main collection, they're removed from the marked list
32            // as well
33            owner.items_removed.connect (on_items_removed);
34        }
35
36        ~MarkerImpl () {
37            owner.items_removed.disconnect (on_items_removed);
38        }
39
40        public void mark (DataObject object) {
41            assert (owner.internal_contains (object));
42
43            marked.add (object);
44        }
45
46        public void unmark (DataObject object) {
47            assert (owner.internal_contains (object));
48
49            marked.remove (object);
50        }
51
52        public bool toggle (DataObject object) {
53            assert (owner.internal_contains (object));
54
55            if (marked.contains (object)) {
56                marked.remove (object);
57            } else {
58                marked.add (object);
59            }
60
61            return marked.contains (object);
62        }
63
64        public void mark_many (Gee.Collection<DataObject> list) {
65            foreach (DataObject object in list) {
66                assert (owner.internal_contains (object));
67
68                marked.add (object);
69            }
70        }
71
72        public void unmark_many (Gee.Collection<DataObject> list) {
73            foreach (DataObject object in list) {
74                assert (owner.internal_contains (object));
75
76                marked.remove (object);
77            }
78        }
79
80        public void mark_all () {
81            foreach (DataObject object in owner.get_all ())
82                marked.add (object);
83        }
84
85        public int get_count () {
86            return (marked != null) ? marked.size : freeze_count;
87        }
88
89        public Gee.Collection<DataObject> get_all () {
90            Gee.ArrayList<DataObject> copy = new Gee.ArrayList<DataObject> ();
91            copy.add_all (marked);
92
93            return copy;
94        }
95
96        private void on_items_removed (Gee.Iterable<DataObject> removed) {
97            foreach (DataObject object in removed)
98                marked.remove (object);
99        }
100
101        // This method is called by DataCollection when it starts iterating over the marked list ...
102        // the marker at this point stops monitoring the collection, preventing a possible
103        // removal during an iteration, which is bad.
104        public void freeze () {
105            owner.items_removed.disconnect (on_items_removed);
106        }
107
108        public void finished () {
109            if (marked != null)
110                freeze_count = marked.size;
111
112            marked = null;
113        }
114
115        public bool is_valid (DataCollection collection) {
116            return (collection == owner) && (marked != null);
117        }
118    }
119
120    private string name;
121    private DataSet dataset = new DataSet ();
122    private Gee.HashMap < string, Value?> properties = new Gee.HashMap < string, Value?> ();
123    private int64 object_ordinal_generator = 0;
124    private int notifies_frozen = 0;
125    private Gee.HashMap<DataObject, Alteration> frozen_items_altered = null;
126    private bool fire_ordering_changed = false;
127
128    // When this signal has been fired, the added items are part of the collection
129    public virtual signal void items_added (Gee.Iterable<DataObject> added) {
130    }
131
132    // When this signal is fired, the removed items are no longer part of the collection
133    public virtual signal void items_removed (Gee.Iterable<DataObject> removed) {
134    }
135
136    // When this signal is fired, the removed items are no longer part of the collection
137    public virtual signal void contents_altered (Gee.Iterable<DataObject>? added,
138            Gee.Iterable<DataObject>? removed) {
139    }
140
141    // This signal fires whenever any (or multiple) items in the collection signal they've been
142    // altered.
143    public virtual signal void items_altered (Gee.Map<DataObject, Alteration> items) {
144    }
145
146    // Fired when a new sort comparator is registered or an item has moved in the ordering due to
147    // an alteration.
148    public virtual signal void ordering_changed () {
149    }
150
151    // Fired when a collection property is set.  The old value is passed as well, null if not set
152    // previously.
153    public virtual signal void property_set (string name, Value? old, Value val) {
154    }
155
156    // Fired when a collection property is cleared.
157    public virtual signal void property_cleared (string name) {
158    }
159
160    // Fired when "altered" signal (and possibly other related signals, depending on the subclass)
161    // is frozen.
162    public virtual signal void frozen () {
163    }
164
165    // Fired when "altered" signal (and other related signals, depending on the subclass) is
166    // restored (thawed).
167    public virtual signal void thawed () {
168    }
169
170    public DataCollection (string name) {
171        this.name = name;
172    }
173
174    ~DataCollection () {
175#if TRACE_DTORS
176        debug ("DTOR: DataCollection %s", name);
177#endif
178    }
179
180    public virtual string to_string () {
181        return "%s (%d)".printf (name, get_count ());
182    }
183
184    // use notifies to ensure proper chronology of signal handling
185    protected virtual void notify_items_added (Gee.Iterable<DataObject> added) {
186        items_added (added);
187    }
188
189    protected virtual void notify_items_removed (Gee.Iterable<DataObject> removed) {
190        items_removed (removed);
191    }
192
193    protected virtual void notify_contents_altered (Gee.Iterable<DataObject>? added,
194            Gee.Iterable<DataObject>? removed) {
195        contents_altered (added, removed);
196    }
197
198    protected virtual void notify_items_altered (Gee.Map<DataObject, Alteration> items) {
199        items_altered (items);
200    }
201
202    protected virtual void notify_ordering_changed () {
203        ordering_changed ();
204    }
205
206    protected virtual void notify_property_set (string name, Value? old, Value val) {
207        property_set (name, old, val);
208    }
209
210    protected virtual void notify_property_cleared (string name) {
211        property_cleared (name);
212    }
213
214    // A singleton list is used when a single item has been added/remove/selected/unselected
215    // and needs to be reported via a signal, which uses a list as a parameter ... although this
216    // seems wasteful, can't reuse a single singleton list because it's possible for a method
217    // that needs it to be called from within a signal handler for another method, corrupting the
218    // shared list's contents mid-signal
219    protected static Gee.Collection<DataObject> get_singleton (DataObject object) {
220        return new SingletonCollection<DataObject> (object);
221    }
222
223    protected static Gee.Map<DataObject, Alteration> get_alteration_singleton (DataObject object,
224            Alteration alteration) {
225        Gee.Map<DataObject, Alteration> map = new Gee.HashMap<DataObject, Alteration> ();
226        map.set (object, alteration);
227
228        return map;
229    }
230
231    public virtual bool valid_type (DataObject object) {
232        return true;
233    }
234
235    public unowned Comparator get_comparator () {
236        return dataset.get_comparator ();
237    }
238
239    public unowned ComparatorPredicate get_comparator_predicate () {
240        return dataset.get_comparator_predicate ();
241    }
242
243    public virtual void set_comparator (Comparator comparator, ComparatorPredicate? predicate) {
244        dataset.set_comparator (comparator, predicate);
245        notify_ordering_changed ();
246    }
247
248    // Return to natural ordering of DataObjects, which is order-added
249    public virtual void reset_comparator () {
250        dataset.reset_comparator ();
251        notify_ordering_changed ();
252    }
253
254    public virtual Gee.Collection<DataObject> get_all () {
255        return dataset.get_all ();
256    }
257
258    protected DataSet get_dataset_copy () {
259        return dataset.copy ();
260    }
261
262    public virtual int get_count () {
263        return dataset.get_count ();
264    }
265
266    public virtual DataObject? get_at (int index) {
267        return dataset.get_at (index);
268    }
269
270    public virtual int index_of (DataObject object) {
271        return dataset.index_of (object);
272    }
273
274    public virtual bool contains (DataObject object) {
275        return internal_contains (object);
276    }
277
278    // Because subclasses may filter out objects (by overriding key methods here), need an
279    // internal_contains for consistency checking.
280    private bool internal_contains (DataObject object) {
281        if (!dataset.contains (object))
282            return false;
283
284        assert (object.get_membership () == this);
285
286        return true;
287    }
288
289    private void internal_add (DataObject object) {
290        assert (valid_type (object));
291
292        object.internal_set_membership (this, object_ordinal_generator++);
293
294        bool added = dataset.add (object);
295        assert (added);
296    }
297
298    private void internal_add_many (Gee.List<DataObject> objects, ProgressMonitor? monitor) {
299        int count = objects.size;
300        for (int ctr = 0; ctr < count; ctr++) {
301            DataObject object = objects.get (ctr);
302            assert (valid_type (object));
303
304            object.internal_set_membership (this, object_ordinal_generator++);
305
306            if (monitor != null)
307                monitor (ctr, count);
308        }
309
310        bool added = dataset.add_many (objects);
311        assert (added);
312    }
313
314    private void internal_remove (DataObject object) {
315        bool removed = dataset.remove (object);
316        assert (removed);
317
318        object.internal_clear_membership ();
319    }
320
321    // Returns false if item is already part of the collection.
322    public virtual bool add (DataObject object) {
323        if (internal_contains (object)) {
324            debug ("%s cannot add %s: already present", to_string (), object.to_string ());
325
326            return false;
327        }
328
329        internal_add (object);
330
331        // fire signal after added using singleton list
332        Gee.Collection<DataObject> added = get_singleton (object);
333        notify_items_added (added);
334        notify_contents_altered (added, null);
335
336        // This must be called *after* the DataCollection has signalled.
337        object.notify_membership_changed (this);
338
339        return true;
340    }
341
342    // Returns the items added to the collection.
343    public virtual Gee.Collection<DataObject> add_many (Gee.Collection<DataObject> objects,
344            ProgressMonitor? monitor = null) {
345        Gee.ArrayList<DataObject> added = new Gee.ArrayList<DataObject> ();
346        foreach (DataObject object in objects) {
347            if (internal_contains (object)) {
348                debug ("%s cannot add %s: already present", to_string (), object.to_string ());
349
350                continue;
351            }
352
353            added.add (object);
354        }
355
356        int count = added.size;
357        if (count == 0)
358            return added;
359
360        internal_add_many (added, monitor);
361
362        // signal once all have been added
363        notify_items_added (added);
364        notify_contents_altered (added, null);
365
366        // This must be called *after* the DataCollection signals have fired.
367        for (int ctr = 0; ctr < count; ctr++)
368            added.get (ctr).notify_membership_changed (this);
369
370        return added;
371    }
372
373    // Obtain a marker to build a list of objects to perform an action upon.
374    public Marker start_marking () {
375        return new MarkerImpl (this);
376    }
377
378    // Obtain a marker with a single item marked.  More can be added.
379    public Marker mark (DataObject object) {
380        Marker marker = new MarkerImpl (this);
381        marker.mark (object);
382
383        return marker;
384    }
385
386    // Obtain a marker for all items in a collection.  More can be added.
387    public Marker mark_many (Gee.Collection<DataObject> objects) {
388        Marker marker = new MarkerImpl (this);
389        marker.mark_many (objects);
390
391        return marker;
392    }
393
394    // Iterate over all the marked objects performing a user-supplied action on each one.  The
395    // marker is invalid after calling this method.
396    public void act_on_marked (Marker m, MarkedAction action, ProgressMonitor? monitor = null,
397                               Object? user = null) {
398        MarkerImpl marker = (MarkerImpl) m;
399
400        assert (marker.is_valid (this));
401
402        // freeze the marker to prepare it for iteration
403        marker.freeze ();
404
405        uint64 count = 0;
406        uint64 total = marker.marked.size;
407
408        // iterate, breaking if the callback asks to stop
409        foreach (DataObject object in marker.marked) {
410            // although marker tracks when items are removed, catch it here as well
411            if (!internal_contains (object)) {
412                warning ("act_on_marked: marker holding ref to unknown %s", object.to_string ());
413
414                continue;
415            }
416
417            if (!action (object, user))
418                break;
419
420            if (monitor != null) {
421                if (!monitor (++count, total))
422                    break;
423            }
424        }
425
426        // invalidate the marker
427        marker.finished ();
428    }
429
430    // Remove marked items from collection.  This two-step process allows for iterating in a foreach
431    // loop and removing without creating a separate list.  The marker is invalid after this call.
432    public virtual void remove_marked (Marker m) {
433        MarkerImpl marker = (MarkerImpl) m;
434
435        assert (marker.is_valid (this));
436
437        // freeze the marker before signalling, so it doesn't remove all its items
438        marker.freeze ();
439
440        // remove everything in the marked list
441        Gee.ArrayList<DataObject> skipped = null;
442        foreach (DataObject object in marker.marked) {
443            // although marker should track items already removed, catch it here as well
444            if (!internal_contains (object)) {
445                warning ("remove_marked: marker holding ref to unknown %s", object.to_string ());
446
447                if (skipped == null)
448                    skipped = new Gee.ArrayList<DataObject> ();
449
450                skipped.add (object);
451
452                continue;
453            }
454
455            internal_remove (object);
456        }
457
458        if (skipped != null)
459            marker.marked.remove_all (skipped);
460
461        // signal after removing
462        if (marker.marked.size > 0) {
463            notify_items_removed (marker.marked);
464            notify_contents_altered (null, marker.marked);
465
466            // this must be called after the DataCollection has signalled.
467            foreach (DataObject object in marker.marked)
468                object.notify_membership_changed (null);
469        }
470
471        // invalidate the marker
472        marker.finished ();
473    }
474
475    public virtual void clear () {
476        if (dataset.get_count () == 0)
477            return;
478
479        // remove everything in the list, but have to maintain a new list for reporting the signal.
480        // Don't use an iterator, as list is modified in internal_remove ().
481        Gee.ArrayList<DataObject> removed = new Gee.ArrayList<DataObject> ();
482        do {
483            DataObject? object = dataset.get_at (0);
484            assert (object != null);
485
486            removed.add (object);
487            internal_remove (object);
488        } while (dataset.get_count () > 0);
489
490        // report after removal
491        notify_items_removed (removed);
492        notify_contents_altered (null, removed);
493
494        // This must be called after the DataCollection has signalled.
495        foreach (DataObject object in removed)
496            object.notify_membership_changed (null);
497    }
498
499    // close () must be called before disposing of the DataCollection, so all signals may be
500    // disconnected and all internal references to the collection can be dropped.  In the bare
501    // minimum, all items will be removed from the collection (and the appropriate signals and
502    // notify calls will be made).  Subclasses may fire other signals while disposing of their
503    // references.  However, if they are entirely synchronized on DataCollection's signals, that
504    // may be enough for them to clean up.
505    public virtual void close () {
506        clear ();
507    }
508
509    // This method is only called by DataObject to report when it has been altered, so observers of
510    // this collection may be notified as well.
511    public void internal_notify_altered (DataObject object, Alteration alteration) {
512        assert (internal_contains (object));
513
514        bool resort_occurred = dataset.resort_object (object, alteration);
515
516        if (are_notifications_frozen ()) {
517            if (frozen_items_altered == null)
518                frozen_items_altered = new Gee.HashMap<DataObject, Alteration> ();
519
520            // if an alteration for the object is already in place, compress the two and add the
521            // new one, otherwise set the supplied one
522            Alteration? current = frozen_items_altered.get (object);
523            if (current != null)
524                current = current.compress (alteration);
525            else
526                current = alteration;
527
528            frozen_items_altered.set (object, current);
529
530            fire_ordering_changed = fire_ordering_changed || resort_occurred;
531
532            return;
533        }
534
535        if (resort_occurred)
536            notify_ordering_changed ();
537
538        notify_items_altered (get_alteration_singleton (object, alteration));
539    }
540
541    public Value? get_property (string name) {
542        return properties.get (name);
543    }
544
545    public void set_property (string name, Value val, ValueEqualFunc? value_equals = null) {
546        if (value_equals == null) {
547            if (val.holds (typeof (bool)))
548                value_equals = bool_value_equals;
549            else if (val.holds (typeof (int)))
550                value_equals = int_value_equals;
551            else
552                error ("value_equals must be specified for this type");
553        }
554
555        Value? old = properties.get (name);
556        if (old != null) {
557            if (value_equals (old, val))
558                return;
559        }
560
561        properties.set (name, val);
562
563        notify_property_set (name, old, val);
564
565        // notify all items in the collection of the change
566        int count = dataset.get_count ();
567        for (int ctr = 0; ctr < count; ctr++)
568            dataset.get_at (ctr).notify_collection_property_set (name, old, val);
569    }
570
571    public void clear_property (string name) {
572        if (!properties.unset (name))
573            return;
574
575        // only notify if the propery was unset (that is, was set to begin with)
576        notify_property_cleared (name);
577
578        // notify all items
579        int count = dataset.get_count ();
580        for (int ctr = 0; ctr < count; ctr++)
581            dataset.get_at (ctr).notify_collection_property_cleared (name);
582    }
583
584    // This is only guaranteed to freeze notifications that come in from contained objects and
585    // need to be propagated with collection signals.  Thus, the caller can freeze notifications,
586    // make modifications to many or all member objects, then unthaw and have the aggregated signals
587    // fired at once.
588    //
589    // DataObject/DataSource/DataView should also "eat" their signals as well, to prevent observers
590    // from being notified while their collection is frozen, and only fire them when
591    // internal_collection_thawed is called.
592    //
593    // For DataCollection, the signals affected are items_altered and ordering_changed.
594    public void freeze_notifications () {
595        if (notifies_frozen++ == 0)
596            notify_frozen ();
597    }
598
599    public void thaw_notifications () {
600        if (notifies_frozen == 0)
601            return;
602
603        if (--notifies_frozen == 0)
604            notify_thawed ();
605    }
606
607    public bool are_notifications_frozen () {
608        return notifies_frozen > 0;
609    }
610
611    // This is called when notifications have frozen.  Child collections should halt notifications
612    // until thawed () is called.
613    protected virtual void notify_frozen () {
614        frozen ();
615    }
616
617    // This is called when enough thaw_notifications () calls have been made.  Child collections
618    // should issue caught notifications.
619    protected virtual void notify_thawed () {
620        if (frozen_items_altered != null) {
621            // refs are swapped around due to reentrancy
622            Gee.Map<DataObject, Alteration> copy = frozen_items_altered;
623            frozen_items_altered = null;
624
625            notify_items_altered (copy);
626        }
627
628        if (fire_ordering_changed) {
629            fire_ordering_changed = false;
630            notify_ordering_changed ();
631        }
632
633        thawed ();
634    }
635}
636